diff --git a/src/servers/api/saved-sessions/saved-sessions.ts b/src/servers/api/saved-sessions/saved-sessions.ts index f6f1fddf..e5c20048 100644 --- a/src/servers/api/saved-sessions/saved-sessions.ts +++ b/src/servers/api/saved-sessions/saved-sessions.ts @@ -93,20 +93,30 @@ savedSessionsRouter.put('/:id', async (ctx) => { const homeDir = getHomeDirForRole(user.email, user.role); + // Load existing saved session to get previous messages + const existing = await getSavedSession(id, user.id); + if (!existing) { + return ctx.json({ error: 'Not found' }, 404); + } + let meta; - let messages: Message[]; + let newMessages: Message[]; try { const loaded = await storage.loadSession(homeDir, sessionId); meta = loaded.meta; - messages = loaded.messages; + newMessages = loaded.messages; } catch { return ctx.json({ error: 'Session not found on disk' }, 404); } + // Merge: old DB messages (from before resume) + new disk messages (from this session) + const oldMessages = (existing.rawMessages ?? []) as Message[]; + const mergedMessages = [...oldMessages, ...newMessages]; + const updated = await updateSavedSessionMessages( id, user.id, - messages, + mergedMessages, meta.cost as unknown as Record, ); diff --git a/src/workspaces/officerdev/src/apps/Chat/ChatHeader.tsx b/src/workspaces/officerdev/src/apps/Chat/ChatHeader.tsx index 9930c922..ad68fe91 100644 --- a/src/workspaces/officerdev/src/apps/Chat/ChatHeader.tsx +++ b/src/workspaces/officerdev/src/apps/Chat/ChatHeader.tsx @@ -1,10 +1,9 @@ import { useState } from 'react'; -import { MessageSquare, History, Plus, Save, Check, Trash2, Loader2 } from 'lucide-react'; +import { MessageSquare, History, Plus, Save, Trash2, Loader2 } from 'lucide-react'; import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover'; import { useSavedSessions, messagesToTranscript, type RawMessage } from 'state/useSavedSessions'; import { usePanelChannel } from 'hooks/usePanelChannel'; import type { ChatMessage } from './types'; -import type { AutoSaveMapping } from '../ChatHistory/ChatDetailPanel'; export type ChatSessionSelection = { sessionId: string | null; @@ -18,12 +17,12 @@ export const ChatHeader = () => { const { sessions, saveSession, deleteSavedSession, resumeSession } = useSavedSessions(); const [, setSelection] = usePanelChannel('chat:panel-session', null); const [activeSessionId] = usePanelChannel('chat:active-session', null); - const [autoSave, setAutoSave] = usePanelChannel('chat:auto-save', null); + const [savedId, setSavedId] = usePanelChannel('chat:saved-id', null); const [open, setOpen] = useState(false); const [isSaving, setIsSaving] = useState(false); const [isResuming, setIsResuming] = useState(null); - const isSaved = !!autoSave && autoSave.sessionId === activeSessionId; + const isSaved = savedId != null; const selectSession = (selection: ChatSessionSelection) => { setSelection(selection); @@ -35,7 +34,7 @@ export const ChatHeader = () => { setIsSaving(true); try { const result = await saveSession(activeSessionId); - setAutoSave({ sessionId: activeSessionId, savedId: result.id }); + setSavedId(result.id); } finally { setIsSaving(false); } @@ -62,6 +61,7 @@ export const ChatHeader = () => { return { role: 'assistant' as const, text: '' }; }); const transcript = messagesToTranscript(rawMessages); + setSavedId(id); selectSession({ sessionId: null, model: result.model ?? model, @@ -84,26 +84,25 @@ export const ChatHeader = () => { <> Chat - {activeSessionId && - (isSaving ? ( - - - Saving… - - ) : isSaved ? ( - - - - ) : ( - - ))} + {isSaving ? ( + + + Saving… + + ) : isSaved ? ( + + + + ) : activeSessionId ? ( + + ) : null} - ))} + {isSaving ? ( + + + Saving… + + ) : isSaved ? ( + + + Saved + + ) : sessionId ? ( + + ) : null}
{sessionTitle ?? 'New chat'} @@ -84,36 +81,40 @@ function DetailBar({ sessionTitle, isConnected, isGenerating, sessionId, isSaved type NewChatProps = { resumeSummary?: string; initialMessages?: ChatMessage[]; + savedId?: number; }; -function NewChat({ resumeSummary, initialMessages }: NewChatProps) { +function NewChat({ resumeSummary, initialMessages, savedId: initialSavedId }: NewChatProps) { const location = useLocation(); const locationState = location.state as ChatLocationState; const { user } = useAuth(); const isSuperAdmin = user?.role === 'Super Admin'; const { saveSession, updateSessionMessages } = useSavedSessions(); const [isSaving, setIsSaving] = useState(false); - const [autoSave, setAutoSave] = usePanelChannel(AUTO_SAVE_CHANNEL, null); - const autoSaveRef = useRef(autoSave); - autoSaveRef.current = autoSave; + const [savedId, setSavedId] = usePanelChannel(SAVED_ID_CHANNEL, initialSavedId ?? null); + const savedIdRef = useRef(savedId); + savedIdRef.current = savedId; + const chatSessionRef = useRef(null); const onTurnComplete = useCallback(() => { - const mapping = autoSaveRef.current; - if (mapping) { - updateSessionMessages(mapping.savedId, mapping.sessionId).catch(() => {}); + const id = savedIdRef.current; + const sid = chatSessionRef.current; + if (id != null && sid) { + updateSessionMessages(id, sid).catch(() => {}); } }, [updateSessionMessages]); const chat = usePiChat(undefined, locationState?.model, { resumeSummary, initialMessages, onTurnComplete }); + chatSessionRef.current = chat.sessionId; - const isSaved = !!autoSave && autoSave.sessionId === chat.sessionId; + const isSaved = savedId != null; const handleSave = async () => { if (!chat.sessionId || isSaving) return; setIsSaving(true); try { const result = await saveSession(chat.sessionId); - setAutoSave({ sessionId: chat.sessionId, savedId: result.id }); + setSavedId(result.id); } finally { setIsSaving(false); } @@ -167,7 +168,14 @@ export const ChatDetailPanel = () => { ); } + const savedId = selected.id.startsWith('saved:') ? Number(selected.id.replace('saved:', '')) : undefined; + return ( - + ); };