Spaces:
Sleeping
Sleeping
| import { useEffect, useState } from "react"; | |
| import { apiPost } from "../../lib/api"; | |
| interface DataPreviewModalProps { | |
| onGenerate: () => void; | |
| onClose: () => void; | |
| isGenerating: boolean; | |
| sessionId: number; | |
| selectedIndices: number[]; | |
| } | |
| interface PreviewResponse { | |
| student_name: string; | |
| total_questions: number; | |
| wrong_count: number; | |
| markdown_prompt: string; | |
| } | |
| export function DataPreviewModal({ | |
| onGenerate, | |
| onClose, | |
| isGenerating, | |
| sessionId, | |
| selectedIndices, | |
| }: DataPreviewModalProps) { | |
| const [previewIdx, setPreviewIdx] = useState<number>( | |
| selectedIndices.length > 0 ? selectedIndices[0] : 0 | |
| ); | |
| const [loading, setLoading] = useState(false); | |
| const [preview, setPreview] = useState<PreviewResponse | null>(null); | |
| const [error, setError] = useState(""); | |
| useEffect(() => { | |
| setLoading(true); | |
| setError(""); | |
| setPreview(null); | |
| apiPost<PreviewResponse>( | |
| `/api/sessions/${sessionId}/preview-student-prompt`, | |
| { student_index: previewIdx } | |
| ) | |
| .then(setPreview) | |
| .catch((e) => | |
| setError(e instanceof Error ? e.message : "Failed to load preview") | |
| ) | |
| .finally(() => setLoading(false)); | |
| }, [previewIdx, sessionId]); | |
| return ( | |
| <div className="fixed inset-0 z-50 flex items-center justify-center p-4"> | |
| <div | |
| className="absolute inset-0 bg-black/50 backdrop-blur-sm" | |
| onClick={onClose} | |
| /> | |
| <div className="relative w-full max-w-6xl h-[92vh] flex flex-col card overflow-hidden"> | |
| <div className="flex items-center justify-between p-5 border-b border-[var(--color-border)]"> | |
| <h3 className="font-display text-xl font-bold text-[var(--color-text)]"> | |
| 🔍 最終提示詞預覽 | |
| </h3> | |
| <button | |
| onClick={onClose} | |
| className="w-8 h-8 flex items-center justify-center rounded-lg text-[var(--color-text-muted)] hover:text-[var(--color-text)] hover:bg-[var(--color-border)]/50 transition-colors text-lg" | |
| > | |
| × | |
| </button> | |
| </div> | |
| <div className="flex-1 overflow-hidden flex flex-col p-5 min-h-0"> | |
| <div className="flex items-center gap-3 mb-3 flex-wrap"> | |
| <label className="text-sm font-semibold text-[var(--color-text)]"> | |
| 預覽學生: | |
| </label> | |
| <select | |
| value={previewIdx} | |
| onChange={(e) => setPreviewIdx(Number(e.target.value))} | |
| className="input !w-auto !py-1.5 text-sm" | |
| > | |
| {selectedIndices.map((idx) => ( | |
| <option key={idx} value={idx}> | |
| Student {idx + 1} | |
| </option> | |
| ))} | |
| </select> | |
| {preview && ( | |
| <> | |
| <span className="badge badge-success text-xs"> | |
| {preview.student_name} | |
| </span> | |
| <span className="badge badge-success text-xs"> | |
| 總題數 {preview.total_questions} | |
| </span> | |
| <span | |
| className={`badge text-xs ${ | |
| preview.wrong_count === 0 ? "badge-success" : "bg-amber-500/20 text-amber-400" | |
| }`} | |
| > | |
| 錯 {preview.wrong_count} | |
| </span> | |
| </> | |
| )} | |
| </div> | |
| <p className="text-xs text-[var(--color-text-muted)] mb-3"> | |
| 以下是實際送給 LLM 的完整提示詞(僅含該學生答錯的題目) | |
| </p> | |
| {loading ? ( | |
| <div className="flex-1 flex items-center justify-center"> | |
| <div className="w-6 h-6 border-2 border-[var(--color-primary)] border-t-transparent rounded-full animate-spin" /> | |
| </div> | |
| ) : error ? ( | |
| <div className="flex-1 px-3 py-2 rounded-lg bg-red-500/10 text-red-400 text-sm"> | |
| {error} | |
| </div> | |
| ) : ( | |
| <textarea | |
| value={preview?.markdown_prompt ?? ""} | |
| readOnly | |
| className="input !rounded-xl font-mono text-sm leading-relaxed resize-none flex-1 min-h-0" | |
| spellCheck={false} | |
| /> | |
| )} | |
| </div> | |
| <div className="flex items-center justify-end gap-3 p-5 border-t border-[var(--color-border)]"> | |
| <button onClick={onClose} className="btn btn-outline"> | |
| 返回編輯 | |
| </button> | |
| <button | |
| onClick={onGenerate} | |
| disabled={isGenerating} | |
| className="btn btn-accent text-lg px-8 py-3 disabled:opacity-50 disabled:cursor-not-allowed" | |
| > | |
| {isGenerating ? ( | |
| <span className="flex items-center gap-2"> | |
| <span className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" /> | |
| 生成報告中... | |
| </span> | |
| ) : ( | |
| "生成報告" | |
| )} | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |