/** * AI Assistant plugin API client. * * All requests use the shared `apiClient` (`baseURL: '/api/v1'`) and target the * AI Assistant plugin routes under `/ai/...`. */ import { apiDelete, apiGet, apiPost, apiPut } from './client'; // ─── Types ─── export interface AIProvider { id: string; name: string; provider_type: string; api_key: string; base_url: string; is_active: boolean; is_default: boolean; config: Record; created_at?: string; updated_at?: string; } export interface AIModel { id: string; provider_id: string; model_id: string; display_name: string; context_window: number; supports_tools: boolean; supports_streaming: boolean; is_active: boolean; config: Record; } export interface AIPreset { id: string; name: string; model_id: string; provider_id: string | null; temperature: number; max_tokens: number; top_p: number; system_prompt: string; config: Record; is_active: boolean; } export interface AIAgent { id: string; name: string; description: string; system_prompt: string; preset_id: string | null; tool_ids: string[]; is_default: boolean; is_active: boolean; config: Record; created_at?: string; updated_at?: string; } export interface ChatFolder { id: string; name: string; parent_id: string | null; user_id: string; created_at?: string; } export interface ChatSession { id: string; user_id: string; agent_id: string | null; title: string; is_pinned: boolean; is_sidebar: boolean; folder_id: string | null; created_at?: string; updated_at?: string; } export interface ChatMessage { id: string; session_id: string; role: string; content: string; tool_calls?: Record[]; tool_results?: Record[]; tokens: number; model_used: string; created_at?: string; } export interface ChatAttachment { id: string; message_id: string | null; session_id: string; filename: string; mime_type: string; size_bytes: number; } export interface AITool { name: string; description: string; parameters: Record; plugin_name: string; required_permission: string | null; category: string; } // ─── Providers ─── export const fetchProviders = () => apiGet('/ai/providers'); export const createProvider = (data: Partial) => apiPost('/ai/providers', data); export const updateProvider = (id: string, data: Partial) => apiPut(`/ai/providers/${id}`, data); export const deleteProvider = (id: string) => apiDelete(`/ai/providers/${id}`); // ─── Models ─── export const fetchModels = (providerId?: string) => apiGet('/ai/models', { params: providerId ? { provider_id: providerId } : {} }); export const createModel = (data: Partial) => apiPost('/ai/models', data); export const updateModel = (id: string, data: Partial) => apiPut(`/ai/models/${id}`, data); export const deleteModel = (id: string) => apiDelete(`/ai/models/${id}`); // ─── Presets ─── export const fetchPresets = () => apiGet('/ai/presets'); export const createPreset = (data: Partial) => apiPost('/ai/presets', data); export const updatePreset = (id: string, data: Partial) => apiPut(`/ai/presets/${id}`, data); export const deletePreset = (id: string) => apiDelete(`/ai/presets/${id}`); // ─── Agents ─── export const fetchAgents = () => apiGet('/ai/agents'); export const createAgent = (data: Partial) => apiPost('/ai/agents', data); export const updateAgent = (id: string, data: Partial) => apiPut(`/ai/agents/${id}`, data); export const deleteAgent = (id: string) => apiDelete(`/ai/agents/${id}`); // ─── Tools ─── export const fetchTools = async () => { const res = await apiGet('/ai/tools'); return Array.isArray(res) ? res : res.items ?? []; }; // ─── Folders ─── export const fetchFolders = () => apiGet('/ai/folders'); export const createFolder = (data: { name: string; parent_id?: string }) => apiPost('/ai/folders', data); export const updateFolder = (id: string, data: Partial) => apiPut(`/ai/folders/${id}`, data); export const deleteFolder = (id: string) => apiDelete(`/ai/folders/${id}`); // ─── Sessions ─── export const fetchSessions = (isSidebar?: boolean) => apiGet('/ai/sessions', { params: isSidebar !== undefined ? { is_sidebar: isSidebar } : {} }); export const createSession = (data: { title?: string; agent_id?: string; is_sidebar?: boolean; folder_id?: string }) => apiPost('/ai/sessions', data); export const updateSession = (id: string, data: Partial) => apiPut(`/ai/sessions/${id}`, data); export const deleteSession = (id: string) => apiDelete(`/ai/sessions/${id}`); // ─── Messages ─── export const fetchMessages = (conversationId: string) => apiGet<{ role: string; content: string }[]>(`/ai/conversations/${conversationId}/messages`); // ─── Attachments ─── export const fetchAttachments = (sessionId: string) => apiGet(`/ai/sessions/${sessionId}/attachments`); export async function uploadAttachment(sessionId: string, file: File): Promise { const formData = new FormData(); formData.append('file', file); const csrfToken = sessionStorage.getItem('leocrm_csrf_token'); const response = await fetch(`/api/v1/ai/sessions/${sessionId}/attachments`, { method: 'POST', headers: { ...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {}), }, credentials: 'include', body: formData, }); if (!response.ok) throw new Error(`Upload failed: ${response.status}`); return response.json(); } export function getAttachmentDownloadUrl(attachmentId: string): string { return `/api/v1/ai/attachments/${attachmentId}/download`; } // ─── Streaming Chat ─── export interface StreamEvent { type: 'token' | 'tool_calls' | 'tool_result' | 'done' | 'error'; content?: string; tool?: string; result?: string; tools?: string[]; } export async function* streamChat( conversationId: string, content: string, agentId?: string ): AsyncGenerator { const csrfToken = sessionStorage.getItem('leocrm_csrf_token'); const response = await fetch(`/api/v1/ai/conversations/${conversationId}/stream`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {}), }, credentials: 'include', body: JSON.stringify({ content, agent_id: agentId }), }); if (!response.ok) { throw new Error(`Stream failed: ${response.status}`); } const reader = response.body?.getReader(); if (!reader) throw new Error('No response body'); const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() || ''; for (const line of lines) { if (line.startsWith('data: ')) { const data = line.slice(6).trim(); if (data === '[DONE]') return; try { yield JSON.parse(data); } catch { // skip invalid JSON } } } } }