From 4cdc542291bdf85b373effef1050fc4207b3b950 Mon Sep 17 00:00:00 2001 From: fish Date: Sun, 10 May 2026 17:11:51 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8DAI=E5=88=86=E6=9E=90SSE?= =?UTF-8?q?=E6=B5=81=E6=96=AD=E5=BC=80=EF=BC=9A=E4=B8=AD=E9=97=B4=E4=BB=B6?= =?UTF-8?q?=E9=80=8F=E4=BC=A0Flusher=E3=80=81DB=20Key=E7=94=9F=E6=95=88?= =?UTF-8?q?=E3=80=81=E9=94=99=E8=AF=AF=E6=8F=90=E7=A4=BA=E4=B8=8D=E8=A6=86?= =?UTF-8?q?=E7=9B=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/backend/internal/handlers/ai.go | 2 +- web/backend/internal/middleware/logger.go | 7 +++++ .../src/components/ScoreDetailDrawer.vue | 6 ++--- web/frontend/src/views/ScoresView.vue | 26 +++++++++++++++++-- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/web/backend/internal/handlers/ai.go b/web/backend/internal/handlers/ai.go index f346c7a..3fa5aaf 100644 --- a/web/backend/internal/handlers/ai.go +++ b/web/backend/internal/handlers/ai.go @@ -88,7 +88,7 @@ func (d *Deps) Analyze(w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") - if err := streamLLM(d.AIConfig, prompt, w, flusher); err != nil { + if err := streamLLM(llmCfg, prompt, w, flusher); err != nil { log.Printf("[ai] stream error: %v", err) sendSSE(w, flusher, "error", err.Error()) } diff --git a/web/backend/internal/middleware/logger.go b/web/backend/internal/middleware/logger.go index 790c10e..1221126 100644 --- a/web/backend/internal/middleware/logger.go +++ b/web/backend/internal/middleware/logger.go @@ -39,6 +39,13 @@ func (r *statusRecorder) WriteHeader(code int) { r.ResponseWriter.WriteHeader(code) } +// Flush 透传 http.Flusher,避免 SSE 流式响应被中间件阻断。 +func (r *statusRecorder) Flush() { + if f, ok := r.ResponseWriter.(http.Flusher); ok { + f.Flush() + } +} + func writeJSON(w http.ResponseWriter, status int, body any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) diff --git a/web/frontend/src/components/ScoreDetailDrawer.vue b/web/frontend/src/components/ScoreDetailDrawer.vue index c3e1022..4fdbeab 100644 --- a/web/frontend/src/components/ScoreDetailDrawer.vue +++ b/web/frontend/src/components/ScoreDetailDrawer.vue @@ -31,7 +31,7 @@ async function askAI() { aiContent.value = '' aiError.value = '' - const url = `/api/v1/ai/analyze?ts_code=${encodeURIComponent(score.value.ts_code)}&trade_date=${encodeURIComponent(score.value.trade_date)}` + const url = `/api/ai/analyze?ts_code=${encodeURIComponent(score.value.ts_code)}&trade_date=${encodeURIComponent(score.value.trade_date)}` es = new EventSource(url) es.addEventListener('token', (e) => { aiContent.value += e.data @@ -44,7 +44,7 @@ async function askAI() { closeAI() }) es.onerror = () => { - if (!aiContent.value) aiError.value = '连接中断' + if (!aiContent.value && !aiError.value) aiError.value = '连接中断' closeAI() } } @@ -267,7 +267,7 @@ const quadrantLabel = (q: string) => { 🤖 AI 分析当前打分 -
+
🤖 AI 分析 取消 diff --git a/web/frontend/src/views/ScoresView.vue b/web/frontend/src/views/ScoresView.vue index 2e88785..d81a84c 100644 --- a/web/frontend/src/views/ScoresView.vue +++ b/web/frontend/src/views/ScoresView.vue @@ -95,7 +95,7 @@ async function askAI() { const ts = encodeURIComponent(scoreResult.value.ts_code) const td = encodeURIComponent(scoreResult.value.trade_date) - aiES = new EventSource(`/api/v1/ai/analyze?ts_code=${ts}&trade_date=${td}`) + aiES = new EventSource(`/api/ai/analyze?ts_code=${ts}&trade_date=${td}`) aiES.addEventListener('token', (e) => { aiContent.value += e.data }) aiES.addEventListener('error', (e) => { aiError.value = (e as any)?.data || '请求失败' @@ -103,7 +103,7 @@ async function askAI() { }) aiES.addEventListener('done', () => closeAI()) aiES.onerror = () => { - if (!aiContent.value) aiError.value = '连接中断' + if (!aiContent.value && !aiError.value) aiError.value = '连接中断' closeAI() } } @@ -232,6 +232,28 @@ onMounted(async () => { + +
+ + 🤖 AI 分析当前打分 + +
+
+ 🤖 AI 分析 + 取消 +
+
+
+
{{ aiError }}
+
⏳ 正在分析...
+
+
+