From bfbf7ee91ab81a6200d5d7eddae1bf232a38ceb4 Mon Sep 17 00:00:00 2001 From: chenhang Date: Wed, 24 Jun 2026 10:02:22 +0800 Subject: [PATCH] feat(app): improve payment and paywall UX --- .../back-button.module.css} | 4 +- src/app/_components/back-button.tsx | 28 ++++++++ .../core/auth-back-button.module.css | 22 ------ src/app/_components/core/auth-back-button.tsx | 59 --------------- src/app/_components/core/index.ts | 1 - src/app/_components/index.ts | 5 ++ src/app/auth/components/auth-back-button.tsx | 22 ------ src/app/auth/components/auth-panel.tsx | 4 +- src/app/auth/components/index.ts | 1 - src/app/chat/chat-screen.tsx | 21 +++--- src/app/chat/components/chat-area.tsx | 6 ++ .../fullscreen-image-viewer.module.css | 65 +++++++++++++++++ .../components/fullscreen-image-viewer.tsx | 50 ++++++++++++- src/app/chat/components/image-bubble.tsx | 17 ++++- src/app/chat/components/message-bubble.tsx | 8 +++ src/app/chat/components/message-content.tsx | 12 +++- src/app/subscription/components/index.ts | 1 + ...cription-payment-success-dialog.module.css | 72 +++++++++++++++++++ .../subscription-payment-success-dialog.tsx | 44 ++++++++++++ src/app/subscription/subscription-screen.tsx | 20 +++++- src/data/dto/chat/ui_message.ts | 2 + src/stores/chat/chat-machine.helpers.ts | 7 +- src/tokens/dimensions.css | 2 +- 23 files changed, 344 insertions(+), 129 deletions(-) rename src/app/{auth/components/auth-back-button.module.css => _components/back-button.module.css} (87%) create mode 100644 src/app/_components/back-button.tsx delete mode 100644 src/app/_components/core/auth-back-button.module.css delete mode 100644 src/app/_components/core/auth-back-button.tsx create mode 100644 src/app/_components/index.ts delete mode 100644 src/app/auth/components/auth-back-button.tsx create mode 100644 src/app/subscription/components/subscription-payment-success-dialog.module.css create mode 100644 src/app/subscription/components/subscription-payment-success-dialog.tsx diff --git a/src/app/auth/components/auth-back-button.module.css b/src/app/_components/back-button.module.css similarity index 87% rename from src/app/auth/components/auth-back-button.module.css rename to src/app/_components/back-button.module.css index f29ef8c0..839eea03 100644 --- a/src/app/auth/components/auth-back-button.module.css +++ b/src/app/_components/back-button.module.css @@ -6,8 +6,8 @@ display: inline-flex; align-items: center; justify-content: center; - width: var(--auth-back-button-size); - height: var(--auth-back-button-size); + width: var(--back-button-size); + height: var(--back-button-size); padding: 0 2px 0 0; color: var(--color-auth-text-primary); cursor: pointer; diff --git a/src/app/_components/back-button.tsx b/src/app/_components/back-button.tsx new file mode 100644 index 00000000..1870a416 --- /dev/null +++ b/src/app/_components/back-button.tsx @@ -0,0 +1,28 @@ +"use client"; + +import { ChevronLeft } from "lucide-react"; + +import styles from "./back-button.module.css"; + +export interface BackButtonProps { + onClick: () => void; + className?: string; + ariaLabel?: string; +} + +export function BackButton({ + onClick, + className, + ariaLabel = "Back", +}: BackButtonProps) { + return ( + + ); +} diff --git a/src/app/_components/core/auth-back-button.module.css b/src/app/_components/core/auth-back-button.module.css deleted file mode 100644 index 6fa6c265..00000000 --- a/src/app/_components/core/auth-back-button.module.css +++ /dev/null @@ -1,22 +0,0 @@ -.button { - display: inline-flex; - align-items: center; - justify-content: center; - width: 40px; - height: 40px; - border: none; - border-radius: var(--radius-full); - background: transparent; - color: var(--color-text-primary); - cursor: pointer; - transition: background 0.15s ease; -} - -.button:hover { - background: rgba(255, 255, 255, 0.08); -} - -.button:focus-visible { - outline: var(--border-medium) solid var(--color-accent); - outline-offset: 2px; -} diff --git a/src/app/_components/core/auth-back-button.tsx b/src/app/_components/core/auth-back-button.tsx deleted file mode 100644 index e2d8d1de..00000000 --- a/src/app/_components/core/auth-back-button.tsx +++ /dev/null @@ -1,59 +0,0 @@ -"use client"; -/** - * 通用返回按钮 - * - * 原始 Dart: lib/ui/auth/widgets/back_button.dart(被 auth/chat/sidebar 共用)。 - */ -import { useRouter } from "next/navigation"; -import { type MouseEvent } from "react"; - -import styles from "./auth-back-button.module.css"; - -export interface AuthBackButtonProps { - /** 自定义跳转目标(默认 history.back)。 */ - href?: string; - /** 自定义点击处理(覆盖默认跳转)。 */ - onClick?: (e: MouseEvent) => void; - className?: string; - ariaLabel?: string; -} - -export function AuthBackButton({ - href, - onClick, - className, - ariaLabel = "Back", -}: AuthBackButtonProps) { - const router = useRouter(); - return ( - - ); -} diff --git a/src/app/_components/core/index.ts b/src/app/_components/core/index.ts index ed7c62e1..edcd15ad 100644 --- a/src/app/_components/core/index.ts +++ b/src/app/_components/core/index.ts @@ -2,7 +2,6 @@ * @file Automatically generated by barrelsby. */ -export * from "./auth-back-button"; export * from "./bottom-sheet"; export * from "./checkbox"; export * from "./dialog"; diff --git a/src/app/_components/index.ts b/src/app/_components/index.ts new file mode 100644 index 00000000..b1560313 --- /dev/null +++ b/src/app/_components/index.ts @@ -0,0 +1,5 @@ +/** + * @file Shared app component barrel. + */ + +export * from "./back-button"; diff --git a/src/app/auth/components/auth-back-button.tsx b/src/app/auth/components/auth-back-button.tsx deleted file mode 100644 index 5203cec9..00000000 --- a/src/app/auth/components/auth-back-button.tsx +++ /dev/null @@ -1,22 +0,0 @@ -"use client"; - -import { ChevronLeft } from "lucide-react"; - -import styles from "./auth-back-button.module.css"; - -export interface AuthBackButtonProps { - onClick: () => void; -} - -export function AuthBackButton({ onClick }: AuthBackButtonProps) { - return ( - - ); -} diff --git a/src/app/auth/components/auth-panel.tsx b/src/app/auth/components/auth-panel.tsx index 5a21cb4b..86cc391e 100644 --- a/src/app/auth/components/auth-panel.tsx +++ b/src/app/auth/components/auth-panel.tsx @@ -4,9 +4,9 @@ */ import { useRouter } from "next/navigation"; +import { BackButton } from "@/app/_components"; import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context"; -import { AuthBackButton } from "./auth-back-button"; import { AuthEmailPanel } from "./auth-email-panel"; import { AuthFacebookPanel } from "./auth-facebook-panel"; import styles from "./auth-panel.module.css"; @@ -31,7 +31,7 @@ export function AuthPanel() { return (
- + {state.authPanelMode === "facebook" ? ( diff --git a/src/app/auth/components/index.ts b/src/app/auth/components/index.ts index fc76f775..45497f29 100644 --- a/src/app/auth/components/index.ts +++ b/src/app/auth/components/index.ts @@ -3,7 +3,6 @@ */ export * from "./auth-background"; -export * from "./auth-back-button"; export * from "./auth-divider"; export * from "./auth-email-panel"; export * from "./auth-error-message"; diff --git a/src/app/chat/chat-screen.tsx b/src/app/chat/chat-screen.tsx index 3c32dabf..eee3b8d8 100644 --- a/src/app/chat/chat-screen.tsx +++ b/src/app/chat/chat-screen.tsx @@ -55,18 +55,12 @@ export function ChatScreen() { ? "The limit for free chat times\nhas been reached" : undefined; const messageLimitCta = isGuest ? "Log in to continue chatting" : undefined; - const showPhotoPaywallBanner = - state.paywallTriggered && state.paywallReason === "photo_paywall"; + const showPrivatePaywallBanner = state.paywallTriggered && state.paywallReason === "private_paywall"; - const showInlineUpgradeBanner = - showPhotoPaywallBanner || showPrivatePaywallBanner; - const inlineUpgradeTitle = showPrivatePaywallBanner - ? "Unlock VIP to view private messages" - : "Unlock VIP to view Elio's photos"; - const inlineUpgradeCta = showPrivatePaywallBanner - ? "Activate VIP to view private messages" - : "Activate VIP to view photos"; + + const inlineUpgradeTitle = "Unlock VIP to view private messages"; + const inlineUpgradeCta = "Activate VIP to view private messages"; const externalBrowserPromptShownRef = useRef(false); @@ -143,6 +137,10 @@ export function ChatScreen() { chatDispatch({ type: "ChatUnlockPrivateMessage", messageId }); } + function handleUnlockImagePaywall(): void { + router.push(`${ROUTES.subscription}?type=vip`); + } + function handleMessageLimitUnlock(): void { if (isGuest) { router.push(ROUTES.auth); @@ -175,9 +173,10 @@ export function ChatScreen() { isGuest={isGuest} unlockingPrivateMessageId={state.unlockingPrivateMessageId} onUnlockPrivateMessage={handleUnlockPrivateMessage} + onUnlockImagePaywall={handleUnlockImagePaywall} /> - {showInlineUpgradeBanner ? ( + {showPrivatePaywallBanner ? ( void; + onUnlockImagePaywall?: () => void; } export function ChatArea({ @@ -37,6 +38,7 @@ export function ChatArea({ isReplyingAI, unlockingPrivateMessageId, onUnlockPrivateMessage, + onUnlockImagePaywall, }: ChatAreaProps) { const scrollRef = useRef(null); const prevLengthRef = useRef(messages.length); @@ -60,6 +62,7 @@ export function ChatArea({ messages, unlockingPrivateMessageId, onUnlockPrivateMessage, + onUnlockImagePaywall, )} {isReplyingAI && } @@ -72,6 +75,7 @@ function renderMessagesWithDateHeaders( messages: readonly UiMessage[], unlockingPrivateMessageId?: string | null, onUnlockPrivateMessage?: (messageId: string) => void, + onUnlockImagePaywall?: () => void, ) { const items: Array<{ type: "date"; date: string; key: string } | { type: "msg"; message: UiMessage; key: string }> = []; @@ -91,6 +95,7 @@ function renderMessagesWithDateHeaders( id={item.message.id} content={item.message.content} imageUrl={item.message.imageUrl} + imagePaywalled={item.message.imagePaywalled} isFromAI={item.message.isFromAI} privateLocked={item.message.privateLocked} privateHint={item.message.privateHint} @@ -99,6 +104,7 @@ function renderMessagesWithDateHeaders( item.message.id === unlockingPrivateMessageId } onUnlockPrivateMessage={onUnlockPrivateMessage} + onUnlockImagePaywall={onUnlockImagePaywall} /> ), ); diff --git a/src/app/chat/components/fullscreen-image-viewer.module.css b/src/app/chat/components/fullscreen-image-viewer.module.css index 9219270f..9bd2beaf 100644 --- a/src/app/chat/components/fullscreen-image-viewer.module.css +++ b/src/app/chat/components/fullscreen-image-viewer.module.css @@ -11,6 +11,11 @@ cursor: pointer; } +.paywallViewer { + cursor: default; + overflow: hidden; +} + .viewer img { max-width: 100%; max-height: 100%; @@ -19,6 +24,66 @@ -webkit-user-drag: none; } +.paywallImage { + filter: blur(18px); + object-fit: cover; + transform: scale(1.08); +} + +.paywallScrim { + position: absolute; + inset: 0; + background: + linear-gradient( + 180deg, + rgba(255, 255, 255, 0.04) 0%, + rgba(255, 255, 255, 0.02) 48%, + rgba(255, 255, 255, 0.18) 100% + ); + pointer-events: none; +} + +.backButton { + position: absolute; + top: 22px; + left: 18px; + z-index: 1; + display: inline-flex; + align-items: center; + justify-content: center; + width: 54px; + height: 54px; + border: 0; + border-radius: 999px; + background: rgba(255, 255, 255, 0.94); + color: #6d6d6d; + box-shadow: 0 6px 14px rgba(0, 0, 0, 0.1); + cursor: pointer; +} + +.unlockButton { + position: absolute; + right: 18px; + bottom: max(24px, env(safe-area-inset-bottom)); + left: 18px; + z-index: 1; + min-height: 64px; + border: 0; + border-radius: 999px; + background: linear-gradient(90deg, #e05ad7 0%, #ef66b0 100%); + box-shadow: 0 8px 16px rgba(130, 60, 90, 0.28); + color: #ffffff; + cursor: pointer; + font-family: var(--font-athelas), Athelas, serif; + font-size: 22px; + font-weight: 700; + letter-spacing: 0.01em; +} + +.unlockButton:active { + transform: translateY(1px); +} + .loading { width: 32px; height: 32px; diff --git a/src/app/chat/components/fullscreen-image-viewer.tsx b/src/app/chat/components/fullscreen-image-viewer.tsx index 1de1a77c..1a6232fd 100644 --- a/src/app/chat/components/fullscreen-image-viewer.tsx +++ b/src/app/chat/components/fullscreen-image-viewer.tsx @@ -9,6 +9,7 @@ * - 点击空白 / 下滑手势 → 关闭 * - 双指缩放(CSS `touch-action: pinch-zoom`) */ +import { ChevronLeft } from "lucide-react"; import Image from "next/image"; import { useEffect } from "react"; @@ -16,10 +17,17 @@ import styles from "./fullscreen-image-viewer.module.css"; export interface FullscreenImageViewerProps { imageUrl: string; + imagePaywalled?: boolean; + onUnlockImagePaywall?: () => void; onClose: () => void; } -export function FullscreenImageViewer({ imageUrl, onClose }: FullscreenImageViewerProps) { +export function FullscreenImageViewer({ + imageUrl, + imagePaywalled = false, + onUnlockImagePaywall, + onClose, +}: FullscreenImageViewerProps) { useEffect(() => { const handleKey = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); @@ -28,6 +36,44 @@ export function FullscreenImageViewer({ imageUrl, onClose }: FullscreenImageView return () => document.removeEventListener("keydown", handleKey); }, [onClose]); + const src = imageUrl.startsWith("data:") || imageUrl.startsWith("http") + ? imageUrl + : `data:image/png;base64,${imageUrl}`; + + if (imagePaywalled) { + return ( +
+ + + ); + } + return (
void; } -export function ImageBubble({ imageUrl }: ImageBubbleProps) { +export function ImageBubble({ + imageUrl, + imagePaywalled = false, + onUnlockImagePaywall, +}: ImageBubbleProps) { const [open, setOpen] = useState(false); const [error, setError] = useState(false); @@ -53,7 +59,14 @@ export function ImageBubble({ imageUrl }: ImageBubbleProps) { /> )}
- {open && setOpen(false)} />} + {open && ( + setOpen(false)} + /> + )} ); } diff --git a/src/app/chat/components/message-bubble.tsx b/src/app/chat/components/message-bubble.tsx index 3c6c10e7..7940543b 100644 --- a/src/app/chat/components/message-bubble.tsx +++ b/src/app/chat/components/message-bubble.tsx @@ -18,22 +18,26 @@ export interface MessageBubbleProps { id?: string; content: string; imageUrl?: string | null; + imagePaywalled?: boolean; isFromAI: boolean; privateLocked?: boolean | null; privateHint?: string | null; isUnlockingPrivate?: boolean; onUnlockPrivateMessage?: (messageId: string) => void; + onUnlockImagePaywall?: () => void; } export function MessageBubble({ id, content, imageUrl, + imagePaywalled, isFromAI, privateLocked, privateHint, isUnlockingPrivate, onUnlockPrivateMessage, + onUnlockImagePaywall, }: MessageBubbleProps) { const { avatarUrl } = useUserState(); @@ -46,11 +50,13 @@ export function MessageBubble({ messageId={id} content={content} imageUrl={imageUrl} + imagePaywalled={imagePaywalled} isFromAI={true} privateLocked={privateLocked} privateHint={privateHint} isUnlockingPrivate={isUnlockingPrivate} onUnlockPrivateMessage={onUnlockPrivateMessage} + onUnlockImagePaywall={onUnlockImagePaywall} />
{/* 占位:保持与头像宽度一致 */}
@@ -64,11 +70,13 @@ export function MessageBubble({ messageId={id} content={content} imageUrl={imageUrl} + imagePaywalled={imagePaywalled} isFromAI={false} privateLocked={privateLocked} privateHint={privateHint} isUnlockingPrivate={isUnlockingPrivate} onUnlockPrivateMessage={onUnlockPrivateMessage} + onUnlockImagePaywall={onUnlockImagePaywall} />
diff --git a/src/app/chat/components/message-content.tsx b/src/app/chat/components/message-content.tsx index 76064fb8..6adddfe3 100644 --- a/src/app/chat/components/message-content.tsx +++ b/src/app/chat/components/message-content.tsx @@ -17,11 +17,13 @@ export interface MessageContentProps { messageId?: string; content: string; imageUrl?: string | null; + imagePaywalled?: boolean; isFromAI: boolean; privateLocked?: boolean | null; privateHint?: string | null; isUnlockingPrivate?: boolean; onUnlockPrivateMessage?: (messageId: string) => void; + onUnlockImagePaywall?: () => void; } const IMAGE_PLACEHOLDER = "[图片]"; @@ -30,11 +32,13 @@ export function MessageContent({ messageId, content, imageUrl, + imagePaywalled, isFromAI, privateLocked, privateHint, isUnlockingPrivate = false, onUnlockPrivateMessage, + onUnlockImagePaywall, }: MessageContentProps) { const hasImage = imageUrl != null && imageUrl.length > 0; const hasText = content.length > 0 && content !== IMAGE_PLACEHOLDER; @@ -65,7 +69,13 @@ export function MessageContent({ /> ) : ( <> - {hasImage && imageUrl && } + {hasImage && imageUrl && ( + + )} {hasText && } )} diff --git a/src/app/subscription/components/index.ts b/src/app/subscription/components/index.ts index 19cb0abb..bc1f58c9 100644 --- a/src/app/subscription/components/index.ts +++ b/src/app/subscription/components/index.ts @@ -8,6 +8,7 @@ export * from "./subscription-benefits-card"; export * from "./subscription-checkout-button"; export * from "./subscription-cta-button"; export * from "./subscription-payment-method"; +export * from "./subscription-payment-success-dialog"; export * from "./subscription-plan-card"; export * from "./subscription-user-row"; export * from "./stripe-payment-dialog"; diff --git a/src/app/subscription/components/subscription-payment-success-dialog.module.css b/src/app/subscription/components/subscription-payment-success-dialog.module.css new file mode 100644 index 00000000..f2f30caf --- /dev/null +++ b/src/app/subscription/components/subscription-payment-success-dialog.module.css @@ -0,0 +1,72 @@ +.overlay { + position: fixed; + inset: 0; + z-index: 80; + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + background: rgba(0, 0, 0, 0.45); +} + +.dialog { + width: 100%; + max-width: 380px; + border-radius: 32px; + background: var(--color-page-background, #ffffff); + box-shadow: 0 18px 40px rgba(0, 0, 0, 0.16); + padding: 28px 20px 20px; + text-align: center; +} + +.badge { + display: inline-flex; + align-items: center; + justify-content: center; + width: 58px; + height: 58px; + margin-bottom: 16px; + border-radius: 999px; + background: linear-gradient(135deg, #ff67e0 0%, #f657a0 100%); + color: #ffffff; + font-size: 32px; + font-weight: 700; + line-height: 1; + box-shadow: 0 8px 18px rgba(247, 89, 168, 0.28); +} + +.title { + margin: 0 0 8px; + color: var(--color-text-foreground, #171717); + font-size: var(--font-size-22, 22px); + font-weight: 700; + line-height: 1.2; +} + +.content { + margin: 0; + color: #393939; + font-size: var(--font-size-md, 14px); + line-height: 1.5; +} + +.button { + width: 100%; + min-height: 48px; + margin-top: 22px; + border: 0; + border-radius: 999px; + background: + linear-gradient(269deg, #ff67e0 0%, rgba(254, 104, 224, 0.5) 20%, rgba(252, 105, 223, 0.79) 66%, #f96ade 100%), + linear-gradient(#f657a0, #f657a0); + background-blend-mode: normal, normal; + box-shadow: 0 5px 7px rgba(247, 89, 168, 0.31); + color: #ffffff; + cursor: pointer; + font-size: var(--font-size-lg, 16px); + font-weight: 700; +} + +.button:active { + transform: translateY(1px); +} diff --git a/src/app/subscription/components/subscription-payment-success-dialog.tsx b/src/app/subscription/components/subscription-payment-success-dialog.tsx new file mode 100644 index 00000000..8e827f20 --- /dev/null +++ b/src/app/subscription/components/subscription-payment-success-dialog.tsx @@ -0,0 +1,44 @@ +"use client"; + +import styles from "./subscription-payment-success-dialog.module.css"; + +export interface SubscriptionPaymentSuccessDialogProps { + open: boolean; + subscriptionType: "vip" | "voice"; + onClose: () => void; +} + +export function SubscriptionPaymentSuccessDialog({ + open, + subscriptionType, + onClose, +}: SubscriptionPaymentSuccessDialogProps) { + if (!open) return null; + + const content = + subscriptionType === "voice" + ? "Your voice package purchase was completed successfully." + : "Your VIP membership has been activated successfully."; + + return ( +
+
+ +

+ Payment successful +

+

{content}

+ +
+
+ ); +} diff --git a/src/app/subscription/subscription-screen.tsx b/src/app/subscription/subscription-screen.tsx index 532713ee..15ce67b9 100644 --- a/src/app/subscription/subscription-screen.tsx +++ b/src/app/subscription/subscription-screen.tsx @@ -14,7 +14,7 @@ * 7. 主操作按钮 * 8. 协议复选框 */ -import { useEffect, useMemo, useRef } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { Checkbox, MobileShell } from "@/app/_components/core"; import { AppConstants } from "@/core/app_constants"; @@ -32,6 +32,7 @@ import { SubscriptionBenefitsCard, SubscriptionCheckoutButton, SubscriptionPaymentMethod, + SubscriptionPaymentSuccessDialog, SubscriptionPlanCard, SubscriptionUserRow, } from "./components"; @@ -61,6 +62,9 @@ export function SubscriptionScreen({ const paymentDispatch = usePaymentDispatch(); const refreshedPaidOrderRef = useRef(null); const resumedPendingOrderRef = useRef(null); + const successDialogShownOrderRef = useRef(null); + const [showPaymentSuccessDialog, setShowPaymentSuccessDialog] = + useState(false); useEffect(() => { if (payment.status === "idle") { @@ -75,6 +79,14 @@ export function SubscriptionScreen({ userDispatch({ type: "UserFetch" }); }, [payment.currentOrderId, payment.isPaid, userDispatch]); + useEffect(() => { + if (!payment.isPaid || !payment.currentOrderId) return; + if (successDialogShownOrderRef.current === payment.currentOrderId) return; + + successDialogShownOrderRef.current = payment.currentOrderId; + setShowPaymentSuccessDialog(true); + }, [payment.currentOrderId, payment.isPaid]); + useEffect(() => { const canInspectPendingOrder = payment.status === "ready" || @@ -296,6 +308,12 @@ export function SubscriptionScreen({ } />
+ + setShowPaymentSuccessDialog(false)} + />
); diff --git a/src/data/dto/chat/ui_message.ts b/src/data/dto/chat/ui_message.ts index a0eac429..1695e4a8 100644 --- a/src/data/dto/chat/ui_message.ts +++ b/src/data/dto/chat/ui_message.ts @@ -15,6 +15,8 @@ export const UiMessageSchema = z.object({ date: z.string(), /** 图片 URL(base64 data URL 或 http URL) */ imageUrl: z.string().optional(), + /** true = 图片可在列表展示,但全屏查看时需要会员解锁 */ + imagePaywalled: z.boolean().optional(), /** 语音 URL */ voiceUrl: z.string().optional(), isPrivate: z.boolean().nullable().optional(), diff --git a/src/stores/chat/chat-machine.helpers.ts b/src/stores/chat/chat-machine.helpers.ts index e0fa41a2..e0734857 100644 --- a/src/stores/chat/chat-machine.helpers.ts +++ b/src/stores/chat/chat-machine.helpers.ts @@ -89,6 +89,9 @@ export function sendResponseToUiMessage(response: ChatSendResponse): UiMessage { isFromAI: true, date: todayString(new Date(response.timestamp)), ...(response.imageUrl ? { imageUrl: response.imageUrl } : {}), + ...(response.showUpgrade && response.imageUrl + ? { imagePaywalled: true } + : {}), }; } @@ -147,8 +150,8 @@ export function applyHttpSendOutput( return { messages: [...context.messages, reply], isReplyingAI: false, - paywallTriggered: true, - paywallReason: "photo_paywall", + paywallTriggered: false, + paywallReason: null, paywallDetail: null, }; } diff --git a/src/tokens/dimensions.css b/src/tokens/dimensions.css index 341a1763..baf2529f 100644 --- a/src/tokens/dimensions.css +++ b/src/tokens/dimensions.css @@ -51,7 +51,7 @@ --auth-legal-checkbox-inner-size: 10px; /* 悬浮返回按钮(40×40 圆形) */ - --auth-back-button-size: 40px; + --back-button-size: 40px; /* 底部弹层圆角(28px = Dart AppRadius.radius28) */ --auth-bottom-sheet-radius: 28px;