saved sessions: replace transient history with persistent DB storage and auto-save
- Add saved_sessions table and CRUD endpoints (save, list, resume, update, delete) - Save is instant (no LLM summarization), stores exact conversation with tool calls - Resume loads full message history into chat UI, sends transcript to agent on first message - Auto-save updates DB after every agent response once a session is saved - Delete old filesystem-based session/group management (sessions router, useChatSessions, useChatGroups) - Clean up ChatHeader, SessionList, ChatDetailPanel for saved sessions flow Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,16 +1,15 @@
|
||||
import { Link } from 'react-router';
|
||||
import { MessageSquare, Trash2 } from 'lucide-react';
|
||||
import { Widget } from 'widgets/Widget';
|
||||
import { useChatSessions } from 'state/useChatSessions';
|
||||
import { useSavedSessions } from 'state/useSavedSessions';
|
||||
|
||||
export const ChatHistory = () => {
|
||||
const { sessions, deleteSession } = useChatSessions();
|
||||
const { sessions, deleteSavedSession } = useSavedSessions();
|
||||
|
||||
return (
|
||||
<Widget title="Chat History">
|
||||
<Widget title="Saved Sessions">
|
||||
<div className="px-4 pb-3 max-h-72 overflow-y-auto">
|
||||
{sessions.length === 0 ? (
|
||||
<p className="text-xs text-duck-dark/40 py-4 text-center">No sessions yet</p>
|
||||
<p className="text-xs text-duck-dark/40 py-4 text-center">No saved sessions yet</p>
|
||||
) : (
|
||||
<ul className="space-y-0.5">
|
||||
{sessions.map((session) => (
|
||||
@@ -18,18 +17,10 @@ export const ChatHistory = () => {
|
||||
key={session.id}
|
||||
className="flex items-center gap-2 px-2 py-1.5 rounded-md hover:bg-duck-dark/5 group"
|
||||
>
|
||||
<Link
|
||||
to={`/chat/${session.id}`}
|
||||
className="flex items-center gap-2 flex-1 min-w-0"
|
||||
>
|
||||
<div className="flex items-center gap-2 flex-1 min-w-0">
|
||||
<MessageSquare className="h-4 w-4 shrink-0 text-duck-teal/60" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<span className="text-sm text-duck-dark dark:text-foreground truncate block">
|
||||
{session.title}
|
||||
<span className="ml-1.5 font-mono text-xs text-duck-dark/25 dark:text-foreground/25">
|
||||
({session.id.slice(0, 8)})
|
||||
</span>
|
||||
</span>
|
||||
<span className="text-sm text-duck-dark dark:text-foreground truncate block">{session.title}</span>
|
||||
<span className="text-xs text-duck-dark/40 dark:text-foreground/40 truncate block">
|
||||
{new Date(session.createdAt).toLocaleDateString(undefined, {
|
||||
month: 'short',
|
||||
@@ -38,15 +29,10 @@ export const ChatHistory = () => {
|
||||
minute: '2-digit',
|
||||
})}
|
||||
</span>
|
||||
{session.model && (
|
||||
<span className="text-xs text-duck-teal/70 dark:text-duck-teal/60 truncate block">
|
||||
{session.model.includes('/') ? session.model.replace('/', ' - ') : session.model}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => deleteSession(session.id)}
|
||||
onClick={() => deleteSavedSession(session.id)}
|
||||
className="shrink-0 p-1 rounded text-duck-dark/20 md:opacity-0 md:group-hover:opacity-100 hover:text-red-500 transition-opacity cursor-pointer"
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
|
||||
@@ -5,7 +5,6 @@ import { WorkspaceView } from 'officerdev';
|
||||
import { useIsMobile } from 'hooks/useIsMobile';
|
||||
import { useDashboardState } from 'state/useDashboardState';
|
||||
import { usePanelChannel } from 'hooks/usePanelChannel';
|
||||
import { useChatSessions } from 'state/useChatSessions';
|
||||
import { defaultLayout } from './defaultLayout';
|
||||
|
||||
export { ChatHistory as ChatHistoryApp } from './Widget';
|
||||
@@ -35,7 +34,6 @@ type SessionListPageProps = {
|
||||
|
||||
export const SessionListPage = ({ isNew }: SessionListPageProps) => {
|
||||
const { sessionId } = useParams<{ sessionId: string }>();
|
||||
const { sessions } = useChatSessions();
|
||||
const [, setSelected] = usePanelChannel<SelectedSession>('chat:selected-session', null);
|
||||
const rawWorkspace = useDashboardState<LayoutNode>('screens/chat', defaultLayout);
|
||||
const isMobile = useIsMobile();
|
||||
@@ -62,8 +60,7 @@ export const SessionListPage = ({ isNew }: SessionListPageProps) => {
|
||||
return;
|
||||
}
|
||||
if (!sessionId) return;
|
||||
const session = sessions.find((s) => s.id === sessionId);
|
||||
setSelected({ id: sessionId, model: session?.model ?? null });
|
||||
setSelected({ id: sessionId });
|
||||
}, [sessionId, isNew]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useChatSessions } from 'state/useChatSessions';
|
||||
import { useSavedSessions } from 'state/useSavedSessions';
|
||||
import { usePlans } from 'state/usePlans';
|
||||
import { useSettings } from 'state/useSettings';
|
||||
import { usePiModels } from 'state/useModels';
|
||||
@@ -6,7 +6,7 @@ import { useAccessPolicy } from 'state/useAccessPolicy';
|
||||
import { useColorModeSync } from './useThemeSync';
|
||||
|
||||
export const useInitialData = () => {
|
||||
const { sessions } = useChatSessions();
|
||||
const { sessions } = useSavedSessions();
|
||||
const { plans } = usePlans();
|
||||
const { settings } = useSettings();
|
||||
usePiModels();
|
||||
|
||||
Reference in New Issue
Block a user