Spaces:
Sleeping
Sleeping
| 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<SessionState>(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<string | null> { | |
| 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<boolean> { | |
| 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<boolean> { | |
| 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<boolean> { | |
| 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); | |