ClassLens / chatkit /frontend /src /components /step2 /DataPreviewModal.tsx
Yu Chen
rebuild the pdf parsing logic
8d3145b
Raw
History Blame
5.04 kB
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"
>
&times;
</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>
);
}