AI Assistant: frontend - chat app, sidebar, settings with 4 tabs
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* 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<string, unknown>;
|
||||
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<string, unknown>;
|
||||
}
|
||||
|
||||
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<string, unknown>;
|
||||
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<string, unknown>;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface ChatSession {
|
||||
id: string;
|
||||
user_id: string;
|
||||
agent_id: string | null;
|
||||
title: string;
|
||||
is_pinned: boolean;
|
||||
is_sidebar: boolean;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface ChatMessage {
|
||||
id: string;
|
||||
session_id: string;
|
||||
role: string;
|
||||
content: string;
|
||||
tool_calls?: Record<string, unknown>[];
|
||||
tool_results?: Record<string, unknown>[];
|
||||
tokens: number;
|
||||
model_used: string;
|
||||
created_at?: string;
|
||||
}
|
||||
|
||||
export interface AITool {
|
||||
name: string;
|
||||
description: string;
|
||||
parameters: Record<string, unknown>;
|
||||
plugin_name: string;
|
||||
required_permission: string | null;
|
||||
category: string;
|
||||
}
|
||||
|
||||
// ─── Providers ───
|
||||
|
||||
export const fetchProviders = () => apiGet<AIProvider[]>('/ai/providers');
|
||||
export const createProvider = (data: Partial<AIProvider>) => apiPost<AIProvider>('/ai/providers', data);
|
||||
export const updateProvider = (id: string, data: Partial<AIProvider>) => apiPut<AIProvider>(`/ai/providers/${id}`, data);
|
||||
export const deleteProvider = (id: string) => apiDelete(`/ai/providers/${id}`);
|
||||
|
||||
// ─── Models ───
|
||||
|
||||
export const fetchModels = (providerId?: string) =>
|
||||
apiGet<AIModel[]>('/ai/models', { params: providerId ? { provider_id: providerId } : {} });
|
||||
export const createModel = (data: Partial<AIModel>) => apiPost<AIModel>('/ai/models', data);
|
||||
export const updateModel = (id: string, data: Partial<AIModel>) => apiPut<AIModel>(`/ai/models/${id}`, data);
|
||||
export const deleteModel = (id: string) => apiDelete(`/ai/models/${id}`);
|
||||
|
||||
// ─── Presets ───
|
||||
|
||||
export const fetchPresets = () => apiGet<AIPreset[]>('/ai/presets');
|
||||
export const createPreset = (data: Partial<AIPreset>) => apiPost<AIPreset>('/ai/presets', data);
|
||||
export const updatePreset = (id: string, data: Partial<AIPreset>) => apiPut<AIPreset>(`/ai/presets/${id}`, data);
|
||||
export const deletePreset = (id: string) => apiDelete(`/ai/presets/${id}`);
|
||||
|
||||
// ─── Agents ───
|
||||
|
||||
export const fetchAgents = () => apiGet<AIAgent[]>('/ai/agents');
|
||||
export const createAgent = (data: Partial<AIAgent>) => apiPost<AIAgent>('/ai/agents', data);
|
||||
export const updateAgent = (id: string, data: Partial<AIAgent>) => apiPut<AIAgent>(`/ai/agents/${id}`, data);
|
||||
export const deleteAgent = (id: string) => apiDelete(`/ai/agents/${id}`);
|
||||
|
||||
// ─── Tools ───
|
||||
|
||||
export const fetchTools = () => apiGet<AITool[]>('/ai/tools');
|
||||
|
||||
// ─── Sessions ───
|
||||
|
||||
export const fetchSessions = (isSidebar?: boolean) =>
|
||||
apiGet<ChatSession[]>('/ai/sessions', { params: isSidebar !== undefined ? { is_sidebar: isSidebar } : {} });
|
||||
export const createSession = (data: { title?: string; agent_id?: string; is_sidebar?: boolean }) =>
|
||||
apiPost<ChatSession>('/ai/sessions', data);
|
||||
export const updateSession = (id: string, data: Partial<ChatSession>) =>
|
||||
apiPut<ChatSession>(`/ai/sessions/${id}`, data);
|
||||
export const deleteSession = (id: string) => apiDelete(`/ai/sessions/${id}`);
|
||||
|
||||
// ─── Messages ───
|
||||
|
||||
export const fetchMessages = (sessionId: string) =>
|
||||
apiGet<ChatMessage[]>(`/ai/sessions/${sessionId}/messages`);
|
||||
|
||||
// ─── 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(
|
||||
sessionId: string,
|
||||
content: string,
|
||||
agentId?: string
|
||||
): AsyncGenerator<StreamEvent> {
|
||||
const response = await fetch(`/api/v1/ai/sessions/${sessionId}/stream`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user