const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ''; async function request(path: string, options?: RequestInit): Promise { const url = API_BASE ? `${API_BASE}${path}` : `/api${path}`; const res = await fetch(url, { credentials: 'include', headers: { 'Content-Type': 'application/json' }, ...options, }); if (!res.ok) throw new Error(`API error: ${res.status}`); return res.json(); } export const api = { getSettings: () => request('/settings'), updateSettings: (s: Partial) => request('/settings', { method: 'POST', body: JSON.stringify(s) }), getConversations: () => request('/conversations'), newConversation: (title = 'New conversation') => request('/conversations', { method: 'POST', body: JSON.stringify({ title }) }), getMessages: (id: string) => request(`/conversations/${id}/messages`), renameConversation: (id: string, title: string) => request(`/conversations/${id}`, { method: 'PATCH', body: JSON.stringify({ title }) }), deleteConversation: (id: string) => request<{ deleted: string }>(`/conversations/${id}`, { method: 'DELETE' }), clearAllConversations: () => request<{ cleared: boolean }>('/conversations', { method: 'DELETE' }), sendMessage: (message: string, conversation_id: string) => request('/chat', { method: 'POST', body: JSON.stringify({ message, conversation_id }) }), getMemory: () => request<{ content: string }>('/memory'), updateMemory: (content: string) => request<{ saved: boolean }>('/memory', { method: 'POST', body: JSON.stringify({ content }) }), getStatus: () => request('/status'), reindex: () => request<{ started: boolean }>('/reindex', { method: 'POST' }), getDreamerStatus: () => request('/dreamer/status'), runDreamer: (mode: string, task?: string) => request<{ started: boolean; mode: string }>('/dreamer/run', { method: 'POST', body: JSON.stringify({ mode, task }), }), transcribe: async (audio: Blob): Promise<{ text: string }> => { const form = new FormData(); form.append('audio', audio, 'recording.webm'); const url = '/api/transcribe'; const res = await fetch(url, { method: 'POST', credentials: 'include', body: form }); if (!res.ok) throw new Error(`Transcribe error: ${res.status}`); return res.json(); }, capture: async (data: CapturePayload): Promise<{ saved: boolean }> => { const form = new FormData(); if (data.audio) form.append('audio', data.audio, 'capture.webm'); if (data.image) form.append('image', data.image); if (data.text) form.append('text', data.text); if (data.project) form.append('project', data.project); const url = '/api/capture'; const res = await fetch(url, { method: 'POST', credentials: 'include', body: form }); if (!res.ok) throw new Error(`Capture error: ${res.status}`); return res.json(); }, }; export const auth = { login: (password: string) => request<{ ok: boolean }>('/auth/login', { method: 'POST', body: JSON.stringify({ password }) }), logout: () => request<{ ok: boolean }>('/auth/logout', { method: 'POST' }), check: () => request<{ authenticated: boolean }>('/auth/check'), }; export interface Settings { theme: 'light' | 'dark'; font_size: 'small' | 'medium' | 'large'; web_search: boolean; show_sources: boolean; } export interface Conversation { id: string; title: string; created_at: string; updated_at: string; message_count: number; } export interface Message { role: 'user' | 'assistant'; content: string; sources: string[]; timestamp: string; } export interface ChatResponse { response: string; sources: string[]; conversation_id: string; } export interface DreamerStatus { last_dream: string; last_mode: string; last_file: string; } export interface Status { aaron_ai: string; watcher: string; chunk_count: number; file_count: number; last_indexed: string; conversation_count: number; model: string; nextcloud_path: string; } export interface CapturePayload { audio?: Blob; image?: File; text?: string; project?: string; }