theo-agent-dashboard

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

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 }));