"use client"; import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode, } from "react"; import type { Session, Tag, ImageAsset, ChatMessage } from "./types"; import { loadSessions, saveSessions, createSession as storeCreateSession, updateSession as storeUpdateSession, deleteSession as storeDeleteSession, loadTags, saveTags, addTag as storeAddTag, deleteTag as storeDeleteTag, loadAssets, addAsset as storeAddAsset, updateAsset as storeUpdateAsset, deleteAsset as storeDeleteAsset, toggleFavorite as storeToggleFavorite, generateId, setStoreUserId, } from "./store"; import { useAuth } from "./auth-context"; interface AppContextValue { // 会话 sessions: Session[]; activeSessionId: string | null; activeSession: Session | null; createSession: () => Session; switchSession: (id: string) => void; deleteSession: (id: string) => void; renameSession: (id: string, title: string) => void; updateSessionTags: (id: string, tags: string[]) => void; updateSessionLlmModel: (id: string, llmModel: string) => void; appendMessage: (sessionId: string, message: ChatMessage) => void; updateSessionThumbnail: (sessionId: string, url: string) => void; // 标签 tags: Tag[]; addTag: (name: string, color: string) => Tag; deleteTag: (id: string) => void; selectedTagIds: string[]; setSelectedTagIds: (ids: string[]) => void; // 图片资源 assets: ImageAsset[]; addAsset: (asset: ImageAsset) => void; updateAsset: (id: string, patch: Partial) => void; deleteAssetById: (id: string) => void; toggleFavorite: (id: string) => boolean; // 右侧面板 detailImage: ImageAsset | null; setDetailImage: (asset: ImageAsset | null) => void; // 左栏折叠 sidebarCollapsed: boolean; setSidebarCollapsed: (v: boolean) => void; } const AppContext = createContext(null); export function useApp(): AppContextValue { const ctx = useContext(AppContext); if (!ctx) throw new Error("useApp must be used within AppProvider"); return ctx; } export function AppProvider({ children }: { children: ReactNode }) { const { user, isAuthenticated } = useAuth(); const [sessions, setSessions] = useState([]); const [activeSessionId, setActiveSessionId] = useState(null); const [tags, setTags] = useState([]); const [assets, setAssets] = useState([]); const [selectedTagIds, setSelectedTagIds] = useState([]); const [detailImage, setDetailImage] = useState(null); const [sidebarCollapsed, setSidebarCollapsed] = useState(() => { if (typeof window !== "undefined") return window.innerWidth < 768; return false; }); const [initialized, setInitialized] = useState(false); // 当用户变化时,切换 store 的 userId 并重新加载数据 useEffect(() => { if (!isAuthenticated || !user) { setInitialized(false); return; } setStoreUserId(user.id); setSessions(loadSessions()); setTags(loadTags()); setAssets(loadAssets()); setActiveSessionId(null); setDetailImage(null); setInitialized(true); }, [user?.id, isAuthenticated]); // eslint-disable-line react-hooks/exhaustive-deps // 初始化后,如果没有会话则自动创建一个 useEffect(() => { if (!initialized) return; if (sessions.length === 0) { const s = storeCreateSession(); setSessions([s]); setActiveSessionId(s.id); } else if (!activeSessionId) { setActiveSessionId(sessions[0].id); } }, [initialized, sessions.length, activeSessionId]); const activeSession = useMemo( () => sessions.find((s) => s.id === activeSessionId) ?? null, [sessions, activeSessionId] ); // --- 会话操作 --- const createSession = useCallback(() => { const s = storeCreateSession(); setSessions((prev) => [s, ...prev]); setActiveSessionId(s.id); setDetailImage(null); return s; }, []); const switchSession = useCallback((id: string) => { setActiveSessionId(id); setDetailImage(null); if (typeof window !== "undefined" && window.innerWidth < 768) { setSidebarCollapsed(true); } }, []); const deleteSessionCb = useCallback( (id: string) => { storeDeleteSession(id); setSessions((prev) => { const next = prev.filter((s) => s.id !== id); if (activeSessionId === id) { if (next.length > 0) { setActiveSessionId(next[0].id); } else { const s = storeCreateSession(); next.push(s); setActiveSessionId(s.id); } } return next; }); setAssets((prev) => prev.filter((a) => a.sessionId !== id)); setDetailImage(null); }, [activeSessionId] ); const renameSession = useCallback((id: string, title: string) => { setSessions((prev) => prev.map((s) => { if (s.id !== id) return s; const updated = { ...s, title, updatedAt: Date.now() }; storeUpdateSession(updated); return updated; }) ); }, []); const updateSessionTags = useCallback((id: string, tagIds: string[]) => { setSessions((prev) => prev.map((s) => { if (s.id !== id) return s; const updated = { ...s, tags: tagIds, updatedAt: Date.now() }; storeUpdateSession(updated); return updated; }) ); }, []); const updateSessionLlmModel = useCallback((id: string, llmModel: string) => { setSessions((prev) => prev.map((s) => { if (s.id !== id) return s; const updated = { ...s, llmModel, updatedAt: Date.now() }; storeUpdateSession(updated); return updated; }) ); }, []); const appendMessage = useCallback((sessionId: string, message: ChatMessage) => { setSessions((prev) => prev.map((s) => { if (s.id !== sessionId) return s; const updated = { ...s, messages: [...s.messages, message], updatedAt: Date.now(), }; if (message.role === "user" && s.messages.length === 0) { updated.title = message.content.slice(0, 30) + (message.content.length > 30 ? "…" : ""); } storeUpdateSession(updated); return updated; }) ); }, []); const updateSessionThumbnail = useCallback((sessionId: string, url: string) => { setSessions((prev) => prev.map((s) => { if (s.id !== sessionId) return s; const updated = { ...s, thumbnail: url, updatedAt: Date.now() }; storeUpdateSession(updated); return updated; }) ); }, []); // --- 标签操作 --- const addTag = useCallback((name: string, color: string) => { const t = storeAddTag(name, color); setTags(loadTags()); return t; }, []); const deleteTagCb = useCallback((id: string) => { storeDeleteTag(id); setTags(loadTags()); setSelectedTagIds((prev) => prev.filter((tid) => tid !== id)); }, []); // --- 资源操作 --- const addAssetCb = useCallback((asset: ImageAsset) => { storeAddAsset(asset); setAssets((prev) => [asset, ...prev]); }, []); const updateAssetCb = useCallback((id: string, patch: Partial) => { storeUpdateAsset(id, patch); setAssets((prev) => prev.map((a) => (a.id === id ? { ...a, ...patch } : a))); setDetailImage((prev) => (prev && prev.id === id ? { ...prev, ...patch } : prev)); }, []); const deleteAssetCb = useCallback((id: string) => { storeDeleteAsset(id); setAssets((prev) => prev.filter((a) => a.id !== id)); setDetailImage((prev) => (prev && prev.id === id ? null : prev)); }, []); const toggleFavoriteCb = useCallback((id: string) => { const result = storeToggleFavorite(id); setAssets((prev) => prev.map((a) => (a.id === id ? { ...a, favorited: result } : a)) ); setDetailImage((prev) => prev && prev.id === id ? { ...prev, favorited: result } : prev ); return result; }, []); const value = useMemo( () => ({ sessions, activeSessionId, activeSession, createSession, switchSession, deleteSession: deleteSessionCb, renameSession, updateSessionTags, updateSessionLlmModel, appendMessage, updateSessionThumbnail, tags, addTag, deleteTag: deleteTagCb, selectedTagIds, setSelectedTagIds, assets, addAsset: addAssetCb, updateAsset: updateAssetCb, deleteAssetById: deleteAssetCb, toggleFavorite: toggleFavoriteCb, detailImage, setDetailImage, sidebarCollapsed, setSidebarCollapsed, }), [ sessions, activeSessionId, activeSession, createSession, switchSession, deleteSessionCb, renameSession, updateSessionTags, updateSessionLlmModel, appendMessage, updateSessionThumbnail, tags, addTag, deleteTagCb, selectedTagIds, assets, addAssetCb, updateAssetCb, deleteAssetCb, toggleFavoriteCb, detailImage, sidebarCollapsed, ] ); // 未登录时(如 /login 页面)直接渲染 children,不注入 AppContext // 已登录但尚未初始化完成时显示加载状态,避免子组件调用 useApp() 拿到 null if (!initialized) { if (isAuthenticated) { return (
加载中...
); } return <>{children}; } return {children}; }