diff --git a/src/app/chat/chat-screen.tsx b/src/app/chat/chat-screen.tsx index 348bf34c..9e0e7694 100644 --- a/src/app/chat/chat-screen.tsx +++ b/src/app/chat/chat-screen.tsx @@ -58,19 +58,13 @@ export function ChatScreen() { authState.loginStatus !== "notLoggedIn" && authState.loginStatus !== "guest"; - // 消息数量限制由后端统一返回 lockDetail,前端不再处理本地额度。 + // 发送能力由后端统一返回,当前只处理积分不足引导。 const showMessageLimitBanner = state.upgradePromptVisible && - (state.upgradeReason === "weekly_limit" || - state.upgradeReason === "insufficient_credits"); + state.upgradeReason === "insufficient_credits"; const messageLimitTitle = - state.upgradeReason === "insufficient_credits" - ? "Insufficient credits\nTop up to continue chatting" - : "The limit for free chat times\nhas been reached"; - const messageLimitCtaLabel = - state.upgradeReason === "insufficient_credits" - ? "Top up credits to continue" - : "Unlock your membership to continue"; + "Insufficient credits\nTop up to continue chatting"; + const messageLimitCtaLabel = "Top up credits to continue"; const externalBrowserPromptShownRef = useRef(false); @@ -188,15 +182,10 @@ export function ChatScreen() { } function handleMessageLimitUnlock(): void { - const subscriptionType = - state.upgradeReason === "insufficient_credits" - ? getInsufficientCreditsSubscriptionType(userState.isVip) - : "vip"; - router.push( getChatPaywallNavigationUrl( authState.loginStatus, - subscriptionType, + getInsufficientCreditsSubscriptionType(userState.isVip), ), ); } diff --git a/src/app/chat/components/chat-quota-exhausted-banner.tsx b/src/app/chat/components/chat-quota-exhausted-banner.tsx index 1caebe8e..ae7ca4af 100644 --- a/src/app/chat/components/chat-quota-exhausted-banner.tsx +++ b/src/app/chat/components/chat-quota-exhausted-banner.tsx @@ -1,14 +1,14 @@ "use client"; /** - * ChatQuotaExhaustedBanner 游客配额耗尽横幅 + * ChatQuotaExhaustedBanner 发送受限横幅 * * 何时显示: - * - 后端返回 lockDetail.reason="weekly_limit" 且 showUpgrade=true + * - 后端返回 cannotSendReason="insufficient_credits" * * 视觉规格(与设计稿对齐): * - 粉→品红渐变背景(与 splash 渐变一致) - * - 大字号粉色/白色提示文案("The limit for free chat times has been reached today") - * - 粉→品红渐变 pill 按钮"Unlock your membership to continue" → 跳 /subscription + * - 大字号粉色/白色提示文案 + * - 粉→品红渐变 pill 按钮 → 跳 /subscription * * 业务关系: * - 与 `src/app/sidebar/components/vip-benefits-card.tsx` 的 "Activate VIP Membership" 行为一致 @@ -36,8 +36,8 @@ export interface ChatQuotaExhaustedBannerProps { } export function ChatQuotaExhaustedBanner({ - title = "The limit for free chat times\nhas been reached today", - ctaLabel = "Unlock your membership to continue", + title = "Insufficient credits\nTop up to continue chatting", + ctaLabel = "Top up credits to continue", onUnlock, }: ChatQuotaExhaustedBannerProps) { const router = useRouter(); diff --git a/src/data/storage/chat/chat_storage.ts b/src/data/storage/chat/chat_storage.ts index 98e9bb09..476427db 100644 --- a/src/data/storage/chat/chat_storage.ts +++ b/src/data/storage/chat/chat_storage.ts @@ -14,5 +14,5 @@ export class ChatStorage { } // 本地游客消息数量额度逻辑已停用: - // 游客 / 非游客的消息数量限制统一由后端接口返回 lockDetail/daily_limit。 + // 游客 / 非游客的发送能力统一以后端返回的积分状态为准。 } diff --git a/src/stores/chat/__tests__/chat-machine.helpers.test.ts b/src/stores/chat/__tests__/chat-machine.helpers.test.ts index a03ddf2c..f34b82da 100644 --- a/src/stores/chat/__tests__/chat-machine.helpers.test.ts +++ b/src/stores/chat/__tests__/chat-machine.helpers.test.ts @@ -143,43 +143,6 @@ describe("sendResponseToUiMessage", () => { }); describe("applyHttpSendOutput", () => { - it("removes the optimistic user message when the weekly limit is reached", () => { - const context = makeChatState({ - messages: [ - { - content: "hello", - isFromAI: false, - date: "2026-06-25", - }, - ], - }); - - const nextState = applyHttpSendOutput(context, { - response: makeResponse({ - reply: "", - messageId: "", - lockDetail: { - locked: true, - showContent: false, - showUpgrade: true, - reason: "weekly_limit", - hint: "Free message limit reached.", - detail: { - type: "weekly_msg_limit", - usedThisWeek: 3, - limit: 3, - }, - }, - }), - reply: null, - }); - - expect(nextState.messages).toEqual([]); - expect(nextState.isReplyingAI).toBe(false); - expect(nextState.upgradePromptVisible).toBe(true); - expect(nextState.upgradeReason).toBe("weekly_limit"); - }); - it("stores insufficient credit state and removes failed optimistic messages", () => { const context = makeChatState({ messages: [ @@ -363,7 +326,7 @@ describe("countLockedHistoryMessages", () => { isFromAI: true, date: "2026-06-29", locked: true, - lockReason: "weekly_limit", + lockReason: "insufficient_credits", }, { content: "user text", diff --git a/src/stores/chat/__tests__/chat-machine.transitions.test.ts b/src/stores/chat/__tests__/chat-machine.transitions.test.ts index 61d3e527..2f24285b 100644 --- a/src/stores/chat/__tests__/chat-machine.transitions.test.ts +++ b/src/stores/chat/__tests__/chat-machine.transitions.test.ts @@ -767,7 +767,7 @@ describe("chatMachine transitions", () => { actor.stop(); }); - it("clears the weekly limit prompt after payment succeeds", async () => { + it("clears the insufficient credits prompt after payment succeeds", async () => { const actor = createActor(createTestChatMachine()).start(); actor.send({ type: "ChatUserLogin", token: "token" }); @@ -789,12 +789,12 @@ describe("chatMachine transitions", () => { locked: true, showContent: false, showUpgrade: true, - reason: "weekly_limit", - hint: "Free message limit reached.", + reason: "insufficient_credits", + hint: "Insufficient credits.", detail: { - type: "weekly_msg_limit", - usedThisWeek: 3, - limit: 3, + requiredCredits: 2, + currentCredits: 0, + shortfallCredits: 2, }, }, canSendMessage: false, @@ -808,7 +808,9 @@ describe("chatMachine transitions", () => { }); expect(actor.getSnapshot().context.upgradePromptVisible).toBe(true); - expect(actor.getSnapshot().context.upgradeReason).toBe("weekly_limit"); + expect(actor.getSnapshot().context.upgradeReason).toBe( + "insufficient_credits", + ); expect(actor.getSnapshot().context.canSendMessage).toBe(false); actor.send({ type: "ChatPaymentSucceeded" }); diff --git a/src/stores/chat/chat-machine.ts b/src/stores/chat/chat-machine.ts index 673015af..a4066792 100644 --- a/src/stores/chat/chat-machine.ts +++ b/src/stores/chat/chat-machine.ts @@ -21,9 +21,9 @@ * - guestSession / userSession:队列串行走 HTTP,限制以后端 `lockDetail` 为准 * - `pendingReplyCount` 跟踪待回复数量,`isReplyingAI` 由计数派生 * - * 配额: + * 发送能力: * - 前端不再处理本地消息额度;游客 / 注册用户均以后端响应为准。 - * - 后端返回 `lockDetail.reason="weekly_limit"` 且需要升级时展示会员引导。 + * - 后端返回 `cannotSendReason="insufficient_credits"` 时展示充值引导。 * */ diff --git a/src/stores/chat/chat-send-flow.ts b/src/stores/chat/chat-send-flow.ts index a54244d8..4b6857c5 100644 --- a/src/stores/chat/chat-send-flow.ts +++ b/src/stores/chat/chat-send-flow.ts @@ -167,10 +167,6 @@ async function sendMessageViaHttp(content: string): Promise<{ throw result.error; } void chatRepo.prefetchMediaForSendResponse(result.data); - const isMessageLimit = - result.data.lockDetail.locked && - result.data.lockDetail.showUpgrade && - result.data.lockDetail.reason === "weekly_limit"; const isInsufficientCredits = result.data.canSendMessage === false && result.data.cannotSendReason === "insufficient_credits" && @@ -178,9 +174,7 @@ async function sendMessageViaHttp(content: string): Promise<{ return { response: result.data, reply: - isMessageLimit || isInsufficientCredits - ? null - : sendResponseToUiMessage(result.data), + isInsufficientCredits ? null : sendResponseToUiMessage(result.data), }; } diff --git a/src/stores/chat/chat-send-state.ts b/src/stores/chat/chat-send-state.ts index f78050ee..04acffb6 100644 --- a/src/stores/chat/chat-send-state.ts +++ b/src/stores/chat/chat-send-state.ts @@ -35,19 +35,12 @@ export function applyHttpSendOutput( ): Partial { const { response, reply } = output; - const lockDetail = response.lockDetail; - const isWeeklyLimitBlocked = - lockDetail.locked && - lockDetail.showUpgrade && - lockDetail.reason === "weekly_limit"; const isInsufficientCredits = response.canSendMessage === false && response.cannotSendReason === "insufficient_credits"; - const upgradeReason: ChatUpgradeReason | null = isWeeklyLimitBlocked - ? "weekly_limit" - : isInsufficientCredits - ? "insufficient_credits" - : null; + const upgradeReason: ChatUpgradeReason | null = isInsufficientCredits + ? "insufficient_credits" + : null; const sendCapabilityState = getSendCapabilityState(response); if (upgradeReason) { @@ -73,7 +66,10 @@ export function applyHttpSendOutput( }; } - if (lockDetail.showUpgrade && lockDetail.reason === "private_message") { + if ( + response.lockDetail.showUpgrade && + response.lockDetail.reason === "private_message" + ) { return { messages: [...context.messages, reply], ...finishPendingReply(context), diff --git a/src/stores/chat/chat-state.ts b/src/stores/chat/chat-state.ts index 0c19ca9d..83847682 100644 --- a/src/stores/chat/chat-state.ts +++ b/src/stores/chat/chat-state.ts @@ -1,7 +1,7 @@ import type { UiMessage } from "@/data/dto/chat"; import type { PendingChatUnlockKind } from "@/lib/navigation/chat_unlock_session"; -export type ChatUpgradeReason = "weekly_limit" | "insufficient_credits"; +export type ChatUpgradeReason = "insufficient_credits"; export interface ChatUnlockPaywallRequest { messageId: string;