refactor(chat): split screen orchestration
Docker Image / Build and Push Docker Image (push) Successful in 14m9s
Docker Image / Build and Push Docker Image (push) Successful in 14m9s
This commit is contained in:
+33
-127
@@ -1,18 +1,10 @@
|
||||
"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 { useChatState } from "@/stores/chat/chat-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";
|
||||
|
||||
@@ -22,32 +14,26 @@ import {
|
||||
ChatHeader,
|
||||
ChatInputBar,
|
||||
ChatInsufficientCreditsBanner,
|
||||
ChatUnlockDialogs,
|
||||
ExternalBrowserDialog,
|
||||
FirstRechargeOfferBanner,
|
||||
HistoryUnlockDialog,
|
||||
InsufficientCreditsDialog,
|
||||
PwaInstallOverlay,
|
||||
} from "./components";
|
||||
import {
|
||||
deriveIsGuest,
|
||||
getInsufficientCreditsMessageLimitView,
|
||||
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 { useChatGuestLogin } from "./hooks/use-chat-guest-login";
|
||||
import { useChatMessageLimitBanner } from "./hooks/use-chat-message-limit-banner";
|
||||
import { useExternalBrowserPrompt } from "./hooks/use-external-browser-prompt";
|
||||
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,
|
||||
@@ -59,100 +45,33 @@ export function ChatScreen() {
|
||||
const isGuest = deriveIsGuest(authState.loginStatus);
|
||||
|
||||
// 发送能力由后端统一返回,当前只处理积分不足引导。
|
||||
const showMessageLimitBanner =
|
||||
state.upgradePromptVisible &&
|
||||
state.upgradeReason === "insufficient_credits";
|
||||
const messageLimitView = getInsufficientCreditsMessageLimitView(
|
||||
authState.loginStatus,
|
||||
);
|
||||
const messageLimitBanner = useChatMessageLimitBanner({
|
||||
upgradePromptVisible: state.upgradePromptVisible,
|
||||
upgradeReason: state.upgradeReason,
|
||||
loginStatus: authState.loginStatus,
|
||||
});
|
||||
const firstRechargeOfferBanner = useFirstRechargeOfferBanner({
|
||||
historyLoaded: state.historyLoaded,
|
||||
loginStatus: authState.loginStatus,
|
||||
});
|
||||
const shouldShowPwaInstall = state.historyLoaded && state.messages.length >= 10;
|
||||
const externalBrowserPrompt = useExternalBrowserPrompt({
|
||||
hasInitialized: authState.hasInitialized,
|
||||
isLoading: authState.isLoading,
|
||||
loginStatus: authState.loginStatus,
|
||||
});
|
||||
|
||||
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<void> {
|
||||
setShowExternalBrowserDialog(false);
|
||||
await openChatInExternalBrowser();
|
||||
}
|
||||
useChatGuestLogin({
|
||||
dispatch: authDispatch,
|
||||
hasInitialized: authState.hasInitialized,
|
||||
isLoading: authState.isLoading,
|
||||
loginStatus: authState.loginStatus,
|
||||
});
|
||||
|
||||
function handleUnlockPrivateMessage(messageId: string): void {
|
||||
requestMessageUnlock(messageId, "private");
|
||||
}
|
||||
|
||||
function handleMessageLimitUnlock(): void {
|
||||
if (messageLimitView.action === "auth") {
|
||||
navigator.openAuth(ROUTES.chat);
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.openSubscription({
|
||||
type: getInsufficientCreditsSubscriptionType(userState.isVip),
|
||||
returnTo: "chat",
|
||||
});
|
||||
}
|
||||
|
||||
function handleUnlockVoiceMessage(messageId: string): void {
|
||||
requestMessageUnlock(messageId, "voice");
|
||||
}
|
||||
@@ -187,19 +106,18 @@ export function ChatScreen() {
|
||||
<ChatArea
|
||||
messages={state.messages}
|
||||
isReplyingAI={state.isReplyingAI}
|
||||
isGuest={isGuest}
|
||||
isUnlockingMessage={state.isUnlockingMessage}
|
||||
unlockingMessageId={state.unlockingMessageId}
|
||||
onUnlockPrivateMessage={handleUnlockPrivateMessage}
|
||||
onUnlockVoiceMessage={handleUnlockVoiceMessage}
|
||||
/>
|
||||
|
||||
{showMessageLimitBanner ? (
|
||||
{messageLimitBanner.visible ? (
|
||||
<ChatInsufficientCreditsBanner
|
||||
title={messageLimitView.title}
|
||||
description={messageLimitView.description}
|
||||
ctaLabel={messageLimitView.ctaLabel}
|
||||
onUnlock={handleMessageLimitUnlock}
|
||||
title={messageLimitBanner.title}
|
||||
description={messageLimitBanner.description}
|
||||
ctaLabel={messageLimitBanner.ctaLabel}
|
||||
onUnlock={messageLimitBanner.unlock}
|
||||
/>
|
||||
) : (
|
||||
<ChatInputBar disabled={!state.historyLoaded} />
|
||||
@@ -213,27 +131,15 @@ export function ChatScreen() {
|
||||
<PwaInstallOverlay enabled={shouldShowPwaInstall} />
|
||||
|
||||
<ExternalBrowserDialog
|
||||
open={showExternalBrowserDialog}
|
||||
onClose={() => setShowExternalBrowserDialog(false)}
|
||||
onConfirm={() => void handleOpenExternalBrowser()}
|
||||
open={externalBrowserPrompt.open}
|
||||
onClose={externalBrowserPrompt.close}
|
||||
onConfirm={() => void externalBrowserPrompt.confirm()}
|
||||
/>
|
||||
|
||||
<HistoryUnlockDialog
|
||||
open={state.unlockHistoryPromptVisible}
|
||||
lockedCount={state.lockedHistoryCount}
|
||||
isLoading={state.isUnlockingHistory}
|
||||
errorMessage={state.unlockHistoryError}
|
||||
onClose={() => chatDispatch({ type: "ChatUnlockHistoryDismissed" })}
|
||||
onConfirm={() => chatDispatch({ type: "ChatUnlockHistoryConfirmed" })}
|
||||
/>
|
||||
|
||||
<InsufficientCreditsDialog
|
||||
open={unlockPaywallRequest !== null}
|
||||
creditBalance={unlockPaywallRequest?.creditBalance ?? 0}
|
||||
requiredCredits={unlockPaywallRequest?.requiredCredits ?? 0}
|
||||
shortfallCredits={unlockPaywallRequest?.shortfallCredits ?? 0}
|
||||
onClose={closeInsufficientCreditsDialog}
|
||||
onConfirm={confirmInsufficientCreditsDialog}
|
||||
<ChatUnlockDialogs
|
||||
unlockPaywallRequest={unlockPaywallRequest}
|
||||
onCloseInsufficientCreditsDialog={closeInsufficientCreditsDialog}
|
||||
onConfirmInsufficientCreditsDialog={confirmInsufficientCreditsDialog}
|
||||
/>
|
||||
</div>
|
||||
</MobileShell>
|
||||
|
||||
Reference in New Issue
Block a user