|
@@ -0,0 +1,74 @@
|
|
|
|
|
+import type {
|
|
|
|
|
+ LoginResponse, CollectionMeta, ApiKeyPublic, ApiKeyCreated,
|
|
|
|
|
+ SearchResult, ProjectStats, GlobalStats, FindResult,
|
|
|
|
|
+} from '@/types'
|
|
|
|
|
+import { ApiError } from '@/types'
|
|
|
|
|
+
|
|
|
|
|
+async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
|
|
|
|
|
+ const res = await fetch(path, {
|
|
|
|
|
+ ...options,
|
|
|
|
|
+ credentials: 'include',
|
|
|
|
|
+ headers: { 'Content-Type': 'application/json', ...(options.headers || {}) },
|
|
|
|
|
+ })
|
|
|
|
|
+ if (res.status === 401 && !path.endsWith('/ui/login')) {
|
|
|
|
|
+ const { useAuthStore } = await import('@/stores/authStore')
|
|
|
|
|
+ useAuthStore.getState().reset()
|
|
|
|
|
+ if (location.pathname !== '/login') location.assign('/login')
|
|
|
|
|
+ throw new ApiError(401, 'unauthorized')
|
|
|
|
|
+ }
|
|
|
|
|
+ const text = await res.text()
|
|
|
|
|
+ const body = text ? JSON.parse(text) : {}
|
|
|
|
|
+ if (!res.ok) throw new ApiError(res.status, body?.error?.message || res.statusText)
|
|
|
|
|
+ return body as T
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const enc = encodeURIComponent
|
|
|
|
|
+const P = (project: string) => `/api/v1/projects/${enc(project)}`
|
|
|
|
|
+
|
|
|
|
|
+export const api = {
|
|
|
|
|
+ login: (key: string) => request<LoginResponse>('/ui/login', { method: 'POST', body: JSON.stringify({ key }) }),
|
|
|
|
|
+ logout: () => request<{ ok: boolean }>('/ui/logout', { method: 'POST' }),
|
|
|
|
|
+
|
|
|
|
|
+ // projects
|
|
|
|
|
+ listProjects: () => request<{ projects: string[] }>('/api/v1/projects'),
|
|
|
|
|
+ createProject: (name: string) => request<{ name: string }>('/api/v1/projects', { method: 'POST', body: JSON.stringify({ name }) }),
|
|
|
|
|
+ deleteProject: (name: string) => request<{ deleted: string }>(`/api/v1/projects/${enc(name)}`, { method: 'DELETE' }),
|
|
|
|
|
+
|
|
|
|
|
+ // keys (admin)
|
|
|
|
|
+ listKeys: () => request<{ keys: ApiKeyPublic[] }>('/api/v1/keys'),
|
|
|
|
|
+ createKey: (label: string, projects: string[], admin: boolean) =>
|
|
|
|
|
+ request<ApiKeyCreated>('/api/v1/keys', { method: 'POST', body: JSON.stringify({ label, projects, admin }) }),
|
|
|
|
|
+ updateKey: (key: string, patch: Partial<{ label: string; projects: string[]; admin: boolean }>) =>
|
|
|
|
|
+ request<{ updated: boolean }>(`/api/v1/keys/${enc(key)}`, { method: 'PATCH', body: JSON.stringify(patch) }),
|
|
|
|
|
+ deleteKey: (key: string) => request<{ deleted: boolean }>(`/api/v1/keys/${enc(key)}`, { method: 'DELETE' }),
|
|
|
|
|
+
|
|
|
|
|
+ // collections
|
|
|
|
|
+ listCollections: (project: string) => request<{ collections: CollectionMeta[] }>(`${P(project)}/collections`),
|
|
|
|
|
+ getCollection: (project: string, name: string) => request<CollectionMeta>(`${P(project)}/collections/${enc(name)}`),
|
|
|
|
|
+ createCollection: (project: string, body: { name: string; kind: 'json' | 'vector'; vector_dimension?: number; embedding_model?: string }) =>
|
|
|
|
|
+ request<CollectionMeta>(`${P(project)}/collections`, { method: 'POST', body: JSON.stringify(body) }),
|
|
|
|
|
+ deleteCollection: (project: string, name: string) =>
|
|
|
|
|
+ request<{ deleted: string }>(`${P(project)}/collections/${enc(name)}`, { method: 'DELETE' }),
|
|
|
|
|
+
|
|
|
|
|
+ // documents
|
|
|
|
|
+ findDocuments: (project: string, coll: string, qs = '') =>
|
|
|
|
|
+ request<FindResult>(`${P(project)}/collections/${enc(coll)}/documents${qs}`),
|
|
|
|
|
+ getDocument: (project: string, coll: string, id: string) =>
|
|
|
|
|
+ request<Record<string, unknown>>(`${P(project)}/collections/${enc(coll)}/documents/${enc(id)}`),
|
|
|
|
|
+ insertDocument: (project: string, coll: string, data: unknown) =>
|
|
|
|
|
+ request<{ id: string }>(`${P(project)}/collections/${enc(coll)}/documents`, { method: 'POST', body: JSON.stringify({ data }) }),
|
|
|
|
|
+ putDocument: (project: string, coll: string, id: string, data: unknown) =>
|
|
|
|
|
+ request<{ id: string }>(`${P(project)}/collections/${enc(coll)}/documents/${enc(id)}`, { method: 'PUT', body: JSON.stringify(data) }),
|
|
|
|
|
+ deleteDocument: (project: string, coll: string, id: string) =>
|
|
|
|
|
+ request<{ deleted: string }>(`${P(project)}/collections/${enc(coll)}/documents/${enc(id)}`, { method: 'DELETE' }),
|
|
|
|
|
+
|
|
|
|
|
+ // vectors / rag
|
|
|
|
|
+ storeVector: (project: string, coll: string, body: { id?: string; text?: string; vector?: number[]; metadata?: Record<string, unknown> }) =>
|
|
|
|
|
+ request<{ id: string }>(`${P(project)}/collections/${enc(coll)}/vectors`, { method: 'POST', body: JSON.stringify(body) }),
|
|
|
|
|
+ search: (project: string, coll: string, body: { query_text?: string; query_vector?: number[]; top_k?: number; min_score?: number }) =>
|
|
|
|
|
+ request<{ results: SearchResult[] }>(`${P(project)}/collections/${enc(coll)}/search`, { method: 'POST', body: JSON.stringify(body) }),
|
|
|
|
|
+
|
|
|
|
|
+ // stats
|
|
|
|
|
+ projectStats: (project: string) => request<ProjectStats>(`${P(project)}/stats`),
|
|
|
|
|
+ globalStats: () => request<GlobalStats>('/api/v1/stats'),
|
|
|
|
|
+}
|