diff --git a/src/components/chat/GlobalChatbox.tsx b/src/components/chat/GlobalChatbox.tsx index 0ac1d9d..35aebc0 100644 --- a/src/components/chat/GlobalChatbox.tsx +++ b/src/components/chat/GlobalChatbox.tsx @@ -25,6 +25,7 @@ import SendRounded from "@mui/icons-material/SendRounded"; import StopRounded from "@mui/icons-material/StopRounded"; import AutoAwesome from "@mui/icons-material/AutoAwesome"; // Sparkle icon for AI import PersonRounded from "@mui/icons-material/PersonRounded"; +import ErrorOutlineRounded from "@mui/icons-material/ErrorOutlineRounded"; // Logic import { streamCopilotChat } from "@/lib/chatStream"; @@ -34,6 +35,7 @@ type Message = { id: string; role: "user" | "assistant"; content: string; + isError?: boolean; }; type Props = { @@ -43,6 +45,12 @@ type Props = { // Utils const createId = () => `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; +const CHAT_STORAGE_KEY = "tjwater_copilot_chat_state_v1"; + +type PersistedChatState = { + messages: Message[]; + conversationId?: string; +}; // --- Components --- @@ -114,6 +122,7 @@ export const GlobalChatbox: React.FC = ({ open, onClose }) => { const abortRef = useRef(null); const bottomRef = useRef(null); const inputRef = useRef(null); + const hasHydratedRef = useRef(false); const theme = useTheme(); const canSend = useMemo(() => input.trim().length > 0 && !isStreaming, [input, isStreaming]); @@ -131,6 +140,40 @@ export const GlobalChatbox: React.FC = ({ open, onClose }) => { return () => window.clearTimeout(timer); }, [open]); + useEffect(() => { + try { + const storedRaw = window.localStorage.getItem(CHAT_STORAGE_KEY); + if (!storedRaw) { + hasHydratedRef.current = true; + return; + } + const parsed = JSON.parse(storedRaw) as PersistedChatState; + if (!Array.isArray(parsed.messages)) { + console.error("[GlobalChatbox] Invalid persisted messages format."); + window.localStorage.removeItem(CHAT_STORAGE_KEY); + hasHydratedRef.current = true; + return; + } + setMessages(parsed.messages); + setConversationId(parsed.conversationId); + } catch (error) { + console.error("[GlobalChatbox] Failed to read persisted chat state:", error); + window.localStorage.removeItem(CHAT_STORAGE_KEY); + } finally { + hasHydratedRef.current = true; + } + }, []); + + useEffect(() => { + if (!hasHydratedRef.current) return; + const state: PersistedChatState = { messages, conversationId }; + try { + window.localStorage.setItem(CHAT_STORAGE_KEY, JSON.stringify(state)); + } catch (error) { + console.error("[GlobalChatbox] Failed to persist chat state:", error); + } + }, [messages, conversationId]); + const handleSend = async () => { const prompt = input.trim(); if (!prompt || isStreaming) return; @@ -159,7 +202,9 @@ export const GlobalChatbox: React.FC = ({ open, onClose }) => { if (!conversationId && event.conversationId) setConversationId(event.conversationId); setMessages((prev) => prev.map((m) => - m.id === assistantId ? { ...m, content: m.content + event.content } : m + m.id === assistantId + ? { ...m, content: m.content + event.content, isError: false } + : m ) ); } else if (event.type === "done") { @@ -169,7 +214,11 @@ export const GlobalChatbox: React.FC = ({ open, onClose }) => { setMessages((prev) => prev.map((m) => m.id === assistantId - ? { ...m, content: m.content || `错误:${event.message}` } + ? { + ...m, + content: m.content || `⚠️ **错误:** ${event.message}`, + isError: true, + } : m ) ); @@ -180,7 +229,11 @@ export const GlobalChatbox: React.FC = ({ open, onClose }) => { } catch (error) { if (abortRef.current?.signal.aborted) return; setMessages((prev) => - prev.map((m) => (m.id === assistantId ? { ...m, content: `错误:${String(error)}` } : m)) + prev.map((m) => + m.id === assistantId + ? { ...m, content: `⚠️ **错误:** ${String(error)}`, isError: true } + : m + ) ); setIsStreaming(false); } finally { @@ -330,6 +383,7 @@ export const GlobalChatbox: React.FC = ({ open, onClose }) => { {messages.map((message) => { const isUser = message.role === "user"; + const isErrorMessage = Boolean(message.isError); return ( = ({ open, onClose }) => { }} > {!isUser && ( - - + + {isErrorMessage ? ( + + ) : ( + + )} )} - {isUser ? ( - {message.content} - ) : ( - {message.content || "..."} - )} + {message.content || "..."} );