Spaces:
Sleeping
Sleeping
| // web/src/lib/api.ts | |
| // Aligns with api/server.py routes: | |
| // POST /api/login, /api/chat, /api/upload, /api/export, /api/summary, /api/feedback | |
| // GET /api/memoryline | |
| export type LearningMode = | |
| | "general" | |
| | "concept" | |
| | "socratic" | |
| | "exam" | |
| | "assignment" | |
| | "summary"; | |
| export type LanguagePref = "Auto" | "English" | "中文"; | |
| export type DocType = | |
| | "Syllabus" | |
| | "Lecture Slides / PPT" | |
| | "Literature Review / Paper" | |
| | "Other Course Document"; | |
| const DEFAULT_TIMEOUT_MS = 20000; | |
| function getBaseUrl() { | |
| // Vite env: VITE_API_BASE can be "", "http://localhost:8000", etc. | |
| const v = (import.meta as any)?.env?.VITE_API_BASE as string | undefined; | |
| return v && v.trim() ? v.trim() : ""; | |
| } | |
| async function fetchWithTimeout( | |
| input: RequestInfo, | |
| init?: RequestInit, | |
| timeoutMs = DEFAULT_TIMEOUT_MS | |
| ) { | |
| const controller = new AbortController(); | |
| const id = setTimeout(() => controller.abort(), timeoutMs); | |
| try { | |
| return await fetch(input, { ...init, signal: controller.signal }); | |
| } finally { | |
| clearTimeout(id); | |
| } | |
| } | |
| async function parseJsonSafe(res: Response) { | |
| const text = await res.text(); | |
| try { | |
| return text ? JSON.parse(text) : null; | |
| } catch { | |
| return { _raw: text }; | |
| } | |
| } | |
| function errMsg(data: any, fallback: string) { | |
| return data && (data.error || data.detail || data.message) | |
| ? String(data.error || data.detail || data.message) | |
| : fallback; | |
| } | |
| // -------------------- | |
| // /api/login | |
| // -------------------- | |
| export type ApiLoginReq = { | |
| name: string; | |
| user_id: string; | |
| }; | |
| export type ApiLoginResp = | |
| | { ok: true; user: { name: string; user_id: string } } | |
| | { ok: false; error: string }; | |
| export async function apiLogin(payload: ApiLoginReq): Promise<ApiLoginResp> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout(`${base}/api/login`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload), | |
| }); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, `apiLogin failed (${res.status})`)); | |
| return data as ApiLoginResp; | |
| } | |
| // -------------------- | |
| // /api/chat | |
| // -------------------- | |
| export type ApiChatReq = { | |
| user_id: string; | |
| message: string; | |
| learning_mode: string; // backend expects string (not strict union) | |
| language_preference?: string; // "Auto" | "English" | "中文" | |
| doc_type?: string; // "Syllabus" | "Lecture Slides / PPT" | ... | |
| }; | |
| // ✅ allow backend to return either object refs or preformatted string refs | |
| export type ApiChatRefObj = { source_file?: string; section?: string }; | |
| export type ApiChatRefRaw = ApiChatRefObj | string; | |
| // ✅ normalize ANY ref format into {source_file, section} so App can map reliably | |
| function normalizeRefs(raw: any): ApiChatRefObj[] { | |
| const arr: any[] = Array.isArray(raw) ? raw : []; | |
| return arr | |
| .map((x) => { | |
| // Case A: already object | |
| if (x && typeof x === "object" && !Array.isArray(x)) { | |
| const a = x.source_file != null ? String(x.source_file) : ""; | |
| const b = x.section != null ? String(x.section) : ""; | |
| return { source_file: a || undefined, section: b || undefined }; | |
| } | |
| // Case B: string like "file.pdf — p3#1" (or just "file.pdf") | |
| if (typeof x === "string") { | |
| const s = x.trim(); | |
| if (!s) return null; | |
| const parts = s.split("—").map((p) => p.trim()).filter(Boolean); | |
| if (parts.length >= 2) { | |
| return { source_file: parts[0], section: parts.slice(1).join(" — ") }; | |
| } | |
| return { source_file: s, section: undefined }; | |
| } | |
| return null; | |
| }) | |
| .filter(Boolean) as ApiChatRefObj[]; | |
| } | |
| export type ApiChatResp = { | |
| reply: string; | |
| session_status_md: string; | |
| // ✅ after normalization, always object array | |
| refs: ApiChatRefObj[]; | |
| latency_ms: number; | |
| // optional tracing run id returned by backend | |
| run_id?: string | null; | |
| }; | |
| export async function apiChat(payload: ApiChatReq): Promise<ApiChatResp> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout( | |
| `${base}/api/chat`, | |
| { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ | |
| language_preference: "Auto", | |
| doc_type: "Syllabus", | |
| ...payload, | |
| }), | |
| }, | |
| 60000 // chat can be slow | |
| ); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, `apiChat failed (${res.status})`)); | |
| // backend returns { reply, session_status_md, refs, latency_ms, run_id? } | |
| // but refs may be ApiChatRefObj[] OR string[] | |
| const out = data as any; | |
| return { | |
| reply: String(out?.reply ?? ""), | |
| session_status_md: String(out?.session_status_md ?? ""), | |
| refs: normalizeRefs(out?.refs ?? out?.references), | |
| latency_ms: Number(out?.latency_ms ?? 0), | |
| run_id: out?.run_id ?? null, | |
| }; | |
| } | |
| // -------------------- | |
| // /api/quiz/start | |
| // -------------------- | |
| export type ApiQuizStartReq = { | |
| user_id: string; | |
| language_preference?: string; // "Auto" | "English" | "中文" | |
| doc_type?: string; // default: "Literature Review / Paper" (backend default ok) | |
| learning_mode?: string; // default: "quiz" | |
| }; | |
| export type ApiQuizStartResp = { | |
| reply: string; | |
| session_status_md: string; | |
| refs: ApiChatRefObj[]; | |
| latency_ms: number; | |
| run_id?: string | null; | |
| }; | |
| export async function apiQuizStart( | |
| payload: ApiQuizStartReq | |
| ): Promise<ApiQuizStartResp> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout( | |
| `${base}/api/quiz/start`, | |
| { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ | |
| language_preference: "Auto", | |
| doc_type: "Literature Review / Paper", | |
| learning_mode: "quiz", | |
| ...payload, | |
| }), | |
| }, | |
| 60000 | |
| ); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, `apiQuizStart failed (${res.status})`)); | |
| const out = data as any; | |
| return { | |
| reply: String(out?.reply ?? ""), | |
| session_status_md: String(out?.session_status_md ?? ""), | |
| refs: normalizeRefs(out?.refs ?? out?.references), | |
| latency_ms: Number(out?.latency_ms ?? 0), | |
| run_id: out?.run_id ?? null, | |
| }; | |
| } | |
| // -------------------- | |
| // /api/upload | |
| // -------------------- | |
| export type ApiUploadResp = { | |
| ok: boolean; | |
| added_chunks?: number; | |
| status_md?: string; | |
| error?: string; | |
| }; | |
| export async function apiUpload(args: { | |
| user_id: string; | |
| doc_type: string; | |
| file: File; | |
| }): Promise<ApiUploadResp> { | |
| const base = getBaseUrl(); | |
| const fd = new FormData(); | |
| fd.append("user_id", args.user_id); | |
| fd.append("doc_type", args.doc_type); | |
| fd.append("file", args.file); | |
| const res = await fetchWithTimeout( | |
| `${base}/api/upload`, | |
| { method: "POST", body: fd }, | |
| 120000 | |
| ); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, `apiUpload failed (${res.status})`)); | |
| return data as ApiUploadResp; | |
| } | |
| // -------------------- | |
| // /api/export | |
| // -------------------- | |
| export async function apiExport(payload: { | |
| user_id: string; | |
| learning_mode: string; | |
| }): Promise<{ markdown: string }> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout(`${base}/api/export`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload), | |
| }); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, `apiExport failed (${res.status})`)); | |
| return data as { markdown: string }; | |
| } | |
| // -------------------- | |
| // /api/summary | |
| // -------------------- | |
| export async function apiSummary(payload: { | |
| user_id: string; | |
| learning_mode: string; | |
| language_preference?: string; | |
| }): Promise<{ markdown: string }> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout(`${base}/api/summary`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ language_preference: "Auto", ...payload }), | |
| }); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, `apiSummary failed (${res.status})`)); | |
| return data as { markdown: string }; | |
| } | |
| // -------------------- | |
| // /api/feedback | |
| // -------------------- | |
| export type ApiFeedbackReq = { | |
| user_id: string; | |
| rating: "helpful" | "not_helpful"; | |
| // run id so backend can attach feedback to tracing run | |
| run_id?: string | null; | |
| assistant_message_id?: string; | |
| assistant_text: string; | |
| user_text?: string; | |
| comment?: string; | |
| tags?: string[]; | |
| refs?: string[]; | |
| learning_mode?: string; | |
| doc_type?: string; | |
| timestamp_ms?: number; | |
| }; | |
| export async function apiFeedback( | |
| payload: ApiFeedbackReq | |
| ): Promise<{ ok: boolean }> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout(`${base}/api/feedback`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload), | |
| }); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, `apiFeedback failed (${res.status})`)); | |
| return data as { ok: boolean }; | |
| } | |
| // -------------------- | |
| // /api/memoryline | |
| // -------------------- | |
| export async function apiMemoryline( | |
| user_id: string | |
| ): Promise<{ next_review_label: string; progress_pct: number }> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout( | |
| `${base}/api/memoryline?user_id=${encodeURIComponent(user_id)}`, | |
| { method: "GET" } | |
| ); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, `apiMemoryline failed (${res.status})`)); | |
| return data as { next_review_label: string; progress_pct: number }; | |
| } | |
| // -------------------- | |
| // /api/tts (text-to-speech) – returns audio/mpeg | |
| // -------------------- | |
| export async function apiTts(payload: { | |
| user_id: string; | |
| text: string; | |
| voice?: string; | |
| }): Promise<Blob> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout( | |
| `${base}/api/tts`, | |
| { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ voice: "nova", ...payload }), | |
| }, | |
| 60000 | |
| ); | |
| if (!res.ok) { | |
| const data = await parseJsonSafe(res); | |
| throw new Error(errMsg(data, `TTS failed (${res.status})`)); | |
| } | |
| return res.blob(); | |
| } | |
| // -------------------- | |
| // /api/podcast – returns audio/mpeg | |
| // -------------------- | |
| export async function apiPodcast(payload: { | |
| user_id: string; | |
| source: "summary" | "conversation"; | |
| voice?: string; | |
| }): Promise<Blob> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout( | |
| `${base}/api/podcast`, | |
| { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ voice: "nova", ...payload }), | |
| }, | |
| 120000 | |
| ); | |
| if (!res.ok) { | |
| const data = await parseJsonSafe(res); | |
| throw new Error(errMsg(data, `Podcast failed (${res.status})`)); | |
| } | |
| return res.blob(); | |
| } | |
| // -------------------- | |
| // 教师 Agent API(AI 智能建课) | |
| // -------------------- | |
| const TEACHER_TIMEOUT_MS = 90000; | |
| export type TeacherStatus = { | |
| weaviate_configured: boolean; | |
| features: string[]; | |
| }; | |
| export async function apiTeacherStatus(): Promise<TeacherStatus> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout(`${base}/api/teacher/status`, {}, 10000); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, "Teacher status failed")); | |
| return data as TeacherStatus; | |
| } | |
| export async function apiTeacherCourseDescription(payload: { | |
| topic: string; | |
| outline_hint?: string | null; | |
| reply_language?: string | null; | |
| history?: Array<[string, string]> | null; | |
| userMessage?: string | null; | |
| }): Promise<{ description: string; weaviate_used: boolean }> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout( | |
| `${base}/api/teacher/course-description`, | |
| { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload), | |
| }, | |
| TEACHER_TIMEOUT_MS | |
| ); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, "Course description failed")); | |
| return data as { description: string; weaviate_used: boolean }; | |
| } | |
| export async function apiTeacherDocSuggestion(payload: { | |
| topic: string; | |
| current_doc_excerpt?: string | null; | |
| doc_type?: string; | |
| reply_language?: string | null; | |
| history?: Array<[string, string]> | null; | |
| }): Promise<{ suggestion: string; weaviate_used: boolean }> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout( | |
| `${base}/api/teacher/doc-suggestion`, | |
| { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload), | |
| }, | |
| TEACHER_TIMEOUT_MS | |
| ); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, "Doc suggestion failed")); | |
| return data as { suggestion: string; weaviate_used: boolean }; | |
| } | |
| export async function apiTeacherAssignmentQuestions(payload: { | |
| topic: string; | |
| week_or_module?: string | null; | |
| question_type?: string; | |
| reply_language?: string | null; | |
| history?: Array<[string, string]> | null; | |
| }): Promise<{ suggestion: string; weaviate_used: boolean }> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout( | |
| `${base}/api/teacher/assignment-questions`, | |
| { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload), | |
| }, | |
| TEACHER_TIMEOUT_MS | |
| ); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, "Assignment questions failed")); | |
| return data as { suggestion: string; weaviate_used: boolean }; | |
| } | |
| export async function apiTeacherAssessmentAnalysis(payload: { | |
| assessment_summary: string; | |
| course_topic_hint?: string | null; | |
| reply_language?: string | null; | |
| history?: Array<[string, string]> | null; | |
| }): Promise<{ analysis: string; weaviate_used: boolean }> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout( | |
| `${base}/api/teacher/assessment-analysis`, | |
| { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload), | |
| }, | |
| TEACHER_TIMEOUT_MS | |
| ); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, "Assessment analysis failed")); | |
| return data as { analysis: string; weaviate_used: boolean }; | |
| } | |
| // -------------------- Courseware (vision, activities, copilot, qa-optimize, content) -------------------- | |
| export async function apiCoursewareVision(payload: { | |
| course_info: string; | |
| syllabus: string; | |
| history?: Array<[string, string]> | null; | |
| }): Promise<{ content: string; weaviate_used: boolean }> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout( | |
| `${base}/api/courseware/vision`, | |
| { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }, | |
| TEACHER_TIMEOUT_MS | |
| ); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, "Course vision failed")); | |
| return data as { content: string; weaviate_used: boolean }; | |
| } | |
| export async function apiCoursewareActivities(payload: { | |
| topic: string; | |
| learning_objectives?: string | null; | |
| rag_context_override?: string | null; | |
| history?: Array<[string, string]> | null; | |
| }): Promise<{ content: string; weaviate_used: boolean }> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout( | |
| `${base}/api/courseware/activities`, | |
| { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }, | |
| TEACHER_TIMEOUT_MS | |
| ); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, "Activities failed")); | |
| return data as { content: string; weaviate_used: boolean }; | |
| } | |
| export async function apiCoursewareCopilot(payload: { | |
| current_content: string; | |
| student_profiles?: Array<{ name?: string; progress?: string; behavior?: string }> | null; | |
| history?: Array<[string, string]> | null; | |
| }): Promise<{ content: string; weaviate_used: boolean }> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout( | |
| `${base}/api/courseware/copilot`, | |
| { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }, | |
| TEACHER_TIMEOUT_MS | |
| ); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, "Copilot failed")); | |
| return data as { content: string; weaviate_used: boolean }; | |
| } | |
| export async function apiCoursewareQAOptimize(payload: { | |
| quiz_summary: string; | |
| course_topic?: string | null; | |
| history?: Array<[string, string]> | null; | |
| }): Promise<{ content: string; weaviate_used: boolean }> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout( | |
| `${base}/api/courseware/qa-optimize`, | |
| { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }, | |
| TEACHER_TIMEOUT_MS | |
| ); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, "QA optimize failed")); | |
| return data as { content: string; weaviate_used: boolean }; | |
| } | |
| export async function apiCoursewareContent(payload: { | |
| topic: string; | |
| duration?: string | null; | |
| outline_points?: string | null; | |
| history?: Array<[string, string]> | null; | |
| }): Promise<{ content: string; weaviate_used: boolean }> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout( | |
| `${base}/api/courseware/content`, | |
| { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }, | |
| TEACHER_TIMEOUT_MS | |
| ); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, "Content generation failed")); | |
| return data as { content: string; weaviate_used: boolean }; | |
| } | |
| // -------------------- AI Courseware structured APIs (Plan / Prepare / Reflect / Improve) -------------------- | |
| export type AiMeta = { | |
| model: string; | |
| model_version?: string | null; | |
| prompt_version: string; | |
| temperature: number; | |
| tokens_used: number; | |
| latency_ms: number; | |
| }; | |
| // 2.1 Generate Syllabus Preview | |
| export type AiSyllabusContext = { | |
| courseName: string; | |
| learningOutcome: string; | |
| studentLevel: string; | |
| teachingFocus: string; | |
| courseLength: number; | |
| }; | |
| export type AiSyllabusGenerateReq = { | |
| requestId: string; | |
| context: AiSyllabusContext; | |
| }; | |
| export type AiWeekSyllabus = { | |
| weekNumber: number; | |
| title: string; | |
| learningObjectives: string[]; | |
| topics: string[]; | |
| }; | |
| export type AiSyllabusGenerateResp = { | |
| data: { syllabus: AiWeekSyllabus[] }; | |
| meta: AiMeta; | |
| }; | |
| export async function apiAiSyllabusGenerate( | |
| payload: AiSyllabusGenerateReq | |
| ): Promise<AiSyllabusGenerateResp> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout( | |
| `${base}/ai/courseware/syllabus/generate`, | |
| { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload), | |
| }, | |
| TEACHER_TIMEOUT_MS | |
| ); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, "AI syllabus generate failed")); | |
| return data as AiSyllabusGenerateResp; | |
| } | |
| // 2.2 Generate Lesson Flow | |
| export type AiModuleContext = { | |
| title: string; | |
| learningObjectives: string[]; | |
| topics: string[]; | |
| durationMinutes: number; | |
| }; | |
| export type AiFlowGenerateReq = { | |
| requestId: string; | |
| moduleContext: AiModuleContext; | |
| systemPrompts?: string[] | null; | |
| }; | |
| export type AiLessonStep = { | |
| type: string; | |
| title: string; | |
| estimated_duration: number; | |
| ai_understanding: string; | |
| }; | |
| export type AiFlowGenerateResp = { | |
| data: { steps: AiLessonStep[] }; | |
| meta: AiMeta; | |
| }; | |
| export async function apiAiFlowGenerate( | |
| payload: AiFlowGenerateReq | |
| ): Promise<AiFlowGenerateResp> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout( | |
| `${base}/ai/courseware/flow/generate`, | |
| { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload), | |
| }, | |
| TEACHER_TIMEOUT_MS | |
| ); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, "AI flow generate failed")); | |
| return data as AiFlowGenerateResp; | |
| } | |
| // 2.3 Regenerate Partial Flow | |
| export type AiSimpleStep = { | |
| id: string; | |
| title: string; | |
| duration: number; | |
| }; | |
| export type AiCurrentFlow = { | |
| lockedSteps: AiSimpleStep[]; | |
| unlockedSteps: AiSimpleStep[]; | |
| }; | |
| export type AiFlowPartialReq = { | |
| requestId: string; | |
| prompt: string; | |
| currentFlow: AiCurrentFlow; | |
| }; | |
| export type AiCopilotProposedStep = { | |
| type: string; | |
| title: string; | |
| estimated_duration: number; | |
| ai_understanding: string; | |
| }; | |
| export type AiFlowPartialResp = { | |
| data: { | |
| explanation: string; | |
| proposedSteps: AiCopilotProposedStep[]; | |
| }; | |
| meta: AiMeta; | |
| }; | |
| export async function apiAiFlowRegeneratePartial( | |
| payload: AiFlowPartialReq | |
| ): Promise<AiFlowPartialResp> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout( | |
| `${base}/ai/courseware/flow/regenerate-partial`, | |
| { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload), | |
| }, | |
| TEACHER_TIMEOUT_MS | |
| ); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, "AI flow partial regenerate failed")); | |
| return data as AiFlowPartialResp; | |
| } | |
| // 2.4 Generate/Polish Lesson Plan Detail | |
| export type AiPlanDetailReq = { | |
| requestId: string; | |
| finalizedSteps: any[]; | |
| }; | |
| export type AiLessonSection = { | |
| section_id: string; | |
| type: string; | |
| content: string; | |
| }; | |
| export type AiPlanDetailResp = { | |
| data: { sections: AiLessonSection[] }; | |
| meta: AiMeta; | |
| }; | |
| export async function apiAiPlanDetailGenerate( | |
| payload: AiPlanDetailReq | |
| ): Promise<AiPlanDetailResp> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout( | |
| `${base}/ai/courseware/plan/detail/generate`, | |
| { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload), | |
| }, | |
| TEACHER_TIMEOUT_MS | |
| ); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, "AI plan detail generate failed")); | |
| return data as AiPlanDetailResp; | |
| } | |
| // 2.5 Generate Reflection Report | |
| export type AiTeachAnnotation = { | |
| category: string; | |
| selectedText?: string | null; | |
| feedback?: string | null; | |
| }; | |
| export type AiQuizAggregations = { | |
| averageScore?: number | null; | |
| lowestTopic?: string | null; | |
| }; | |
| export type AiReflectionReq = { | |
| requestId: string; | |
| teachAnnotations: AiTeachAnnotation[]; | |
| quizAggregations?: AiQuizAggregations | null; | |
| }; | |
| export type AiReflectionUnderstanding = { | |
| status: string; | |
| summary: string; | |
| bulletPoints?: string[] | null; | |
| }; | |
| export type AiReflectionEngagement = { | |
| status: string; | |
| summary?: string | null; | |
| }; | |
| export type AiReflectionDifficulty = { | |
| status: string; | |
| challengingTopics?: any[] | null; | |
| }; | |
| export type AiReflectionMisconception = { | |
| status: string; | |
| issues?: any[] | null; | |
| }; | |
| export type AiNextLessonSuggestion = { | |
| actionText: string; | |
| deepLinkType?: string | null; | |
| }; | |
| export type AiReflectionResp = { | |
| data: { | |
| understanding: AiReflectionUnderstanding; | |
| engagement: AiReflectionEngagement; | |
| difficulty: AiReflectionDifficulty; | |
| misconceptions: AiReflectionMisconception; | |
| nextLessonSuggestions: AiNextLessonSuggestion[]; | |
| }; | |
| meta: AiMeta; | |
| }; | |
| export async function apiAiReflectionGenerate( | |
| payload: AiReflectionReq | |
| ): Promise<AiReflectionResp> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout( | |
| `${base}/ai/courseware/reflection/generate`, | |
| { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload), | |
| }, | |
| TEACHER_TIMEOUT_MS | |
| ); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, "AI reflection generate failed")); | |
| return data as AiReflectionResp; | |
| } | |
| // 2.6 Generate Improvement Proposals | |
| export type AiImprovementReq = { | |
| requestId: string; | |
| reflectionReports: any[]; | |
| }; | |
| export type AiImprovementProposal = { | |
| title: string; | |
| priority: string; | |
| affectedWeeks?: string | null; | |
| evidence?: string | null; | |
| rootCause?: string | null; | |
| proposedSolution?: string | null; | |
| expectedImpact?: string | null; | |
| }; | |
| export type AiImprovementResp = { | |
| data: { proposals: AiImprovementProposal[] }; | |
| meta: AiMeta; | |
| }; | |
| export async function apiAiImprovementGenerate( | |
| payload: AiImprovementReq | |
| ): Promise<AiImprovementResp> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout( | |
| `${base}/ai/courseware/improvement/generate`, | |
| { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload), | |
| }, | |
| TEACHER_TIMEOUT_MS | |
| ); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, "AI improvement generate failed")); | |
| return data as AiImprovementResp; | |
| } | |