Files
Edu/services/teacher-bff/src/teacher/ai-chat-sse.controller.ts

65 lines
2.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// SSE 端点P5ai.StreamChat gRPC stream → BFF → 前端 EventSource
// 路由GET /ai/chat/stream
// 裁决依据02-architecture-design.md §10
import { Controller, Get, Query, Res, Req } from "@nestjs/common";
import type { Request, Response } from "express";
import { TeacherService } from "./teacher.service.js";
import type { CallContext } from "../clients/types.js";
import type { ChatMessage } from "../clients/ai/ai.types.js";
@Controller("/ai/chat")
export class AiChatSseController {
constructor(private readonly teacherService: TeacherService) {}
/**
* SSE 流式聊天端点
* 前端使用 EventSource 连接BFF 透传 ai.StreamChat gRPC stream
*/
@Get("stream")
async streamChat(
@Req() req: Request,
@Res() res: Response,
@Query("prompt") prompt: string,
): Promise<void> {
// 设置 SSE 响应头
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
res.setHeader("X-Accel-Buffering", "no"); // 禁用 Nginx 缓冲
// 构造 CallContext从 req.headers 提取 x-user-id 和 x-request-id
const ctx: CallContext = {
userId: (req.headers["x-user-id"] as string) ?? "dev-user",
traceId: (req.headers["x-request-id"] as string) ?? "",
};
// 构造聊天消息
const messages: ChatMessage[] = [{ role: "user", content: prompt }];
try {
const stream = await this.teacherService.streamChat(ctx, messages);
for await (const chunk of stream) {
const data = JSON.stringify({
content: chunk.content,
done: chunk.done,
});
res.write(`data: ${data}\n\n`);
if (chunk.done) {
break;
}
}
res.write("event: done\ndata: {}\n\n");
} catch (err) {
const errorData = JSON.stringify({
error: err instanceof Error ? err.message : "Unknown error",
});
res.write(`event: error\ndata: ${errorData}\n\n`);
} finally {
res.end();
}
}
}