initial
This commit is contained in:
98
apps/web/src/api/client.ts
Normal file
98
apps/web/src/api/client.ts
Normal 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
71
apps/web/src/api/types.ts
Normal 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 };
|
||||
Reference in New Issue
Block a user