d4cb40a74b
- Move state management from `src/contexts` to `src/stores` (zustand-based) - Update barrelsby config to include new stores directory - Add bottom-sheet and auth-background components - Auto-open browser on dev server ready in VS Code launch config - Refresh generated barrel index files
122 lines
3.7 KiB
TypeScript
122 lines
3.7 KiB
TypeScript
"use client";
|
||
/**
|
||
* ChatScreen 编排层
|
||
*
|
||
* 原始 Dart: lib/ui/chat/chat_screen.dart(172 行)
|
||
*
|
||
* 职责(本文件):
|
||
* - 屏幕生命周期事件(init / visible / invisible)
|
||
* - 配额告警监听(quotaExceededTrigger + warningThreshold)
|
||
* - 渲染骨架(背景 + 顶部栏 + 消息区 + 输入栏)
|
||
*
|
||
* 子组件职责:
|
||
* - ChatHeader:顶部栏(游客 banner / 菜单按钮)
|
||
* - ChatArea:消息列表
|
||
* - ChatInputBar:输入栏(文字 + 发送 + More)
|
||
* - QuotaDialog:配额告警弹窗
|
||
* - PwaInstallOverlay:PWA 安装提示触发器
|
||
* - BrowserHintOverlay:浏览器提示
|
||
* - CharacterIntro:角色介绍卡片(首屏)
|
||
*/
|
||
import { useEffect, useRef, useState } from "react";
|
||
import Image from "next/image";
|
||
|
||
import { useChatDispatch, useChatState } from "@/stores/chat/chat-context";
|
||
import { GuestChatQuota } from "@/stores/chat";
|
||
|
||
import { MobileShell } from "@/app/_components/core/mobile-shell";
|
||
|
||
import { BrowserHintOverlay } from "./browser-hint-overlay";
|
||
import { ChatArea } from "./chat-area";
|
||
import { ChatHeader } from "./chat-header";
|
||
import { ChatInputBar } from "./chat-input-bar";
|
||
import { PwaInstallOverlay } from "./pwa-install-overlay";
|
||
import { QuotaDialog } from "./quota-dialog";
|
||
import styles from "./chat-screen.module.css";
|
||
|
||
export function ChatScreen() {
|
||
const state = useChatState();
|
||
const dispatch = useChatDispatch();
|
||
|
||
const [quotaDialogOpen, setQuotaDialogOpen] = useState(false);
|
||
const [quotaExhausted, setQuotaExhausted] = useState(false);
|
||
|
||
// 屏幕生命周期
|
||
useEffect(() => {
|
||
dispatch({ type: "ChatInit" });
|
||
dispatch({ type: "ChatScreenVisible" });
|
||
return () => {
|
||
dispatch({ type: "ChatScreenInvisible" });
|
||
};
|
||
}, [dispatch]);
|
||
|
||
// 配额耗尽触发器
|
||
const prevTriggerRef = useRef(state.quotaExceededTrigger);
|
||
useEffect(() => {
|
||
if (
|
||
state.quotaExceededTrigger !== prevTriggerRef.current &&
|
||
state.quotaExceededTrigger > 0
|
||
) {
|
||
setQuotaExhausted(true);
|
||
setQuotaDialogOpen(true);
|
||
}
|
||
prevTriggerRef.current = state.quotaExceededTrigger;
|
||
}, [state.quotaExceededTrigger]);
|
||
|
||
// 配额警告(剩余 = 5)
|
||
const prevRemainingRef = useRef(state.guestRemainingQuota);
|
||
useEffect(() => {
|
||
if (
|
||
state.isGuest &&
|
||
state.guestRemainingQuota === GuestChatQuota.warningThreshold &&
|
||
prevRemainingRef.current !== state.guestRemainingQuota
|
||
) {
|
||
setQuotaExhausted(false);
|
||
setQuotaDialogOpen(true);
|
||
}
|
||
prevRemainingRef.current = state.guestRemainingQuota;
|
||
}, [state.guestRemainingQuota, state.isGuest]);
|
||
|
||
return (
|
||
<MobileShell>
|
||
<div className={styles.shell}>
|
||
{/* 背景图(与原 Dart MobileLayout 一致) */}
|
||
<div className={styles.background}>
|
||
<Image
|
||
src="/images/chat/bg-chatpage.png"
|
||
alt=""
|
||
fill
|
||
priority
|
||
sizes="(max-width: 500px) 100vw, 500px"
|
||
/>
|
||
</div>
|
||
|
||
<div className={styles.layout}>
|
||
<ChatHeader isGuest={state.isGuest} />
|
||
|
||
<ChatArea
|
||
messages={state.messages}
|
||
isReplyingAI={state.isReplyingAI}
|
||
isGuest={state.isGuest}
|
||
/>
|
||
|
||
<ChatInputBar disabled={state.isReplyingAI} />
|
||
</div>
|
||
|
||
{/* 浏览器提示(应用内浏览器检测) */}
|
||
<BrowserHintOverlay />
|
||
|
||
{/* PWA 安装提示触发器(无可见 UI,3.5s 后弹 dialog) */}
|
||
<PwaInstallOverlay />
|
||
|
||
{/* 配额告警弹窗 */}
|
||
<QuotaDialog
|
||
open={quotaDialogOpen}
|
||
onClose={() => setQuotaDialogOpen(false)}
|
||
isExhausted={quotaExhausted}
|
||
/>
|
||
</div>
|
||
</MobileShell>
|
||
);
|
||
}
|