import api from "./client"; import { useAuthStore } from "@/stores/auth-store"; export interface Chat { id: string; user_id: string; title: string; skill_id: string | null; is_archived: boolean; created_at: string; updated_at: string; } export interface Message { id: string; chat_id: string; role: "user" | "assistant" | "system" | "tool"; content: string; metadata: Record | null; created_at: string; } export interface ChatListResponse { chats: Chat[]; } export interface MessageListResponse { messages: Message[]; } export async function createChat(title?: string): Promise { const { data } = await api.post("/chats/", { title }); return data; } export async function getChats(archived?: boolean): Promise { const params = archived !== undefined ? { archived } : {}; const { data } = await api.get("/chats/", { params }); return data.chats; } export async function getChat(chatId: string): Promise { const { data } = await api.get(`/chats/${chatId}`); return data; } export async function updateChat( chatId: string, updates: { title?: string; is_archived?: boolean; skill_id?: string } ): Promise { const { data } = await api.patch(`/chats/${chatId}`, updates); return data; } export async function deleteChat(chatId: string): Promise { await api.delete(`/chats/${chatId}`); } export async function getMessages( chatId: string, limit = 50, before?: string ): Promise { const params: Record = { limit }; if (before) params.before = before; const { data } = await api.get( `/chats/${chatId}/messages`, { params } ); return data.messages; } export interface SSECallbacks { onStart?: (messageId: string) => void; onDelta?: (delta: string) => void; onComplete?: (messageId: string, metadata: Record) => void; onError?: (detail: string) => void; } export function sendMessage( chatId: string, content: string, callbacks: SSECallbacks, signal?: AbortSignal ): void { const token = useAuthStore.getState().accessToken; fetch(`/api/v1/chats/${chatId}/messages`, { method: "POST", headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), }, body: JSON.stringify({ content }), signal, }) .then(async (response) => { if (!response.ok) { const err = await response.json().catch(() => ({ detail: "Request failed" })); callbacks.onError?.(err.detail || "Request failed"); return; } const reader = response.body?.getReader(); if (!reader) return; 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() || ""; let currentEvent = ""; for (const line of lines) { if (line.startsWith("event: ")) { currentEvent = line.slice(7).trim(); } else if (line.startsWith("data: ")) { const data = JSON.parse(line.slice(6)); switch (currentEvent) { case "message_start": callbacks.onStart?.(data.message_id); break; case "content_delta": callbacks.onDelta?.(data.delta); break; case "message_end": callbacks.onComplete?.(data.message_id, data.metadata); break; case "error": callbacks.onError?.(data.detail); break; } } } } }) .catch((err) => { if (err.name !== "AbortError") { callbacks.onError?.(err.message || "Network error"); } }); }