Explorar o código

feat(speaking): streamClassReport SSE client

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
jimmylee hai 1 mes
pai
achega
1ac02855d7
Modificáronse 1 ficheiros con 26 adicións e 11 borrados
  1. 26 11
      src/services/speaking.ts

+ 26 - 11
src/services/speaking.ts

@@ -1,5 +1,6 @@
 import type { TopicDiscussionConfig } from '@/types/englishSpeaking'
 import { SPEAKING_CONFIG_API_BASE_URL, SPEAKING_DIALOGUE_API_BASE_URL } from '@/views/Editor/EnglishSpeaking/services/speakingApiConfig'
+import { parseSSEStream } from '@/views/Editor/EnglishSpeaking/services/sseStream'
 
 const API_BASE = SPEAKING_CONFIG_API_BASE_URL
 
@@ -83,25 +84,39 @@ export async function listSpeakingSessionsByConfig(
   return parse<ListSessionsByConfigResponse>(res)
 }
 
-export interface ClassSummaryResponse {
-  bullets: [string, string, string]
-  generatedAt: string
-  fromCache: boolean
-  llmStatus: 'ok' | 'fallback'
+export interface ClassReportStudentRef {
+  userId: string
+  name: string
 }
 
-export async function generateClassSummary(
+/**
+ * Stream a tiered Markdown class-analysis report.
+ * Yields Markdown text chunks. Throws Error on a stream `error` event or HTTP failure.
+ */
+export async function* streamClassReport(
   configId: string,
-  userIds: string[],
+  students: ClassReportStudentRef[],
   locale: 'zh' | 'en' | 'hk',
-): Promise<ClassSummaryResponse> {
-  const res = await fetch(`${DIALOGUE_BASE}/sessions/by-config/summary`, {
+): AsyncGenerator<string> {
+  const res = await fetch(`${DIALOGUE_BASE}/sessions/by-config/summary/stream`, {
     method: 'POST',
     headers: { 'Content-Type': 'application/json' },
     credentials: 'include',
-    body: JSON.stringify({ configId, userIds, locale }),
+    body: JSON.stringify({ configId, students, locale }),
   })
-  return parse<ClassSummaryResponse>(res)
+  if (!res.ok || !res.body) {
+    const detail = await res.text().catch(() => '')
+    throw new Error(`[${res.status}] ${detail || res.statusText}`)
+  }
+  for await (const event of parseSSEStream(res.body.getReader())) {
+    if (event.type === 'token') {
+      yield event.text
+    } else if (event.type === 'error') {
+      throw new Error(event.message || 'report stream error')
+    } else if (event.type === 'done') {
+      return
+    }
+  }
 }
 
 export interface DialogueModeDefaults {