From 86b05dac357e9af8075007b94160519c1d1c5802 Mon Sep 17 00:00:00 2001 From: Li Xin Date: Thu, 17 Apr 2025 16:39:27 +0800 Subject: [PATCH] feat: enable API proxy --- web/src/app/api/chat/stream/route.ts | 73 ++++++++++++++++++++++++++++ web/src/core/api/chat.ts | 2 +- web/src/env.js | 2 +- 3 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 web/src/app/api/chat/stream/route.ts diff --git a/web/src/app/api/chat/stream/route.ts b/web/src/app/api/chat/stream/route.ts new file mode 100644 index 0000000..6eaf96b --- /dev/null +++ b/web/src/app/api/chat/stream/route.ts @@ -0,0 +1,73 @@ +import { NextResponse, type NextRequest } from "next/server"; + +import { env } from "~/env"; + +export async function POST(request: NextRequest) { + const requestBody = await request.json(); + + const stream = new ReadableStream({ + async start(controller) { + try { + const response = await fetch( + (env.NEXT_PUBLIC_API_URL + ? env.NEXT_PUBLIC_API_URL + : "http://localhost:8000/api") + "/chat/stream", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(requestBody), + }, + ); + if (!response.ok) { + const errorBody = await response.text(); + console.error("API error message:", errorBody); + controller.enqueue( + createErrorEvent(`API responded with status ${response.status}`), + ); + controller.close(); + return; + } + const reader = response.body + ?.pipeThrough(new TextDecoderStream()) + .getReader(); + if (!reader) { + controller.enqueue( + createErrorEvent("No data received from /api/chat/stream"), + ); + controller.close(); + return; + } + + while (true) { + const { done, value } = await reader.read(); + if (done) { + break; + } + controller.enqueue(value); + } + + controller.close(); + reader.releaseLock(); + } catch (error) { + console.error("Stream error:", error); + controller.enqueue(createErrorEvent("Stream interrupted")); + controller.close(); + } + }, + }); + + return new NextResponse(stream, { + headers: { + "Cache-Control": "no-cache, no-transform", + Connection: "keep-alive", + "Content-Type": "text/event-stream", + }, + status: 200, + }); +} + +function createErrorEvent(message: string) { + return `event: error\ndata: ${message}\n\n`; +} diff --git a/web/src/core/api/chat.ts b/web/src/core/api/chat.ts index eac16fb..dc6171e 100644 --- a/web/src/core/api/chat.ts +++ b/web/src/core/api/chat.ts @@ -22,7 +22,7 @@ export function chatStream( return chatStreamMock(userMessage, params, options); } return fetchStream( - (env.NEXT_PUBLIC_API_URL ?? "http://localhost:8000/api") + "/chat/stream", + (env.NEXT_PUBLIC_API_URL ?? "/api") + "/chat/stream", { body: JSON.stringify({ messages: [{ role: "user", content: userMessage }], diff --git a/web/src/env.js b/web/src/env.js index ef0ba9f..38c10ea 100644 --- a/web/src/env.js +++ b/web/src/env.js @@ -19,7 +19,7 @@ export const env = createEnv({ * `NEXT_PUBLIC_`. */ client: { - NEXT_PUBLIC_API_URL: z.string(), + NEXT_PUBLIC_API_URL: z.string().optional(), }, /**