44 lines
1.4 KiB
TypeScript
44 lines
1.4 KiB
TypeScript
import type { SendMessageResponse } from "@duoc-thu/shared-types";
|
|
import { buildMockResponse } from "./mockFixtures";
|
|
|
|
/**
|
|
* Sends a question to the real RAG backend through the app's own route
|
|
* handler, which holds the service URL and maps the response.
|
|
*
|
|
* The mock is retained but is now opt-in via `NEXT_PUBLIC_USE_MOCK_CHAT`, and
|
|
* every mocked answer is labelled as such in its own text. Silently falling
|
|
* back to a plausible-looking fake answer is the exact failure this project
|
|
* cannot afford: a fabricated dose that looks like a real one.
|
|
*/
|
|
const USE_MOCK = process.env.NEXT_PUBLIC_USE_MOCK_CHAT === "true";
|
|
|
|
export class ChatUnavailableError extends Error {
|
|
constructor(readonly status: number) {
|
|
super(`Chat backend unavailable (${status})`);
|
|
this.name = "ChatUnavailableError";
|
|
}
|
|
}
|
|
|
|
export async function sendChatMessage(
|
|
content: string,
|
|
conversationId?: string
|
|
): Promise<SendMessageResponse> {
|
|
if (USE_MOCK) {
|
|
await new Promise((resolve) => setTimeout(resolve, 400));
|
|
return buildMockResponse(content);
|
|
}
|
|
|
|
const response = await fetch("/api/chat", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ content, conversationId }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
// Surfaced to the user as an error state, never as an answer.
|
|
throw new ChatUnavailableError(response.status);
|
|
}
|
|
|
|
return (await response.json()) as SendMessageResponse;
|
|
}
|