import { lazy, Suspense, useCallback, useEffect, useMemo, useState } from "react"; import { BugOutlined, CheckCircleFilled, CloseOutlined, HomeOutlined, LockOutlined, LoadingOutlined, LogoutOutlined, MailOutlined, MobileOutlined, SafetyOutlined, UserOutlined, } from "@ant-design/icons"; import { LocalAvatar } from "./components/LocalAvatar"; import { Topbar } from "./components/Topbar"; import ErrorBoundary from "./components/ErrorBoundary"; import ToastContainer from "./components/toast/ToastContainer"; import { toast } from "./components/toast/toastStore"; import { flushPendingGenerationRecords } from "./api/generationRecordClient"; import { keyServerClient } from "./api/keyServerClient"; import { setUserMaxConcurrency } from "./api/generationConcurrency"; import { SERVER_SESSION_EXPIRED_EVENT, SERVER_SESSION_REPLACED_EVENT, clearAllUserStorage, type ServerSessionReplacedDetail, } from "./api/serverConnection"; import { initNotificationPermission } from "./utils/generationNotifier"; import { reportError } from "./utils/errorReporting"; import { loadDarkGreenTheme } from "./styles/loadDarkGreenTheme"; import { useAppStore, useSessionStore } from "./stores"; import type { WebUserSession } from "./types"; import "./styles/ecommerce-standalone.css"; const EcommercePage = lazy(() => import("./features/ecommerce/EcommercePage")); type AuthMode = "login" | "register"; type AuthMethod = "account" | "email" | "phone"; type WorkspaceChromeState = { isToolPage: boolean; }; interface LocalProfilePageProps { session: WebUserSession; balance: number; imageCount: number; videoCount: number; onBack: () => void; onBugFeedback: () => void; onLogout: () => void; } function LocalProfilePage({ session, balance, imageCount, videoCount, onBack, onBugFeedback, onLogout }: LocalProfilePageProps) { const displayName = session.user.displayName || session.user.username || "用户"; const workCount = Math.max(imageCount + videoCount, 0); const projectCount = 0; const assetCount = 0; return (
{["我的作品", "我的项目", "我的资产", "社区发布"].map((item, index) => ( ))}
代表作 后续将展示接口返回的真实作品
{workCount} 项
暂无代表作数据 作品接口接入后,这里会显示你的真实生成内容。
); } function App() { const session = useSessionStore((s) => s.session); const setSession = useSessionStore((s) => s.setSession); const clearSessionState = useSessionStore((s) => s.clearSession); const usage = useAppStore((s) => s.usage); const setUsage = useAppStore((s) => s.setUsage); const [authOpen, setAuthOpen] = useState(false); const [authMode, setAuthMode] = useState("login"); const [authMethod, setAuthMethod] = useState("account"); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [betaCode, setBetaCode] = useState(""); const [authSubmitting, setAuthSubmitting] = useState(false); const [authError, setAuthError] = useState(null); const [sessionNotice, setSessionNotice] = useState(null); const [profileMenuOpen, setProfileMenuOpen] = useState(false); const [currentPage, setCurrentPage] = useState<"workspace" | "profile">("workspace"); const [workspaceChrome, setWorkspaceChrome] = useState({ isToolPage: false, }); useEffect(() => { void loadDarkGreenTheme(); document.documentElement.dataset.theme = "dark"; document.documentElement.dataset.uiTheme = "dark-green"; document.documentElement.style.colorScheme = "dark"; document.body.classList.add("ecommerce-standalone-body"); return () => { document.body.classList.remove("ecommerce-standalone-body"); }; }, []); useEffect(() => { const splash = document.getElementById("app-boot-splash"); if (splash) { splash.style.opacity = "0"; const timer = window.setTimeout(() => splash.remove(), 350); return () => window.clearTimeout(timer); } }, []); useEffect(() => { initNotificationPermission(); }, []); useEffect(() => { if (!session) return; void flushPendingGenerationRecords(); }, [session]); useEffect(() => { const handleUnhandled = (event: ErrorEvent) => { reportError(event.error || new Error(event.message), "unhandled"); }; const handleRejection = (event: PromiseRejectionEvent) => { reportError(event.reason instanceof Error ? event.reason : new Error(String(event.reason)), "rejection"); }; window.addEventListener("error", handleUnhandled); window.addEventListener("unhandledrejection", handleRejection); return () => { window.removeEventListener("error", handleUnhandled); window.removeEventListener("unhandledrejection", handleRejection); }; }, []); const refreshUsage = useCallback(async () => { try { setUsage(await keyServerClient.getUsageSummary()); } catch { // Usage is helpful but should not block the standalone generator. } }, [setUsage]); const completeAuth = useCallback( async (nextSession: WebUserSession) => { setSession(nextSession); setUserMaxConcurrency(nextSession.user.maxConcurrency); setAuthOpen(false); setAuthError(null); await refreshUsage(); if (nextSession.user.email && !nextSession.user.emailVerified) { toast.info("邮箱尚未验证,部分功能可能受限"); } }, [refreshUsage, setSession], ); const clearAuthenticatedState = useCallback(() => { clearAllUserStorage(); clearSessionState(); setUserMaxConcurrency(null); setUsage({ balanceCents: 0, imageUsed: 0, videoUsed: 0, textUsed: 0, source: "preview", }); }, [clearSessionState, setUsage]); useEffect(() => { let cancelled = false; const loadSession = async () => { try { const nextSession = await keyServerClient.getCurrentSession(); if (cancelled) return; setSession(nextSession); setUserMaxConcurrency(nextSession?.user.maxConcurrency); if (nextSession) await refreshUsage(); } catch { if (!cancelled) clearAuthenticatedState(); } }; void loadSession(); return () => { cancelled = true; }; }, [clearAuthenticatedState, refreshUsage, setSession]); useEffect(() => { const handleSessionInvalid = (event: Event) => { const detail = (event as CustomEvent).detail; clearAuthenticatedState(); setSessionNotice(detail?.message || "登录状态已失效,请重新登录。"); setAuthOpen(true); }; window.addEventListener(SERVER_SESSION_REPLACED_EVENT, handleSessionInvalid); window.addEventListener(SERVER_SESSION_EXPIRED_EVENT, handleSessionInvalid); return () => { window.removeEventListener(SERVER_SESSION_REPLACED_EVENT, handleSessionInvalid); window.removeEventListener(SERVER_SESSION_EXPIRED_EVENT, handleSessionInvalid); }; }, [clearAuthenticatedState]); const openAuth = useCallback((mode: AuthMode = "login") => { setAuthMode(mode); setAuthError(null); setSessionNotice(null); setAuthOpen(true); }, []); const handleSubmitAuth = async () => { if (!username.trim() || !password) { setAuthError(authMethod === "email" ? "请输入邮箱和密码" : authMethod === "phone" ? "请输入手机号和验证码/密码" : "请输入用户名和密码"); return; } setAuthSubmitting(true); setAuthError(null); try { const nextSession = authMode === "login" ? await keyServerClient.login({ username, password }) : await keyServerClient.register({ username, password, betaCode }); await completeAuth(nextSession); } catch (error) { setAuthError(error instanceof Error ? error.message : "登录失败,请稍后重试。"); } finally { setAuthSubmitting(false); } }; const handleLogout = () => { setProfileMenuOpen(false); setCurrentPage("workspace"); clearAuthenticatedState(); toast.info("已退出登录"); }; const balance = Math.max(usage.balanceCents, 0) / 100; const handleOpenProfile = () => { setProfileMenuOpen(false); setCurrentPage("profile"); }; const handleOpenWorkspace = () => { setProfileMenuOpen(false); setCurrentPage("workspace"); }; const handleBugFeedback = () => { setProfileMenuOpen(false); toast.info("Bug 反馈入口已保留,后续可接入反馈页面。"); }; return (
{session ? ( ) : null} {/* 工作台常驻挂载,仅用 hidden 切换。切到个人中心时不卸载, 生成任务、进度动画、已上传图片等本地状态全部保留,切回即继续。 */}
{authOpen ? (

{authMode === "login" ? "欢迎回来" : "创建账号"}

{authMode === "login" ? "登录后继续你的 AI 创作之旅" : "注册即可免费体验全部功能"}

{sessionNotice ?

{sessionNotice}

: null}
{authMode === "register" ? ( ) : null} {authMethod === "phone" ? ( <> ) : ( <> )} {authError ?

{authError}

: null} {authMode === "login" && authMethod !== "phone" ? : null}

{authMode === "login" ? "登录" : "注册"}即表示同意 《用户协议》《隐私政策》

其他方式
) : null}
); } export default App;