profileShare

rasmusjy / voicetask

Read-only snapshot

No repository description.

main default branch 105 files Expires Sep 13, 2026, 9:06 AM
api.ts 3,567 bytes
1 import type {
2 AnswerRequest,
3 AnswerResponse,
4 BlockersResponse,
5 ConfigStatus,
6 ConfigUpdateRequest,
7 ConfigUpdateResponse,
8 CreateSessionRequest,
9 FsBrowseResponse,
10 FsMkdirResponse,
11 GenerateRequest,
12 GenerateResponse,
13 Session,
14 SessionSummary,
15 } from 'shared/types'
16
17 const BASE = '/api'
18
19 async function readJsonOrThrow<T>(res: Response): Promise<T> {
20 if (!res.ok) {
21 const body = (await res.json().catch(() => null)) as { error?: string } | null
22 throw new Error(body?.error ?? `request failed with status ${res.status}`)
23 }
24 return res.json() as Promise<T>
25 }
26
27 async function requestJson<T>(url: string, init?: RequestInit): Promise<T> {
28 const res = await fetch(url, {
29 ...init,
30 headers: { 'Content-Type': 'application/json', ...init?.headers },
31 })
32 return readJsonOrThrow<T>(res)
33 }
34
35 export function createSession(req: CreateSessionRequest): Promise<Session> {
36 return requestJson<Session>(`${BASE}/sessions`, { method: 'POST', body: JSON.stringify(req) })
37 }
38
39 export function listSessions(): Promise<SessionSummary[]> {
40 return requestJson<SessionSummary[]>(`${BASE}/sessions`)
41 }
42
43 export function getSession(id: string): Promise<Session> {
44 return requestJson<Session>(`${BASE}/sessions/${id}`)
45 }
46
47 export async function deleteSession(id: string): Promise<void> {
48 const res = await fetch(`${BASE}/sessions/${id}`, { method: 'DELETE' })
49 if (!res.ok) {
50 const body = (await res.json().catch(() => null)) as { error?: string } | null
51 throw new Error(body?.error ?? `request failed with status ${res.status}`)
52 }
53 }
54
55 export function submitAnswer(id: string, req: AnswerRequest): Promise<AnswerResponse> {
56 return requestJson<AnswerResponse>(`${BASE}/sessions/${id}/answer`, {
57 method: 'POST',
58 body: JSON.stringify(req),
59 })
60 }
61
62 export async function uploadAudio(id: string, audio: Blob): Promise<AnswerResponse> {
63 const form = new FormData()
64 form.append('audio', audio, 'clip.webm')
65 const res = await fetch(`${BASE}/sessions/${id}/audio`, { method: 'POST', body: form })
66 return readJsonOrThrow<AnswerResponse>(res)
67 }
68
69 export function generateSpecPack(id: string, req: GenerateRequest = {}): Promise<GenerateResponse> {
70 return requestJson<GenerateResponse>(`${BASE}/sessions/${id}/generate`, {
71 method: 'POST',
72 body: JSON.stringify(req),
73 })
74 }
75
76 export async function hasSpecPack(id: string): Promise<boolean> {
77 const res = await fetch(`${BASE}/sessions/${id}/export/spec-pack.zip`, { method: 'HEAD' })
78 if (res.ok) return true
79 if (res.status === 404) return false
80 throw new Error(`Could not check the build brief, status ${res.status}`)
81 }
82
83 export function importBlockers(id: string): Promise<BlockersResponse> {
84 return requestJson<BlockersResponse>(`${BASE}/sessions/${id}/blockers`, {
85 method: 'POST',
86 body: JSON.stringify({}),
87 })
88 }
89
90 export function getConfig(): Promise<ConfigStatus> {
91 return requestJson<ConfigStatus>(`${BASE}/config`)
92 }
93
94 export function saveConfig(req: ConfigUpdateRequest): Promise<ConfigUpdateResponse> {
95 return requestJson<ConfigUpdateResponse>(`${BASE}/config`, {
96 method: 'POST',
97 body: JSON.stringify(req),
98 })
99 }
100
101 export function browseFolder(path?: string): Promise<FsBrowseResponse> {
102 const query = path ? `?path=${encodeURIComponent(path)}` : ''
103 return requestJson<FsBrowseResponse>(`${BASE}/fs/browse${query}`)
104 }
105
106 export function createFolder(path: string, name: string): Promise<FsMkdirResponse> {
107 return requestJson<FsMkdirResponse>(`${BASE}/fs/mkdir`, {
108 method: 'POST',
109 body: JSON.stringify({ path, name }),
110 })
111 }
112