From df9755c6b307f9bb3d85e2ef42b79bbe45d5018a Mon Sep 17 00:00:00 2001 From: Lemon Date: Mon, 30 Mar 2026 00:11:06 -0700 Subject: [PATCH] Persist AI Chat session to localStorage Messages survive page navigation and browser refresh. The session is loaded from localStorage on mount and saved on every change. The clear button removes the stored session. Timestamps are serialized as ISO strings and NostrEvent objects serialize as plain JSON. --- src/pages/AIChatPage.tsx | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/pages/AIChatPage.tsx b/src/pages/AIChatPage.tsx index 14996795..3efc886b 100644 --- a/src/pages/AIChatPage.tsx +++ b/src/pages/AIChatPage.tsx @@ -413,6 +413,35 @@ Keep spell names short and descriptive (2-4 words). When you create a spell, bri Be concise and friendly. When you use a tool, briefly describe what you created.`, }; +// ─── Chat Persistence ─── + +const CHAT_STORAGE_KEY = 'ditto:ai-chat-messages'; + +/** Serialized shape stored in localStorage (Date → ISO string). */ +interface StoredMessage extends Omit { + timestamp: string; +} + +function loadMessages(): DisplayMessage[] { + try { + const raw = localStorage.getItem(CHAT_STORAGE_KEY); + if (!raw) return []; + const stored: StoredMessage[] = JSON.parse(raw); + return stored.map((m) => ({ ...m, timestamp: new Date(m.timestamp) })); + } catch { + return []; + } +} + +function saveMessages(messages: DisplayMessage[]): void { + try { + const stored: StoredMessage[] = messages.map((m) => ({ ...m, timestamp: m.timestamp.toISOString() })); + localStorage.setItem(CHAT_STORAGE_KEY, JSON.stringify(stored)); + } catch { + // Storage full or unavailable — silently ignore + } +} + // ─── Page Component ─── export function AIChatPage() { @@ -421,7 +450,7 @@ export function AIChatPage() { const { sendChatMessage, getAvailableModels, isLoading: apiLoading, error: apiError, clearError } = useShakespeare(); const { executeToolCall } = useToolExecutor(); - const [messages, setMessages] = useState([]); + const [messages, setMessages] = useState(loadMessages); const [input, setInput] = useState(''); const [isStreaming, setIsStreaming] = useState(false); const [models, setModels] = useState([]); @@ -439,6 +468,11 @@ export function AIChatPage() { useLayoutOptions({ noOverscroll: true }); + // Persist messages to localStorage + useEffect(() => { + saveMessages(messages); + }, [messages]); + // Scroll to bottom on new messages const scrollToBottom = useCallback(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); @@ -619,6 +653,7 @@ export function AIChatPage() { // Clear conversation const handleClear = useCallback(() => { setMessages([]); + localStorage.removeItem(CHAT_STORAGE_KEY); clearError(); }, [clearError]);