This commit is contained in:
2026-05-14 19:24:50 +00:00
parent af0628867f
commit a7f218e182
63 changed files with 10539 additions and 0 deletions

View File

@@ -0,0 +1,98 @@
import type {
Project,
AvailableProject,
Session,
Message,
ModelInfo,
} from './types';
export class ApiError extends Error {
constructor(
public status: number,
public body: unknown
) {
super(typeof body === 'object' && body && 'error' in body ? String((body as { error: unknown }).error) : `HTTP ${status}`);
}
}
async function request<T>(
path: string,
init: RequestInit = {}
): Promise<T> {
const res = await fetch(path, {
...init,
headers: {
'Content-Type': 'application/json',
...(init.headers ?? {}),
},
});
if (res.status === 204) return undefined as T;
const text = await res.text();
const data = text ? JSON.parse(text) : undefined;
if (!res.ok) throw new ApiError(res.status, data);
return data as T;
}
export const api = {
health: () => request<{ status: string; db: boolean }>('/api/health'),
projects: {
list: () => request<Project[]>('/api/projects'),
available: () => request<AvailableProject[]>('/api/projects/available'),
add: (body: { path: string; name?: string }) =>
request<Project>('/api/projects', {
method: 'POST',
body: JSON.stringify(body),
}),
remove: (id: string) =>
request<void>(`/api/projects/${id}`, { method: 'DELETE' }),
},
sessions: {
listForProject: (projectId: string) =>
request<Session[]>(`/api/projects/${projectId}/sessions`),
create: (
projectId: string,
body: { name?: string; model?: string; system_prompt?: string }
) =>
request<Session>(`/api/projects/${projectId}/sessions`, {
method: 'POST',
body: JSON.stringify(body),
}),
get: (id: string) => request<Session>(`/api/sessions/${id}`),
update: (
id: string,
body: Partial<Pick<Session, 'name' | 'model' | 'system_prompt'>>
) =>
request<Session>(`/api/sessions/${id}`, {
method: 'PATCH',
body: JSON.stringify(body),
}),
remove: (id: string) =>
request<void>(`/api/sessions/${id}`, { method: 'DELETE' }),
},
messages: {
list: (sessionId: string) =>
request<Message[]>(`/api/sessions/${sessionId}/messages`),
send: (sessionId: string, content: string) =>
request<{ user_message_id: string; assistant_message_id: string }>(
`/api/sessions/${sessionId}/messages`,
{
method: 'POST',
body: JSON.stringify({ content }),
}
),
},
models: () => request<ModelInfo[]>('/api/models'),
settings: {
get: () => request<Record<string, unknown>>('/api/settings'),
patch: (body: Record<string, unknown>) =>
request<Record<string, unknown>>('/api/settings', {
method: 'PATCH',
body: JSON.stringify(body),
}),
},
};

71
apps/web/src/api/types.ts Normal file
View File

@@ -0,0 +1,71 @@
export interface Project {
id: string;
name: string;
path: string;
added_at: string;
last_session_id: string | null;
}
export interface AvailableProject {
path: string;
name: string;
}
export interface Session {
id: string;
project_id: string;
name: string;
model: string;
system_prompt: string;
created_at: string;
updated_at: string;
}
export type MessageRole = 'user' | 'assistant' | 'tool';
export type MessageStatus = 'streaming' | 'complete' | 'failed';
export interface ToolCall {
id: string;
name: string;
args: Record<string, unknown>;
}
export interface ToolResult {
tool_call_id: string;
output: unknown;
truncated: boolean;
error?: string;
}
export interface Message {
id: string;
session_id: string;
role: MessageRole;
content: string;
tool_calls: ToolCall[] | null;
tool_results: ToolResult | null;
status: MessageStatus;
last_seq: number;
created_at: string;
}
export interface ModelInfo {
id: string;
[key: string]: unknown;
}
export type WsFrame =
| { type: 'snapshot'; messages: Message[] }
| { type: 'message_started'; message_id: string; role: MessageRole }
| { type: 'delta'; message_id: string; content: string }
| { type: 'tool_call'; message_id: string; tool_call: ToolCall }
| {
type: 'tool_result';
tool_message_id: string;
tool_call_id: string;
output: unknown;
truncated: boolean;
error?: string;
}
| { type: 'message_complete'; message_id: string }
| { type: 'error'; message_id?: string; error: string };