Stats history with year filter, folding and XLSX/CSV export; snippets, read-only links, commit suggestions, weekly review, offline copies, server speech-to-text, planning model, job reports; fal.ai health fix

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Sonnet 5.5 committed 2026-11-15 08:20:52 +01:00
1 parent c08c6c7436
commit 388ca96419
31 files changed
+1518 -82

No files matched your search

+28 -4
View File
@@ -1,3 +1,4 @@
import { cacheChat, cacheSessions, cachedChat, cachedSessions, isOffline } from "./offline";
import { useSyncExternalStore } from "react";
import { get } from "./api";
import type {
@@ -29,6 +30,8 @@ export interface State {
/** Newer release (admins). */
update: { version: string; url: string; notes: string } | null;
connected: boolean;
/** Showing a saved copy: the server cannot be reached. */
offline: boolean;
sessions: Session[];
/** Includes subagent sessions loaded on demand. */
sessionById: Record<string, Session>;
@@ -60,6 +63,7 @@ let state: State = {
me: null,
update: null,
connected: false,
offline: false,
sessions: [],
sessionById: {},
messages: {},
@@ -236,7 +240,17 @@ export function dismissError(sessionId: string, id: number) {
}
export async function loadSessions() {
const list = await get<Session[]>("/sessions");
let list: Session[];
try {
list = await get<Session[]>("/sessions");
cacheSessions(list);
if (getState().offline) setState(() => ({ offline: false }));
} catch (e) {
if (!isOffline(e)) throw e;
list = cachedSessions().map((x) => ({ ...x, busy: false }));
setState(() => ({ offline: true }));
if (list.length === 0) throw e;
}
setState((s) => ({
sessions: list,
sessionById: { ...s.sessionById, ...Object.fromEntries(list.map((x) => [x.id, x])) },
@@ -250,9 +264,19 @@ export const PAGE = 200;
type Page = { messages: Message[]; has_older: boolean; has_newer: boolean };
export async function loadSession(id: string) {
const data = await get<{ session: Session; messages: Message[]; busy: boolean; can_write: boolean; queue: QueueItem[]; step?: any; has_older?: boolean; has_newer?: boolean }>(
`/sessions/${id}?limit=${PAGE}`,
);
type Loaded = { session: Session; messages: Message[]; busy: boolean; can_write: boolean; queue: QueueItem[]; step?: any; has_older?: boolean; has_newer?: boolean };
let data: Loaded;
try {
data = await get<Loaded>(`/sessions/${id}?limit=${PAGE}`);
cacheChat(data.session, data.messages);
if (getState().offline) setState(() => ({ offline: false }));
} catch (e) {
const copy = isOffline(e) ? cachedChat(id) : undefined;
if (!copy) throw e;
// No connection: show the saved copy; new messages wait in the outbox.
data = { session: copy.session, messages: copy.messages, busy: false, can_write: true, queue: [], has_older: true };
setState(() => ({ offline: true }));
}
setState((s) => ({
windows: { ...s.windows, [id]: { older: !!data.has_older, newer: !!data.has_newer } },
...upsertSession(s, data.session),