mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 14:43:06 +08:00
b76f5adaad
* feat(ee): AI menu * - Add insert below and copy option * prebuild @editor-ext * sanitize output * clear existing output * switch to menu component * refactor directory * separator * refactor directory * support more languages * pass markdown to model * fix: close AI menu on page change * enhance text input and preview styling * fix: Use absolute positioning for the AI menu * make preview scrollable * activation controls * enhance bubble menu * sync * set width * fix line break * switch terminologies * cloud * buffer --------- Co-authored-by: Philipinho <16838612+Philipinho@users.noreply.github.com>
93 lines
2.4 KiB
TypeScript
93 lines
2.4 KiB
TypeScript
import api from "@/lib/api-client.ts";
|
|
import {
|
|
AiGenerateDto,
|
|
AiContentResponse,
|
|
AiStreamChunk,
|
|
AiStreamError,
|
|
} from "@/ee/ai/types/ai.types.ts";
|
|
|
|
export async function generateAiContent(
|
|
data: AiGenerateDto,
|
|
): Promise<AiContentResponse> {
|
|
const req = await api.post<AiContentResponse>("/ai/generate", data);
|
|
return req.data;
|
|
}
|
|
|
|
export async function generateAiContentStream(
|
|
data: AiGenerateDto,
|
|
onChunk: (chunk: AiStreamChunk) => void,
|
|
onError?: (error: AiStreamError) => void,
|
|
onComplete?: () => void,
|
|
): Promise<AbortController> {
|
|
const abortController = new AbortController();
|
|
try {
|
|
const response = await fetch("/api/ai/generate/stream", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(data),
|
|
signal: abortController.signal,
|
|
credentials: "include", // This ensures cookies are sent, matching axios withCredentials
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const reader = response.body?.getReader();
|
|
const decoder = new TextDecoder();
|
|
|
|
if (!reader) {
|
|
throw new Error("Response body is not readable");
|
|
}
|
|
|
|
const processStream = async () => {
|
|
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);
|
|
if (parsed.error) {
|
|
onError?.(parsed);
|
|
} else {
|
|
onChunk(parsed);
|
|
}
|
|
} catch (e) {
|
|
// Skip invalid JSON
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
if (error.name !== "AbortError") {
|
|
onError?.({ error: error.message });
|
|
}
|
|
} finally {
|
|
reader.releaseLock();
|
|
}
|
|
};
|
|
|
|
processStream();
|
|
} catch (error) {
|
|
onError?.({ error: error.message });
|
|
}
|
|
|
|
return abortController;
|
|
}
|