"use client"; import { useEffect, useRef, useState } from "react"; import Image from "next/image"; import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context"; import { useChatDispatch, useChatState } from "@/stores/chat/chat-context"; import { useUserState } from "@/stores/user/user-context"; import { ROUTES } from "@/router/routes"; import { useAppNavigator } from "@/router/use-app-navigator"; import { openChatInExternalBrowser, recordExternalBrowserPromptShown, resolveExternalBrowserPromptEligibility, } from "@/lib/chat/chat_external_browser"; import { MobileShell } from "@/app/_components/core"; import { BrowserHintOverlay, ChatArea, ChatHeader, ChatInputBar, ChatInsufficientCreditsBanner, ExternalBrowserDialog, FirstRechargeOfferBanner, HistoryUnlockDialog, InsufficientCreditsDialog, PwaInstallOverlay, } from "./components"; import { deriveIsGuest, getInsufficientCreditsSubscriptionType, isChatDevelopmentEnvironment, shouldStartExternalBrowserPrompt, } from "./chat-screen.helpers"; import { useFirstRechargeOfferBanner } from "./hooks/use-first-recharge-offer-banner"; import { useChatUnlockNavigationFlow } from "./hooks/use-chat-unlock-navigation-flow"; import styles from "./components/chat-screen.module.css"; export function ChatScreen() { const state = useChatState(); const chatDispatch = useChatDispatch(); const authState = useAuthState(); const authDispatch = useAuthDispatch(); const userState = useUserState(); const navigator = useAppNavigator(); const [showExternalBrowserDialog, setShowExternalBrowserDialog] = useState(false); const { unlockPaywallRequest, requestMessageUnlock, closeInsufficientCreditsDialog, confirmInsufficientCreditsDialog, } = useChatUnlockNavigationFlow({ returnUrl: ROUTES.chat }); // isGuest 派生(chat-screen 是唯一知道这个值的地方 —— chat 机器无 isGuest 字段) const isGuest = deriveIsGuest(authState.loginStatus); // 发送能力由后端统一返回,当前只处理积分不足引导。 const showMessageLimitBanner = state.upgradePromptVisible && state.upgradeReason === "insufficient_credits"; const messageLimitTitle = "Insufficient credits\nTop up to continue chatting"; const messageLimitCtaLabel = "Top up credits to continue"; const firstRechargeOfferBanner = useFirstRechargeOfferBanner({ historyLoaded: state.historyLoaded, loginStatus: authState.loginStatus, }); const shouldShowPwaInstall = state.historyLoaded && state.messages.length >= 10; const externalBrowserPromptShownRef = useRef(false); const guestLoginRequestedRef = useRef(false); useEffect(() => { if (!authState.hasInitialized || authState.isLoading) return; if (authState.loginStatus !== "notLoggedIn") { guestLoginRequestedRef.current = false; return; } if (guestLoginRequestedRef.current) return; guestLoginRequestedRef.current = true; authDispatch({ type: "AuthGuestLoginSubmitted" }); }, [ authDispatch, authState.hasInitialized, authState.isLoading, authState.loginStatus, ]); useEffect(() => { if ( !shouldStartExternalBrowserPrompt({ hasInitialized: authState.hasInitialized, isLoading: authState.isLoading, loginStatus: authState.loginStatus, }) ) { return; } let mounted = true; let timer: number | undefined; const init = async () => { if (externalBrowserPromptShownRef.current) return; const eligibility = await resolveExternalBrowserPromptEligibility(); if (!mounted || !eligibility.canShow) return; externalBrowserPromptShownRef.current = true; timer = window.setTimeout(() => { if (!mounted) return; setShowExternalBrowserDialog(true); void recordExternalBrowserPromptShown(eligibility.today); }, 3000); }; void init(); return () => { mounted = false; if (timer !== undefined) window.clearTimeout(timer); }; }, [ authState.hasInitialized, authState.isLoading, authState.loginStatus, ]); async function handleOpenExternalBrowser(): Promise { setShowExternalBrowserDialog(false); await openChatInExternalBrowser(); } function handleUnlockPrivateMessage(messageId: string): void { requestMessageUnlock(messageId, "private"); } function handleMessageLimitUnlock(): void { navigator.openSubscription({ type: getInsufficientCreditsSubscriptionType(userState.isVip), returnTo: "chat", }); } function handleUnlockVoiceMessage(messageId: string): void { requestMessageUnlock(messageId, "voice"); } return (
{/* isGuest 派生自 auth.loginStatus */} } /> {showMessageLimitBanner ? ( ) : ( )}
{/* 浏览器提示(应用内浏览器检测) */} {/* PWA 安装提示触发器(聊天消息数量达到阈值后才允许弹出) */} setShowExternalBrowserDialog(false)} onConfirm={() => void handleOpenExternalBrowser()} /> chatDispatch({ type: "ChatUnlockHistoryDismissed" })} onConfirm={() => chatDispatch({ type: "ChatUnlockHistoryConfirmed" })} />
); }