import type { TopicDiscussionConfig } from '@/types/englishSpeaking' import { SPEAKING_CONFIG_API_BASE_URL } from '@/views/Editor/EnglishSpeaking/services/speakingApiConfig' const API_BASE = SPEAKING_CONFIG_API_BASE_URL export interface SpeakingConfigRecord { id: string config: TopicDiscussionConfig ownerUid?: string | null } async function parse(res: Response): Promise { if (!res.ok) { const detail = await res.text().catch(() => '') throw new Error(`[${res.status}] ${detail || res.statusText}`) } return res.json() as Promise } /** 新建话题讨论配置 */ export async function createSpeakingConfig( config: TopicDiscussionConfig, ownerUid?: string | null, ): Promise { const res = await fetch(API_BASE, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ config, ownerUid: ownerUid ?? null }), }) return parse(res) } /** 读取话题讨论配置 */ export async function getSpeakingConfig(id: string): Promise { const res = await fetch(`${API_BASE}/${encodeURIComponent(id)}`) return parse(res) } /** 更新话题讨论配置 */ export async function updateSpeakingConfig( id: string, config: TopicDiscussionConfig, ): Promise { const res = await fetch(`${API_BASE}/${encodeURIComponent(id)}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ config }), }) return parse(res) } import { SPEAKING_DIALOGUE_API_BASE_URL } from '@/views/Editor/EnglishSpeaking/services/speakingApiConfig' const DIALOGUE_BASE = SPEAKING_DIALOGUE_API_BASE_URL export interface ClassSessionSummary { userId: string sessionId: string status: 'active' | 'completed' | 'abandoned' overallStatus: 'ready' | 'generating' | 'failed' | null currentRound: number totalRounds: number overallScore: number | null dimensions: Record topHighlights: string[] topImprovements: string[] createdAt: string | null completedAt: string | null } export interface ListSessionsByConfigResponse { summaries: ClassSessionSummary[] } export async function listSpeakingSessionsByConfig( configId: string, userIds: string[], ): Promise { const params = new URLSearchParams({ configId, userIds: userIds.join(',') }) const res = await fetch(`${DIALOGUE_BASE}/sessions/by-config?${params}`, { method: 'GET', credentials: 'include', }) if (!res.ok) throw new Error(`listSpeakingSessionsByConfig failed: ${res.status}`) return res.json() } export interface ClassSummaryResponse { bullets: [string, string, string] generatedAt: string fromCache: boolean llmStatus: 'ok' | 'fallback' } export async function generateClassSummary( configId: string, userIds: string[], locale: 'zh' | 'en' | 'hk', ): Promise { const res = await fetch(`${DIALOGUE_BASE}/sessions/by-config/summary`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ configId, userIds, locale }), }) if (!res.ok) throw new Error(`generateClassSummary failed: ${res.status}`) return res.json() }