import { useState, useEffect, useCallback } from "react"; import { Header } from "./components/Header"; import { InviteGate } from "./components/InviteGate"; import { LoginForm } from "./components/LoginForm"; import { StepIndicator } from "./components/StepIndicator"; import { FileUploadPanel } from "./components/step1/FileUploadPanel"; import { ParsedDataSummary } from "./components/step2/ParsedDataSummary"; import { PromptEditor } from "./components/step2/PromptEditor"; import { ReportViewer } from "./components/step3/ReportViewer"; import { apiGet, apiPost, getToken, clearToken } from "./lib/api"; const INVITE_CODE = "taboola-npo-cz"; interface User { id: number; email: string; display_name: string; } export default function App() { const [invited, setInvited] = useState(() => localStorage.getItem("invite_code") === INVITE_CODE); const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [currentStep, setCurrentStep] = useState<1 | 2 | 3>(1); const [sessionId, setSessionId] = useState(null); const [parsedData, setParsedData] = useState>({}); const [promptContent, setPromptContent] = useState(""); const [reportHtml, setReportHtml] = useState(""); const [isGenerating, setIsGenerating] = useState(false); // Check for saved JWT on mount useEffect(() => { const token = getToken(); if (token) { apiGet("/api/auth/me") .then(setUser) .catch(() => clearToken()) .finally(() => setLoading(false)); } else { setLoading(false); } }, []); // Load default prompt useEffect(() => { if (user && !promptContent) { apiGet<{ content: string }>("/api/prompts/default") .then((res) => setPromptContent(res.content)) .catch(() => {}); } }, [user]); // Create session when user is authenticated and no session exists useEffect(() => { if (user && !sessionId) { apiPost<{ id: number }>("/api/sessions", { title: "New Session" }) .then((session) => setSessionId(session.id)) .catch(() => {}); } }, [user]); const handleLogout = () => { setUser(null); setSessionId(null); setParsedData({}); setPromptContent(""); setReportHtml(""); setCurrentStep(1); }; const handleParsedDataUpdate = useCallback((dataType: string, data: unknown) => { setParsedData((prev) => ({ ...prev, [dataType]: data })); }, []); const handleGoToStep2 = useCallback(() => { setCurrentStep(2); }, []); const handleRunSummary = useCallback(async (model: string) => { if (!sessionId || !promptContent.trim()) return; setIsGenerating(true); try { const res = await apiPost<{ report_id: number; html_content: string }>( `/api/sessions/${sessionId}/generate-report`, { prompt_content: promptContent, model } ); setReportHtml(res.html_content); setCurrentStep(3); } catch (err) { alert(err instanceof Error ? err.message : "Report generation failed"); } finally { setIsGenerating(false); } }, [sessionId, promptContent]); const handleExportPdf = useCallback(async () => { // Dynamic import to avoid loading html2pdf.js unless needed const html2pdf = (await import("html2pdf.js")).default; const container = document.createElement("div"); container.innerHTML = reportHtml; document.body.appendChild(container); await html2pdf() .set({ margin: 10, filename: "classlens-report.pdf", html2canvas: { scale: 2 } }) .from(container) .save(); document.body.removeChild(container); }, [reportHtml]); const handleExportPng = useCallback(async () => { const html2canvas = (await import("html2canvas")).default; const container = document.createElement("div"); container.innerHTML = reportHtml; container.style.width = "1200px"; container.style.position = "absolute"; container.style.left = "-9999px"; document.body.appendChild(container); const canvas = await html2canvas(container, { scale: 2 }); document.body.removeChild(container); const link = document.createElement("a"); link.download = "classlens-report.png"; link.href = canvas.toDataURL("image/png"); link.click(); }, [reportHtml]); const handleStepClick = (step: 1 | 2 | 3) => { if (step <= currentStep) setCurrentStep(step); }; // Loading state if (loading) { return (
); } // Invite code gate if (!invited) { return ( { localStorage.setItem("invite_code", INVITE_CODE); setInvited(true); }} correctCode={INVITE_CODE} /> ); } // Login gate if (!user) { return ; } return (
{currentStep === 1 && (

上傳考試資料

上傳考試題目、學生答案和標準答案,分別解析後確認資料再前往下一步

{sessionId && ( )}
)} {currentStep === 2 && (

編輯分析提示詞

編輯提示詞來自訂分析報告的內容與格式

{sessionId && ( )}
)} {currentStep === 3 && (

分析報告

setCurrentStep(2)} onExportPdf={handleExportPdf} onExportPng={handleExportPng} />
)}
); }