mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 06:23:06 +08:00
57efb91bd3
* feat: ai chat * feat: ai chat * sync * cleanup * view space button
145 lines
3.6 KiB
TypeScript
145 lines
3.6 KiB
TypeScript
import api from "@/lib/api-client.ts";
|
|
import type {
|
|
AiChat,
|
|
AiChatMessage,
|
|
AiChatStreamEvent,
|
|
ChatAttachment,
|
|
} from "../types/ai-chat.types";
|
|
import { IPagination } from "@/lib/types.ts";
|
|
|
|
export async function createChat(): Promise<AiChat> {
|
|
const req = await api.post<AiChat>("/ai/chats/create");
|
|
return req.data;
|
|
}
|
|
|
|
export async function listChats(params?: {
|
|
limit?: number;
|
|
cursor?: string;
|
|
}): Promise<IPagination<AiChat>> {
|
|
const req = await api.post("/ai/chats", params);
|
|
return req.data;
|
|
}
|
|
|
|
export async function getChatInfo(
|
|
chatId: string,
|
|
): Promise<{ chat: AiChat; messages: AiChatMessage[] }> {
|
|
const req = await api.post("/ai/chats/info", { chatId });
|
|
return req.data;
|
|
}
|
|
|
|
export async function deleteChat(chatId: string): Promise<void> {
|
|
await api.post("/ai/chats/delete", { chatId });
|
|
}
|
|
|
|
export async function updateChatTitle(
|
|
chatId: string,
|
|
title: string,
|
|
): Promise<void> {
|
|
await api.post("/ai/chats/update", { chatId, title });
|
|
}
|
|
|
|
export async function searchChats(query: string): Promise<AiChat[]> {
|
|
const req = await api.post("/ai/chats/search", { query });
|
|
return req.data;
|
|
}
|
|
|
|
export async function uploadChatFile(
|
|
file: File,
|
|
chatId?: string,
|
|
): Promise<ChatAttachment> {
|
|
const formData = new FormData();
|
|
formData.append("file", file);
|
|
if (chatId) {
|
|
formData.append("chatId", chatId);
|
|
}
|
|
return await api.post("/ai/chats/upload", formData, {
|
|
headers: { "Content-Type": "multipart/form-data" },
|
|
});
|
|
}
|
|
|
|
export function sendChatMessage(
|
|
params: {
|
|
chatId?: string;
|
|
content: string;
|
|
mentionedPageIds?: string[];
|
|
contextPageId?: string;
|
|
attachmentIds?: string[];
|
|
},
|
|
onEvent: (event: AiChatStreamEvent) => void,
|
|
onError?: (error: string) => void,
|
|
onComplete?: () => void,
|
|
): AbortController {
|
|
const abortController = new AbortController();
|
|
|
|
(async () => {
|
|
try {
|
|
const response = await fetch("/api/ai/chats/send", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(params),
|
|
signal: abortController.signal,
|
|
credentials: "include",
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorBody = await response.text();
|
|
let errorMessage = `HTTP error ${response.status}`;
|
|
try {
|
|
const parsed = JSON.parse(errorBody);
|
|
errorMessage = parsed.message || errorMessage;
|
|
} catch {
|
|
// use default
|
|
}
|
|
onError?.(errorMessage);
|
|
return;
|
|
}
|
|
|
|
const reader = response.body?.getReader();
|
|
const decoder = new TextDecoder();
|
|
|
|
if (!reader) {
|
|
onError?.("Response body is not readable");
|
|
return;
|
|
}
|
|
|
|
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 data = line.slice(6);
|
|
if (data === "[DONE]") {
|
|
onComplete?.();
|
|
return;
|
|
}
|
|
try {
|
|
const parsed = JSON.parse(data) as AiChatStreamEvent;
|
|
onEvent(parsed);
|
|
} catch {
|
|
// Skip invalid JSON
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
reader.releaseLock();
|
|
}
|
|
|
|
onComplete?.();
|
|
} catch (error: any) {
|
|
if (error.name !== "AbortError") {
|
|
onError?.(error.message);
|
|
}
|
|
}
|
|
})();
|
|
|
|
return abortController;
|
|
}
|