diff --git a/PWA/public/1.jpg b/PWA/public/1.jpg new file mode 100644 index 0000000..d743d60 Binary files /dev/null and b/PWA/public/1.jpg differ diff --git a/PWA/src/App.tsx b/PWA/src/App.tsx index 0217f4f..697e34e 100644 --- a/PWA/src/App.tsx +++ b/PWA/src/App.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useState, useEffect, useRef } from 'react' import ChatArea from './components/ChatArea' import InputArea from './components/InputArea' @@ -8,16 +8,77 @@ export type Message = { text: string } -const API = 'http://localhost:3001/api/chat' +const API = '/api/chat' +const HISTORY = '/api/chat-history' +const STORAGE_KEY = 'shenyan-chat-messages' +const MAX_MSGS = 200 -let nextId = 0 +function loadLocal(): Message[] { + try { + const raw = localStorage.getItem(STORAGE_KEY) + return raw ? JSON.parse(raw) : [] + } catch { return [] } +} + +function saveLocal(msgs: Message[]) { + const trimmed = msgs.length > MAX_MSGS ? msgs.slice(-MAX_MSGS) : msgs + localStorage.setItem(STORAGE_KEY, JSON.stringify(trimmed)) +} + +async function loadRemote(): Promise { + try { + const res = await fetch(HISTORY) + if (!res.ok) return null + const data = await res.json() + if (Array.isArray(data) && data.length > 0) return data + return null + } catch { return null } +} + +async function saveRemote(msgs: Message[]) { + try { + const trimmed = msgs.length > MAX_MSGS ? msgs.slice(-MAX_MSGS) : msgs + await fetch(HISTORY, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(trimmed), + }) + } catch { /* silent */ } +} export default function App() { - const [messages, setMessages] = useState([]) + const [messages, setMessages] = useState(loadLocal) const [loading, setLoading] = useState(false) + const [synced, setSynced] = useState(false) + const nextId = useRef(0) + + // On mount, try server first; if server has messages, use those; otherwise keep local + useEffect(() => { + (async () => { + const remote = await loadRemote() + if (remote && remote.length > 0) { + setMessages(remote) + nextId.current = Math.max(...remote.map(m => m.id)) + 1 + } else { + const local = loadLocal() + if (local.length > 0) { + nextId.current = Math.max(...local.map(m => m.id)) + 1 + saveRemote(local) // first-time: push local to server + } + } + setSynced(true) + })() + }, []) + + // Save to both storages whenever messages change (after initial sync) + useEffect(() => { + if (!synced || messages.length === 0) return + saveLocal(messages) + saveRemote(messages) + }, [messages, synced]) const handleSend = async (text: string) => { - const userMsg: Message = { id: nextId++, role: 'user', text } + const userMsg: Message = { id: nextId.current++, role: 'user', text } setMessages((prev) => [...prev, userMsg]) setLoading(true) @@ -38,17 +99,25 @@ export default function App() { const data = await res.json() const reply = data.choices?.[0]?.message?.content || '(no response)' - setMessages((prev) => [...prev, { id: nextId++, role: 'shenyan', text: reply }]) + setMessages((prev) => [...prev, { id: nextId.current++, role: 'shenyan', text: reply }]) } catch { - setMessages((prev) => [...prev, { id: nextId++, role: 'shenyan', text: '连接失败' }]) + setMessages((prev) => [...prev, { id: nextId.current++, role: 'shenyan', text: '连接失败' }]) } finally { setLoading(false) } } return ( -
-
+
+
沈晏
diff --git a/PWA/src/components/ChatArea.tsx b/PWA/src/components/ChatArea.tsx index cff4dcf..a420dc4 100644 --- a/PWA/src/components/ChatArea.tsx +++ b/PWA/src/components/ChatArea.tsx @@ -9,18 +9,23 @@ export default function ChatArea({ messages }: { messages: Message[] }) { }, [messages]) return ( -
-
+
+
{messages.map((m) => (
{m.text} diff --git a/PWA/src/components/InputArea.tsx b/PWA/src/components/InputArea.tsx index f458931..aac5424 100644 --- a/PWA/src/components/InputArea.tsx +++ b/PWA/src/components/InputArea.tsx @@ -11,7 +11,11 @@ export default function InputArea({ onSend, disabled }: { onSend: (text: string) } return ( -
+
+