import { writable, derived } from 'svelte/store'; import type { Session, SessionMetadata } from '$lib/types/api'; import type { SessionListItem } from '$lib/types/client'; import { apiClient } from '$lib/services/api'; import { STORAGE_KEY_ACTIVE_SESSION } from '$lib/utils/constants'; import { getItem, setItem, removeItem } from '$lib/services/storage'; /** * Session store managing user's chat sessions * Handles session list, active session, and CRUD operations */ interface SessionState { sessions: SessionListItem[]; // List of user sessions (metadata only) activeSession: Session | null; // Currently loaded session with full messages loading: boolean; error: string | null; } function createInitialState(): SessionState { return { sessions: [], activeSession: null, loading: false, error: null }; } // Create the session store function createSessionStore() { const { subscribe, set, update } = writable(createInitialState()); return { subscribe, /** * Load all sessions for current user */ async loadSessions() { update((state) => ({ ...state, loading: true, error: null })); try { const sessions = await apiClient.listSessions(); // Convert to SessionListItem format (convert Unix timestamps to ISO strings) const sessionList: SessionListItem[] = sessions.map((s) => ({ id: s.id, title: s.title, last_interaction: new Date(s.last_interaction * 1000).toISOString(), message_count: s.message_count, is_reference: s.is_reference, is_active: false })); update((state) => ({ ...state, sessions: sessionList, loading: false })); } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to load sessions'; update((state) => ({ ...state, loading: false, error: errorMessage })); } }, /** * Create a new session */ async createSession(title: string): Promise { update((state) => ({ ...state, loading: true, error: null })); try { const response = await apiClient.createSession(title); const newSessionId = response.session_id; // Add to session list const newSession: SessionListItem = { id: newSessionId, title, last_interaction: new Date().toISOString(), message_count: 0, is_reference: false, is_active: false }; update((state) => ({ ...state, sessions: [newSession, ...state.sessions], loading: false })); return newSessionId; } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to create session'; update((state) => ({ ...state, loading: false, error: errorMessage })); return null; } }, /** * Load a specific session with messages */ async loadSession(sessionId: string) { update((state) => ({ ...state, loading: true, error: null })); try { const session = await apiClient.getSession(sessionId); // Mark as active in session list update((state) => ({ ...state, sessions: state.sessions.map((s) => ({ ...s, is_active: s.id === sessionId })), activeSession: session, loading: false })); // Persist active session ID setItem(STORAGE_KEY_ACTIVE_SESSION, sessionId); } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to load session'; update((state) => ({ ...state, loading: false, error: errorMessage })); } }, /** * Delete a session */ async deleteSession(sessionId: string): Promise { update((state) => ({ ...state, loading: true, error: null })); try { await apiClient.deleteSession(sessionId); update((state) => { const newState = { ...state, sessions: state.sessions.filter((s) => s.id !== sessionId), loading: false }; // Clear active session if it was deleted if (state.activeSession?.id === sessionId) { newState.activeSession = null; removeItem(STORAGE_KEY_ACTIVE_SESSION); } return newState; }); return true; } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to delete session'; update((state) => ({ ...state, loading: false, error: errorMessage })); return false; } }, /** * Update session reference flag */ async updateSessionReference(sessionId: string, isReference: boolean): Promise { update((state) => ({ ...state, loading: true, error: null })); try { await apiClient.updateSession(sessionId, isReference); update((state) => ({ ...state, sessions: state.sessions.map((s) => s.id === sessionId ? { ...s, is_reference: isReference } : s ), activeSession: state.activeSession?.id === sessionId ? { ...state.activeSession, is_reference: isReference } : state.activeSession, loading: false })); return true; } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to update session'; update((state) => ({ ...state, loading: false, error: errorMessage })); return false; } }, /** * Clear active session */ clearActive() { update((state) => ({ ...state, activeSession: null })); removeItem(STORAGE_KEY_ACTIVE_SESSION); }, /** * Send a message to the active session */ async sendMessage( sessionId: string, content: string, mode: string ): Promise { update((state) => ({ ...state, loading: true, error: null })); try { const response = await apiClient.sendMessage(sessionId, { mode: mode as any, message: content, save_to_memory: mode === 'memorize' }); // Update active session with new message update((state) => { if (state.activeSession?.id === sessionId) { return { ...state, activeSession: { ...state.activeSession, messages: [ ...state.activeSession.messages, { mode: mode as any, content, created_at: new Date().toISOString() } ], last_interaction: new Date().toISOString() }, sessions: state.sessions.map((s) => s.id === sessionId ? { ...s, message_count: s.message_count + 1, last_interaction: new Date().toISOString() } : s ), loading: false }; } return { ...state, loading: false }; }); return true; } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to send message'; update((state) => ({ ...state, loading: false, error: errorMessage })); return false; } }, /** * Clear error */ clearError() { update((state) => ({ ...state, error: null })); } }; } export const sessionStore = createSessionStore(); // Derived stores export const activeSessions = derived(sessionStore, ($session) => $session.sessions); export const activeSession = derived(sessionStore, ($session) => $session.activeSession); export const sessionCount = derived(sessionStore, ($session) => $session.sessions.length); export const isLoading = derived(sessionStore, ($session) => $session.loading); export const sessionError = derived(sessionStore, ($session) => $session.error);