import { lazy, Suspense, useCallback, useEffect, useMemo, useState } from "react"; import { BugOutlined, CheckCircleFilled, CloseOutlined, HomeOutlined, IdcardOutlined, LockOutlined, LoadingOutlined, LoginOutlined, LogoutOutlined, MailOutlined, MobileOutlined, PictureOutlined, SafetyOutlined, UserOutlined, VideoCameraOutlined, WalletOutlined, } from "@ant-design/icons"; 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"; interface LocalProfilePageProps { session: WebUserSession; balance: number; imageCount: number; videoCount: number; onBack: () => void; onBugFeedback: () => void; onLogout: () => void; } function LocalAvatar({ session, size = "md" }: { session: WebUserSession; size?: "sm" | "md" | "lg" }) { const displayName = session.user.displayName || session.user.username || "用户"; const label = displayName.trim().slice(0, 1).toUpperCase() || "用"; const avatarUrl = session.user.avatarUrl; return ( {avatarUrl ? : {label}} ); } 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) => ( {item} ))} 代表作 后续将展示接口返回的真实作品 {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"); 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 displayName = session?.user.displayName || session?.user.username || "用户"; const actualWorkCount = Math.max(usage.imageUsed + usage.videoUsed, 0); const shownWorkCount = actualWorkCount; const avatarMenuStats = useMemo( () => [ { icon: , label: "UID", value: session?.user.id ?? "-" }, { icon: , label: "积分", value: `${balance.toFixed(2)} 积分` }, { icon: , label: "图片", value: usage.imageUsed }, { icon: , label: "视频", value: usage.videoUsed }, { icon: , label: "作品", value: shownWorkCount }, ], [balance, session?.user.id, shownWorkCount, usage.imageUsed, usage.videoUsed], ); const handleOpenProfile = () => { setProfileMenuOpen(false); setCurrentPage("profile"); }; const handleOpenWorkspace = () => { setProfileMenuOpen(false); setCurrentPage("workspace"); }; const handleBugFeedback = () => { setProfileMenuOpen(false); toast.info("Bug 反馈入口已保留,后续可接入反馈页面。"); }; return ( OmniAI 电商智能体 {session ? ( {(Math.max(usage.balanceCents, 0) / 100).toFixed(2)} 积分 setProfileMenuOpen((open) => !open)} aria-haspopup="dialog" aria-expanded={profileMenuOpen} > {displayName} {profileMenuOpen ? ( <> setProfileMenuOpen(false)} /> {displayName} {session.user.username} {avatarMenuStats.map((item) => ( {item.icon}{item.label} {item.value} ))} 个人中心 Bug 反馈 退出 > ) : null} ) : ( openAuth("login")}> 登录 / 注册 )} {session ? ( ) : null} {/* 工作台常驻挂载,仅用 hidden 切换。切到个人中心时不卸载, 生成任务、进度动画、已上传图片等本地状态全部保留,切回即继续。 */} 加载中... } > undefined} onOpenProject={() => undefined} onDeleteProject={() => undefined} onImportWorkflow={() => undefined} onCreateTask={() => undefined} onRequireLogin={() => openAuth("login")} initialTemplate={null} onInitialTemplateConsumed={() => undefined} /> {authOpen ? ( setAuthOpen(false)} /> setAuthOpen(false)} > {authMode === "login" ? "欢迎回来" : "创建账号"} {authMode === "login" ? "登录后继续你的 AI 创作之旅" : "注册即可免费体验全部功能"} {sessionNotice ? {sessionNotice} : null} { setAuthMode("login"); setAuthError(null); }}>登录 { setAuthMode("register"); setAuthError(null); }}>注册 setAuthMethod("account")}>账号密码 setAuthMethod("email")}>邮箱 setAuthMethod("phone")}>手机 {authMode === "register" ? ( 企业邀请码 / 内测码 setBetaCode(event.target.value)} placeholder="请输入企业邀请码或内测码" /> ) : null} {authMethod === "phone" ? ( <> 手机号 +86 setUsername(event.target.value)} autoComplete="tel" placeholder="输入手机号" /> 验证码 setPassword(event.target.value)} inputMode="numeric" placeholder="输入 6 位验证码" /> 获取验证码 > ) : ( <> {authMethod === "email" ? : }{authMode === "register" ? authMethod === "email" ? "邮箱" : "设置用户名" : authMethod === "email" ? "邮箱" : "用户名"} setUsername(event.target.value)} autoComplete="username" inputMode={authMethod === "email" ? "email" : "text"} placeholder={authMethod === "email" ? "输入邮箱地址" : authMode === "register" ? "输入用户名或邮箱" : "输入用户名或邮箱"} /> {authMode === "register" ? "设置密码" : "密码"} setPassword(event.target.value)} type="password" autoComplete={authMode === "login" ? "current-password" : "new-password"} placeholder={authMode === "register" ? "至少 6 位" : "输入密码"} /> > )} {authError ? {authError} : null} {authMode === "login" && authMethod !== "phone" ? 忘记密码? : null} {authSubmitting ? : null} {authMode === "login" ? "登录" : "注册"} {authMode === "login" ? "登录" : "注册"}即表示同意 《用户协议》 和 《隐私政策》 其他方式 setAuthMethod("phone")} aria-label="手机登录"> ) : null} ); } export default App;
{authMode === "login" ? "登录后继续你的 AI 创作之旅" : "注册即可免费体验全部功能"}
{sessionNotice}
{authError}
{authMode === "login" ? "登录" : "注册"}即表示同意 《用户协议》 和 《隐私政策》