mirror of
https://github.com/lukaszraczylo/claude-mnemonic.git
synced 2026-06-09 23:59:40 +00:00
Dashboard and sdk processor improvements.
This commit is contained in:
+96
-17
@@ -1,39 +1,118 @@
|
||||
import type { Observation, UserPrompt, SessionSummary, Stats, FeedItem, ObservationFeedItem, PromptFeedItem, SummaryFeedItem } from '@/types'
|
||||
|
||||
const API_BASE = '/api'
|
||||
const DEFAULT_TIMEOUT = 10000 // 10 seconds
|
||||
const MAX_RETRIES = 3
|
||||
const RETRY_DELAY = 1000 // 1 second base delay
|
||||
|
||||
async function fetchJson<T>(url: string): Promise<T> {
|
||||
const response = await fetch(url)
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||||
interface FetchOptions {
|
||||
timeout?: number
|
||||
signal?: AbortSignal
|
||||
retries?: number
|
||||
}
|
||||
|
||||
async function fetchJson<T>(url: string, options: FetchOptions = {}): Promise<T> {
|
||||
const { timeout = DEFAULT_TIMEOUT, signal } = options
|
||||
|
||||
// Create timeout abort controller
|
||||
const timeoutController = new AbortController()
|
||||
const timeoutId = setTimeout(() => timeoutController.abort(), timeout)
|
||||
|
||||
// Combine signals if both provided
|
||||
const combinedSignal = signal
|
||||
? combineAbortSignals(signal, timeoutController.signal)
|
||||
: timeoutController.signal
|
||||
|
||||
try {
|
||||
const response = await fetch(url, { signal: combinedSignal })
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||||
}
|
||||
return response.json()
|
||||
} catch (err) {
|
||||
// Re-throw abort errors (user cancellation)
|
||||
if (err instanceof Error && err.name === 'AbortError') {
|
||||
// Check if it was a timeout vs user abort
|
||||
if (signal?.aborted) {
|
||||
throw err // User cancelled
|
||||
}
|
||||
throw new Error('Request timed out')
|
||||
}
|
||||
throw err
|
||||
} finally {
|
||||
clearTimeout(timeoutId)
|
||||
}
|
||||
return response.json()
|
||||
}
|
||||
|
||||
export async function fetchObservations(limit: number = 100, project?: string): Promise<Observation[]> {
|
||||
const params = new URLSearchParams({ limit: String(limit) })
|
||||
if (project) params.append('project', project)
|
||||
return fetchJson<Observation[]>(`${API_BASE}/observations?${params}`)
|
||||
// Helper to combine multiple abort signals
|
||||
function combineAbortSignals(...signals: AbortSignal[]): AbortSignal {
|
||||
const controller = new AbortController()
|
||||
for (const signal of signals) {
|
||||
if (signal.aborted) {
|
||||
controller.abort()
|
||||
break
|
||||
}
|
||||
signal.addEventListener('abort', () => controller.abort(), { once: true })
|
||||
}
|
||||
return controller.signal
|
||||
}
|
||||
|
||||
export async function fetchPrompts(limit: number = 100, project?: string): Promise<UserPrompt[]> {
|
||||
const params = new URLSearchParams({ limit: String(limit) })
|
||||
if (project) params.append('project', project)
|
||||
return fetchJson<UserPrompt[]>(`${API_BASE}/prompts?${params}`)
|
||||
// Fetch with retry logic
|
||||
async function fetchWithRetry<T>(url: string, options: FetchOptions = {}): Promise<T> {
|
||||
const { retries = MAX_RETRIES, ...fetchOptions } = options
|
||||
let lastError: Error | null = null
|
||||
|
||||
for (let attempt = 0; attempt < retries; attempt++) {
|
||||
try {
|
||||
return await fetchJson<T>(url, fetchOptions)
|
||||
} catch (err) {
|
||||
lastError = err instanceof Error ? err : new Error(String(err))
|
||||
|
||||
// Don't retry on user abort
|
||||
if (lastError.name === 'AbortError') {
|
||||
throw lastError
|
||||
}
|
||||
|
||||
// Don't retry on client errors (4xx)
|
||||
if (lastError.message.includes('HTTP 4')) {
|
||||
throw lastError
|
||||
}
|
||||
|
||||
// Wait before retry (exponential backoff)
|
||||
if (attempt < retries - 1) {
|
||||
const delay = Math.min(RETRY_DELAY * Math.pow(2, attempt), 5000)
|
||||
await new Promise(r => setTimeout(r, delay))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError!
|
||||
}
|
||||
|
||||
export async function fetchSummaries(limit: number = 50, project?: string): Promise<SessionSummary[]> {
|
||||
export async function fetchObservations(limit: number = 100, project?: string, signal?: AbortSignal): Promise<Observation[]> {
|
||||
const params = new URLSearchParams({ limit: String(limit) })
|
||||
if (project) params.append('project', project)
|
||||
return fetchJson<SessionSummary[]>(`${API_BASE}/summaries?${params}`)
|
||||
return fetchWithRetry<Observation[]>(`${API_BASE}/observations?${params}`, { signal })
|
||||
}
|
||||
|
||||
export async function fetchPrompts(limit: number = 100, project?: string, signal?: AbortSignal): Promise<UserPrompt[]> {
|
||||
const params = new URLSearchParams({ limit: String(limit) })
|
||||
if (project) params.append('project', project)
|
||||
return fetchWithRetry<UserPrompt[]>(`${API_BASE}/prompts?${params}`, { signal })
|
||||
}
|
||||
|
||||
export async function fetchSummaries(limit: number = 50, project?: string, signal?: AbortSignal): Promise<SessionSummary[]> {
|
||||
const params = new URLSearchParams({ limit: String(limit) })
|
||||
if (project) params.append('project', project)
|
||||
return fetchWithRetry<SessionSummary[]>(`${API_BASE}/summaries?${params}`, { signal })
|
||||
}
|
||||
|
||||
export async function fetchStats(): Promise<Stats> {
|
||||
return fetchJson<Stats>(`${API_BASE}/stats`)
|
||||
return fetchWithRetry<Stats>(`${API_BASE}/stats`)
|
||||
}
|
||||
|
||||
export async function fetchProjects(): Promise<string[]> {
|
||||
return fetchJson<string[]>(`${API_BASE}/projects`)
|
||||
return fetchWithRetry<string[]>(`${API_BASE}/projects`)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user