"use client"; import Link from "next/link"; import { useParams } from "next/navigation"; import { useEffect, useMemo, useState } from "react"; import { CodeEditor } from "@/components/code-editor"; import { MarkdownRenderer } from "@/components/markdown-renderer"; import { apiFetch } from "@/lib/api"; import { readToken } from "@/lib/auth"; type Problem = { id: number; title: string; statement_md: string; difficulty: number; source: string; statement_url: string; llm_profile_json: string; sample_input: string; sample_output: string; }; type LlmProfile = { title?: string; difficulty?: number; answer?: string; explanation?: string; knowledge_points?: string[]; tags?: string[]; statement_summary_md?: string; }; type Submission = { id: number; status: string; score: number; compile_log: string; runtime_log: string; created_at: number; }; type RunResult = { status: string; time_ms: number; stdout: string; stderr: string; compile_log: string; }; type DraftResp = { language: string; code: string; stdin: string; updated_at: number; }; type SolutionItem = { id: number; problem_id: number; variant: number; title: string; idea_md: string; explanation_md: string; code_cpp: string; complexity: string; tags_json: string; source: string; created_at: number; updated_at: number; }; type SolutionJob = { id: number; problem_id: number; status: string; progress: number; message: string; created_at: number; started_at: number | null; finished_at: number | null; }; type SolutionResp = { items: SolutionItem[]; latest_job: SolutionJob | null; runner_running: boolean; }; const starterCode = `#include using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); return 0; } `; const defaultRunInput = `1 2\n`; export default function ProblemDetailPage() { const params = useParams<{ id: string }>(); const id = useMemo(() => Number(params.id), [params.id]); const [problem, setProblem] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); const [code, setCode] = useState(starterCode); const [runInput, setRunInput] = useState(defaultRunInput); const [contestId, setContestId] = useState(""); const [submitLoading, setSubmitLoading] = useState(false); const [runLoading, setRunLoading] = useState(false); const [draftLoading, setDraftLoading] = useState(false); const [submitResp, setSubmitResp] = useState(null); const [runResp, setRunResp] = useState(null); const [draftMsg, setDraftMsg] = useState(""); const [showSolutions, setShowSolutions] = useState(false); const [solutionLoading, setSolutionLoading] = useState(false); const [solutionData, setSolutionData] = useState(null); const [solutionMsg, setSolutionMsg] = useState(""); const llmProfile = useMemo(() => { if (!problem?.llm_profile_json) return null; try { const parsed = JSON.parse(problem.llm_profile_json); return typeof parsed === "object" && parsed !== null ? (parsed as LlmProfile) : null; } catch { return null; } }, [problem?.llm_profile_json]); useEffect(() => { const load = async () => { if (!Number.isFinite(id) || id <= 0) return; setLoading(true); setError(""); try { const data = await apiFetch(`/api/v1/problems/${id}`); setProblem(data); } catch (e: unknown) { setError(String(e)); } finally { setLoading(false); } }; void load(); }, [id]); useEffect(() => { const loadDraft = async () => { if (!Number.isFinite(id) || id <= 0) return; const token = readToken(); if (!token) return; try { const draft = await apiFetch(`/api/v1/problems/${id}/draft`, undefined, token); if (draft.code) setCode(draft.code); if (draft.stdin) setRunInput(draft.stdin); setDraftMsg("已自动加载草稿"); } catch { // ignore empty draft / unauthorized } }; void loadDraft(); }, [id]); const submit = async () => { setSubmitLoading(true); setSubmitResp(null); setError(""); try { const token = readToken(); if (!token) throw new Error("请先登录后再提交评测"); const body: Record = { language: "cpp", code, }; if (contestId) body.contest_id = Number(contestId); const resp = await apiFetch( `/api/v1/problems/${id}/submit`, { method: "POST", body: JSON.stringify(body), }, token ); setSubmitResp(resp); } catch (e: unknown) { setError(String(e)); } finally { setSubmitLoading(false); } }; const runCode = async () => { setRunLoading(true); setRunResp(null); setError(""); try { const resp = await apiFetch("/api/v1/run/cpp", { method: "POST", body: JSON.stringify({ code, input: runInput }), }); setRunResp(resp); } catch (e: unknown) { setError(String(e)); } finally { setRunLoading(false); } }; const saveDraft = async () => { setDraftLoading(true); setDraftMsg(""); setError(""); try { const token = readToken(); if (!token) throw new Error("请先登录后再保存草稿"); await apiFetch<{ saved: boolean }>( `/api/v1/problems/${id}/draft`, { method: "PUT", body: JSON.stringify({ language: "cpp", code, stdin: runInput }), }, token ); setDraftMsg("草稿已保存"); } catch (e: unknown) { setError(String(e)); } finally { setDraftLoading(false); } }; const loadSolutions = async () => { setSolutionLoading(true); setSolutionMsg(""); try { const resp = await apiFetch(`/api/v1/problems/${id}/solutions`); setSolutionData(resp); } catch (e: unknown) { setSolutionMsg(`加载题解失败:${String(e)}`); } finally { setSolutionLoading(false); } }; const triggerSolutions = async () => { setSolutionLoading(true); setSolutionMsg(""); try { const token = readToken(); if (!token) throw new Error("请先登录后再触发题解生成"); await apiFetch<{ started: boolean; job_id: number }>( `/api/v1/problems/${id}/solutions/generate`, { method: "POST", body: JSON.stringify({ max_solutions: 3 }), }, token ); setSolutionMsg("题解生成任务已提交,后台异步处理中..."); await loadSolutions(); } catch (e: unknown) { setSolutionMsg(`提交失败:${String(e)}`); } finally { setSolutionLoading(false); } }; useEffect(() => { if (!showSolutions) return; void loadSolutions(); const timer = setInterval(() => { void loadSolutions(); }, 5000); return () => clearInterval(timer); // eslint-disable-next-line react-hooks/exhaustive-deps }, [showSolutions, id]); return (

题目详情与评测

{loading &&

加载中...

} {error &&

{error}

} {problem && (

{problem.title}

难度 {problem.difficulty} · 来源 {problem.source}

{problem.statement_url && (

查看原始题面链接

)}
{llmProfile?.knowledge_points && llmProfile.knowledge_points.length > 0 && ( <>

知识点考查

{llmProfile.knowledge_points.map((kp) => ( {kp} ))}
)}

样例输入

{problem.sample_input}

样例输出

{problem.sample_output}
setContestId(e.target.value)} />
{draftMsg &&

{draftMsg}

}