import { api } from "./client"; import type { ChatConversation, ChatConversationListResponse, ChatMessage, ChatMessageListResponse, } from "@paperclipai/shared"; export const chatApi = { listConversations(companyId: string, opts?: { cursor?: string; limit?: number; search?: string; agentId?: string }) { const params = new URLSearchParams(); if (opts?.cursor) params.set("cursor", opts.cursor); if (opts?.limit) params.set("limit", String(opts.limit)); if (opts?.search) params.set("search", opts.search); if (opts?.agentId) params.set("agentId", opts.agentId); const qs = params.toString(); return api.get( `/companies/${companyId}/conversations${qs ? `?${qs}` : ""}`, ); }, createConversation(companyId: string, data?: { title?: string; agentId?: string }) { return api.post(`/companies/${companyId}/conversations`, data ?? {}); }, getConversation(id: string) { return api.get(`/conversations/${id}`); }, updateConversation( id: string, data: { title?: string; pinnedAt?: string | null; archivedAt?: string | null; agentId?: string | null }, ) { return api.patch(`/conversations/${id}`, data); }, deleteConversation(id: string) { return api.delete(`/conversations/${id}`); }, listMessages(conversationId: string, opts?: { cursor?: string; limit?: number }) { const params = new URLSearchParams(); if (opts?.cursor) params.set("cursor", opts.cursor); if (opts?.limit) params.set("limit", String(opts.limit)); const qs = params.toString(); return api.get( `/conversations/${conversationId}/messages${qs ? `?${qs}` : ""}`, ); }, postMessage( conversationId: string, data: { role: string; content: string; agentId?: string }, ) { return api.post(`/conversations/${conversationId}/messages`, data); }, async postMessageAndStream( conversationId: string, data: { content: string; agentId?: string }, callbacks: { onToken: (token: string) => void; onDone: (messageId: string, content: string) => void; onError: (error: string) => void; }, signal?: AbortSignal, ): Promise { const response = await fetch(`/api/conversations/${conversationId}/stream`, { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify(data), signal, }); if (!response.ok) { callbacks.onError(`HTTP ${response.status}`); return; } const reader = response.body?.getReader(); if (!reader) { callbacks.onError("No response body"); return; } const decoder = new TextDecoder(); let buffer = ""; try { 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 raw = line.slice(6).trim(); if (raw === "[DONE]") continue; try { const parsed = JSON.parse(raw) as { type: string; token?: string; messageId?: string; content?: string; error?: string }; if (parsed.type === "token" && parsed.token !== undefined) { callbacks.onToken(parsed.token); } else if (parsed.type === "done" && parsed.messageId !== undefined) { callbacks.onDone(parsed.messageId, parsed.content ?? ""); } else if (parsed.type === "error") { callbacks.onError(parsed.error ?? "Unknown error"); } } catch { // ignore malformed SSE lines } } } } } catch (err) { if ((err as Error).name !== "AbortError") { callbacks.onError((err as Error).message); } } finally { reader.releaseLock(); } }, savePartialMessage( conversationId: string, data: { role: string; content: string }, ) { return api.post(`/conversations/${conversationId}/messages`, data); }, async editMessage(conversationId: string, messageId: string, content: string) { return api.patch(`/conversations/${conversationId}/messages/${messageId}`, { content }); }, async truncateMessagesAfter(conversationId: string, messageId: string) { await fetch(`/api/conversations/${conversationId}/messages/after/${messageId}`, { method: "DELETE", credentials: "include", }); }, async deleteMessage(conversationId: string, messageId: string) { await fetch(`/api/conversations/${conversationId}/messages/${messageId}`, { method: "DELETE", credentials: "include", }); }, };