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"
          >
            &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>
  );
}