Spaces:
Runtime error
Runtime error
File size: 5,037 Bytes
ad5d4b6 5722fbf e1ed51d 5722fbf e1ed51d ad5d4b6 5722fbf e1ed51d 5722fbf e1ed51d ad5d4b6 5722fbf ad5d4b6 5722fbf e1ed51d ad5d4b6 e1ed51d 8d3145b e1ed51d ad5d4b6 e1ed51d ad5d4b6 5722fbf ad5d4b6 5722fbf ad5d4b6 8d3145b ad5d4b6 5722fbf e1ed51d ad5d4b6 e1ed51d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | 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>
);
}
|