Files
leocrm/frontend/src/api/ai.ts
T
Agent Zero 7f61dfb25b
Check Cross-Plugin Imports / check (push) Has been cancelled
feat(UI-Overhaul-Phase2): AI Assistent in Kommunikation integriert
Migration 0137: Drop AI chat tables (ai_chat_sessions, ai_chat_messages, ai_chat_attachments, ai_conversations, ai_messages)

Backend:
- Remove AIChatSession, AIChatMessage, AIChatAttachment models from ai_assistant/models.py
- Remove AIConversation, AIMessage from app/models/__init__.py
- Remove session/message/stream/attachment routes from ai_assistant/routes.py
- Add new streaming route POST /ai/conversations/{conversation_id}/stream using comm tables
- Add new messages route GET /ai/conversations/{conversation_id}/messages using comm tables
- Add stream_chat_comm, get_comm_messages, save_comm_message to services.py
- Update external_api.py to use CommConversation/CommMessage instead of AIChatSession/AIChatMessage
- Update unified_search ai_chat_provider to search comm_messages with conversation_type=ai
- Remove ai_copilot router from main.py and routes/__init__.py
- Remove ai_conversation from entity_permissions.py and owner_transfer_service.py
- Update ai_assistant/plugin.py get_entity_models to remove AIChatSession
- Guard ai_copilot_service.py imports with try/except

Frontend:
- Remove AIAssistant.tsx, AIAssistantStandalone.tsx, SessionList.tsx, ChatWindow.tsx
- Remove AI Assistant routes from routes/index.tsx
- Update api/ai.ts: streamChat uses /ai/conversations/{id}/stream, fetchMessages uses /ai/conversations/{id}/messages
- Update Communication.tsx: use convId for AI streaming, remove aiSessionId, use fetchAiMessages for AI conversations
- Update AiChatPanel.tsx: create comm conversation instead of AI session, use new fetchMessages
- Update AISidebar.tsx: remove ChatWindow import, show placeholder

tsc clean, build successful, backend import OK
2026-08-21 13:34:54 +02:00

254 lines
7.4 KiB
TypeScript

/**
* 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 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<string, unknown>[];
tool_results?: Record<string, unknown>[];
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<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 = async () => {
const res = await apiGet<AITool[] | { items: AITool[] }>('/ai/tools');
return Array.isArray(res) ? res : res.items ?? [];
};
// ─── Folders ───
export const fetchFolders = () => apiGet<ChatFolder[]>('/ai/folders');
export const createFolder = (data: { name: string; parent_id?: string }) => apiPost<ChatFolder>('/ai/folders', data);
export const updateFolder = (id: string, data: Partial<ChatFolder>) => apiPut<ChatFolder>(`/ai/folders/${id}`, data);
export const deleteFolder = (id: string) => apiDelete(`/ai/folders/${id}`);
// ─── 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; folder_id?: string }) =>
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 = (conversationId: string) =>
apiGet<{ role: string; content: string }[]>(`/ai/conversations/${conversationId}/messages`);
// ─── Attachments ───
export const fetchAttachments = (sessionId: string) =>
apiGet<ChatAttachment[]>(`/ai/sessions/${sessionId}/attachments`);
export async function uploadAttachment(sessionId: string, file: File): Promise<ChatAttachment> {
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<StreamEvent> {
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
}
}
}
}
}