session.ts (5536B)
1 import { create } from "zustand"; 2 import { api } from "../lib/api"; 3 4 export interface Session { 5 id: string; 6 name?: string; 7 status?: string; 8 created_at?: number; 9 topic_id?: string; 10 topic_name?: string; 11 message_count?: number; 12 last_activity?: number; 13 source?: string; 14 archived?: boolean; 15 } 16 17 interface SessionState { 18 sessions: Session[]; 19 activeSessionId: string | null; 20 loading: boolean; 21 error: string | null; 22 _allSessions: Session[]; 23 _topicFilter: string | null; 24 listSessions: () => Promise<void>; 25 createSession: (name?: string, topicId?: string) => Promise<Session | undefined>; 26 updateSession: (id: string, fields: Partial<Session>) => Promise<void>; 27 deleteSession: (id: string) => Promise<void>; 28 archiveSession: (id: string) => Promise<void>; 29 unarchiveSession: (id: string) => Promise<void>; 30 setActiveSession: (id: string | null) => void; 31 incrementMessageCount: (id: string) => void; 32 filterByTopic: (topicId: string | null) => void; 33 clearFilter: () => void; 34 _applyFilter: () => void; 35 } 36 37 export const useSessionStore = create<SessionState>((set, get) => ({ 38 sessions: [], 39 activeSessionId: null, 40 loading: false, 41 error: null, 42 _allSessions: [], 43 _topicFilter: null, 44 45 listSessions: async () => { 46 set({ loading: true, error: null }); 47 try { 48 const data = await api.get<{ sessions: Session[] }>("/sessions"); 49 const sessions = data.sessions ?? []; 50 set({ 51 sessions, 52 _allSessions: sessions, 53 loading: false, 54 }); 55 get()._applyFilter(); 56 } catch (err) { 57 const message = 58 err instanceof Error ? err.message : "Failed to load sessions"; 59 set({ loading: false, error: message }); 60 } 61 }, 62 63 createSession: async (name?: string, topicId?: string) => { 64 set({ error: null }); 65 try { 66 const body: Record<string, unknown> = {}; 67 if (name) body.name = name; 68 if (topicId) body.topic_id = topicId; 69 const created = await api.post<Session>("/sessions", body); 70 set((state) => ({ 71 _allSessions: [...state._allSessions, created], 72 })); 73 get()._applyFilter(); 74 return created; 75 } catch (err) { 76 const message = 77 err instanceof Error ? err.message : "Failed to create session"; 78 set({ error: message }); 79 return undefined; 80 } 81 }, 82 83 updateSession: async (id, fields) => { 84 set({ error: null }); 85 try { 86 const updated = await api.patch<Session>(`/sessions/${id}`, fields); 87 set((state) => ({ 88 sessions: state.sessions.map((s) => 89 s.id === id ? { ...s, ...updated } : s, 90 ), 91 _allSessions: state._allSessions.map((s) => 92 s.id === id ? { ...s, ...updated } : s, 93 ), 94 })); 95 } catch (err) { 96 const message = 97 err instanceof Error ? err.message : "Failed to update session"; 98 set({ error: message }); 99 } 100 }, 101 102 deleteSession: async (id) => { 103 set({ error: null }); 104 try { 105 await api.delete(`/sessions/${id}`); 106 set((state) => ({ 107 sessions: state.sessions.filter((s) => s.id !== id), 108 _allSessions: state._allSessions.filter((s) => s.id !== id), 109 activeSessionId: 110 state.activeSessionId === id ? null : state.activeSessionId, 111 })); 112 } catch (err) { 113 const message = 114 err instanceof Error ? err.message : "Failed to delete session"; 115 set({ error: message }); 116 } 117 }, 118 119 archiveSession: async (id) => { 120 set({ error: null }); 121 try { 122 await api.post(`/sessions/${id}/archive`); 123 set((state) => ({ 124 sessions: state.sessions.filter((s) => s.id !== id), 125 _allSessions: state._allSessions.map((s) => 126 s.id === id ? { ...s, archived: true } : s 127 ), 128 activeSessionId: 129 state.activeSessionId === id ? null : state.activeSessionId, 130 })); 131 } catch (err) { 132 const message = 133 err instanceof Error ? err.message : "Failed to archive session"; 134 set({ error: message }); 135 } 136 }, 137 138 unarchiveSession: async (id) => { 139 set({ error: null }); 140 try { 141 await api.delete(`/sessions/${id}/archive`); 142 set((state) => ({ 143 _allSessions: state._allSessions.map((s) => 144 s.id === id ? { ...s, archived: false } : s 145 ), 146 })); 147 get()._applyFilter(); 148 } catch (err) { 149 const message = 150 err instanceof Error ? err.message : "Failed to unarchive session"; 151 set({ error: message }); 152 } 153 }, 154 155 setActiveSession: (id) => set({ activeSessionId: id }), 156 157 incrementMessageCount: (id) => { 158 const bump = (s: Session) => s.id === id ? { ...s, message_count: (s.message_count ?? 0) + 1, last_activity: Date.now() / 1000 } : s; 159 set((state) => ({ 160 _allSessions: state._allSessions.map(bump), 161 })); 162 get()._applyFilter(); 163 }, 164 165 filterByTopic: (topicId) => { 166 set({ _topicFilter: topicId }); 167 get()._applyFilter(); 168 }, 169 170 clearFilter: () => { 171 set({ _topicFilter: null }); 172 get()._applyFilter(); 173 }, 174 175 _applyFilter: () => { 176 const { _allSessions, _topicFilter } = get(); 177 const sorted = [..._allSessions] 178 .filter((s) => (s.message_count ?? 0) > 0 && !s.archived) 179 .sort((a, b) => { 180 const ta = a.last_activity ?? a.created_at ?? 0; 181 const tb = b.last_activity ?? b.created_at ?? 0; 182 return tb - ta; // newest first 183 }); 184 if (!_topicFilter) { 185 set({ sessions: sorted }); 186 return; 187 } 188 set({ sessions: sorted.filter((s) => s.topic_id === _topicFilter) }); 189 }, 190 }));