feat(chat): migrate chat protocol to lock detail

This commit is contained in:
2026-06-24 14:06:30 +08:00
parent c280a3c1ea
commit 42c03f8901
34 changed files with 609 additions and 373 deletions
+4 -8
View File
@@ -45,19 +45,15 @@ export function ChatScreen() {
// isGuest 派生(chat-screen 是唯一知道这个值的地方 —— chat 机器无 isGuest 字段)
const isGuest = deriveIsGuest(authState.loginStatus);
// 消息数量限制由后端统一返回 blocked,前端不再处理本地额度。
// 消息数量限制由后端统一返回 lockDetail,前端不再处理本地额度。
const showMessageLimitBanner =
state.paywallTriggered &&
(state.paywallReason === "daily_limit" ||
state.paywallReason === "total_limit");
state.upgradePromptVisible && state.upgradeReason === "daily_limit";
const messageLimitTitle =
state.paywallReason === "total_limit"
? "The limit for free chat times\nhas been reached"
: undefined;
state.upgradeHint ?? "The limit for free chat times\nhas been reached";
const messageLimitCta = isGuest ? "Log in to continue chatting" : undefined;
const showPrivatePaywallBanner =
state.paywallTriggered && state.paywallReason === "private_paywall";
state.upgradePromptVisible && state.upgradeReason === "private_message";
const inlineUpgradeTitle = "Unlock VIP to view private messages";
const inlineUpgradeCta = "Activate VIP to view private messages";
+2 -2
View File
@@ -95,8 +95,8 @@ function renderMessagesWithDateHeaders(
imageUrl={item.message.imageUrl}
imagePaywalled={item.message.imagePaywalled}
isFromAI={item.message.isFromAI}
privateLocked={item.message.privateLocked}
privateHint={item.message.privateHint}
lockedPrivate={item.message.lockedPrivate}
privateMessageHint={item.message.privateMessageHint}
isUnlockingPrivate={
item.message.id != null &&
item.message.id === unlockingPrivateMessageId
@@ -3,7 +3,7 @@
* ChatQuotaExhaustedBanner 游客配额耗尽横幅
*
* 何时显示:
* - 后端返回 blocked=true && blockReason="daily_limit"
* - 后端返回 lockDetail.reason="daily_limit" 且 showUpgrade=true
*
* 视觉规格(与设计稿对齐):
* - 粉→品红渐变背景(与 splash 渐变一致)
+8 -8
View File
@@ -20,8 +20,8 @@ export interface MessageBubbleProps {
imageUrl?: string | null;
imagePaywalled?: boolean;
isFromAI: boolean;
privateLocked?: boolean | null;
privateHint?: string | null;
lockedPrivate?: boolean | null;
privateMessageHint?: string | null;
isUnlockingPrivate?: boolean;
onUnlockPrivateMessage?: (messageId: string) => void;
onUnlockImagePaywall?: () => void;
@@ -33,8 +33,8 @@ export function MessageBubble({
imageUrl,
imagePaywalled,
isFromAI,
privateLocked,
privateHint,
lockedPrivate,
privateMessageHint,
isUnlockingPrivate,
onUnlockPrivateMessage,
onUnlockImagePaywall,
@@ -52,8 +52,8 @@ export function MessageBubble({
imageUrl={imageUrl}
imagePaywalled={imagePaywalled}
isFromAI={true}
privateLocked={privateLocked}
privateHint={privateHint}
lockedPrivate={lockedPrivate}
privateMessageHint={privateMessageHint}
isUnlockingPrivate={isUnlockingPrivate}
onUnlockPrivateMessage={onUnlockPrivateMessage}
onUnlockImagePaywall={onUnlockImagePaywall}
@@ -72,8 +72,8 @@ export function MessageBubble({
imageUrl={imageUrl}
imagePaywalled={imagePaywalled}
isFromAI={false}
privateLocked={privateLocked}
privateHint={privateHint}
lockedPrivate={lockedPrivate}
privateMessageHint={privateMessageHint}
isUnlockingPrivate={isUnlockingPrivate}
onUnlockPrivateMessage={onUnlockPrivateMessage}
onUnlockImagePaywall={onUnlockImagePaywall}
+6 -6
View File
@@ -17,8 +17,8 @@ export interface MessageContentProps {
imageUrl?: string | null;
imagePaywalled?: boolean;
isFromAI: boolean;
privateLocked?: boolean | null;
privateHint?: string | null;
lockedPrivate?: boolean | null;
privateMessageHint?: string | null;
isUnlockingPrivate?: boolean;
onUnlockPrivateMessage?: (messageId: string) => void;
onUnlockImagePaywall?: () => void;
@@ -32,15 +32,15 @@ export function MessageContent({
imageUrl,
imagePaywalled,
isFromAI,
privateLocked,
privateHint,
lockedPrivate,
privateMessageHint,
isUnlockingPrivate = false,
onUnlockPrivateMessage,
onUnlockImagePaywall,
}: MessageContentProps) {
const hasImage = imageUrl != null && imageUrl.length > 0;
const hasText = content.length > 0 && content !== IMAGE_PLACEHOLDER;
const isLockedPrivateMessage = privateLocked === true;
const isLockedPrivateMessage = lockedPrivate === true;
return (
<div
@@ -57,7 +57,7 @@ export function MessageContent({
>
{isLockedPrivateMessage ? (
<PrivateMessageCard
hint={privateHint}
hint={privateMessageHint}
isUnlocking={isUnlockingPrivate}
onUnlock={
messageId
@@ -14,7 +14,7 @@
*/
import { useEffect } from "react";
import { BrowserDetector, AppEnvUtil, SpAsyncUtil, pwaUtil } from "@/utils";
import { BrowserDetector, SpAsyncUtil, pwaUtil } from "@/utils";
import { PwaInstallDialog } from "./pwa-install-dialog";
import styles from "./pwa-install-overlay.module.css";