DocDoeAI / src /components /study-chat /AnswerCorrectionForm.tsx
asnannp's picture
deploy: sync backend to Space root (learn-lesson HF cache fix)
65d68f5
Raw History Blame Contribute Delete
6.44 kB
"use client";
import { useState } from "react";
import { ClipboardCheck, LoaderCircle, Send } from "lucide-react";
import { generateBrainAnswerCorrection } from "@/lib/api/brain";
import type { BrainAnswerCorrectionOutput, BrainGenerationResponse } from "@/lib/api/types";
import type { ApiDocument } from "@/lib/api/types";
type AnswerCorrectionFormProps = {
selectedDocuments: ApiDocument[];
onSubmit: (result: BrainGenerationResponse<BrainAnswerCorrectionOutput>, question: string, studentAnswer: string) => void;
onCancel: () => void;
};
export function AnswerCorrectionForm({
selectedDocuments,
onSubmit,
onCancel,
}: AnswerCorrectionFormProps) {
const [question, setQuestion] = useState("");
const [studentAnswer, setStudentAnswer] = useState("");
const [marks, setMarks] = useState<string>("not_sure");
const [subject, setSubject] = useState(selectedDocuments[0]?.subject ?? "");
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
const canSubmit = question.trim().length > 0 && studentAnswer.trim().length > 0 && !isSubmitting;
async function handleSubmit() {
if (!canSubmit) return;
setIsSubmitting(true);
setError(null);
try {
const sourceIds = selectedDocuments.filter((d) => d.status === "ready").map((d) => d.id);
const result = await generateBrainAnswerCorrection({
topic: question,
subject: subject || undefined,
source_id: sourceIds[0] || undefined,
source_ids: sourceIds.length > 0 ? sourceIds : undefined,
language_preference: "English",
options: {
mode: "answer_correction",
question,
student_answer: studentAnswer,
marks,
subject: subject || undefined,
},
});
onSubmit(result, question, studentAnswer);
} catch (err) {
setError(err instanceof Error ? err.message : "Correction failed. Please try again.");
setIsSubmitting(false);
}
}
return (
<div className="animate-msg-assistant w-full rounded-xl border border-white/[0.06] bg-[#080809] p-5">
<div className="mb-4 flex items-center gap-2.5">
<ClipboardCheck className="size-4 shrink-0 text-emerald-400/70" />
<div>
<span className="text-[10px] font-medium uppercase tracking-[0.08em] text-white/22">Answer Correction</span>
<p className="text-[14px] font-semibold text-white/90">Find marks lost and rewrite in board format</p>
</div>
</div>
{selectedDocuments.length > 0 && (
<p className="mb-3 text-[11px] text-white/30">
Using: {selectedDocuments.map((d) => d.title).join(", ")}
</p>
)}
<div className="grid gap-3">
<label className="grid gap-1.5">
<span className="text-[10px] font-bold uppercase tracking-[0.12em] text-white/30">Question</span>
<textarea
value={question}
onChange={(e) => setQuestion(e.target.value)}
rows={2}
placeholder="Paste the exam question"
className="min-h-16 resize-none rounded-xl border border-white/[0.07] bg-black/30 px-3 py-2.5 text-[13px] leading-5 text-white/80 outline-none placeholder:text-white/25 focus:border-emerald-400/30"
/>
</label>
<label className="grid gap-1.5">
<span className="text-[10px] font-bold uppercase tracking-[0.12em] text-white/30">Your answer</span>
<textarea
value={studentAnswer}
onChange={(e) => setStudentAnswer(e.target.value)}
rows={4}
placeholder="Paste what you wrote"
className="min-h-24 resize-none rounded-xl border border-white/[0.07] bg-black/30 px-3 py-2.5 text-[13px] leading-5 text-white/80 outline-none placeholder:text-white/25 focus:border-emerald-400/30"
/>
</label>
<div className="grid gap-2 sm:grid-cols-[minmax(0,1fr)_120px]">
<label className="grid gap-1.5">
<span className="text-[10px] font-bold uppercase tracking-[0.12em] text-white/30">Subject or chapter</span>
<input
value={subject}
onChange={(e) => setSubject(e.target.value)}
placeholder="Optional"
className="h-10 rounded-xl border border-white/[0.07] bg-black/30 px-3 text-[13px] text-white/80 outline-none placeholder:text-white/25 focus:border-emerald-400/30"
/>
</label>
<label className="grid gap-1.5">
<span className="text-[10px] font-bold uppercase tracking-[0.12em] text-white/30">Marks</span>
<select
value={marks}
onChange={(e) => setMarks(e.target.value)}
className="h-10 rounded-xl border border-white/[0.07] bg-black/30 px-3 text-[13px] text-white/80 outline-none focus:border-emerald-400/30"
>
<option value="not_sure">Not sure</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
</select>
</label>
</div>
</div>
{error && (
<p className="mt-2 text-[12px] text-red-400/80">{error}</p>
)}
<div className="mt-4 flex items-center gap-2">
<button
type="button"
onClick={handleSubmit}
disabled={!canSubmit}
className="inline-flex h-9 items-center justify-center gap-2 rounded-xl bg-emerald-600/90 px-4 text-[12px] font-bold text-white shadow-[0_0_16px_rgba(16,185,129,0.15)] transition hover:bg-emerald-500 active:scale-95 disabled:cursor-not-allowed disabled:opacity-40"
>
{isSubmitting ? (
<LoaderCircle className="size-3.5 animate-spin" />
) : (
<Send className="size-3.5" />
)}
{isSubmitting ? "Correcting..." : "Correct answer"}
</button>
<button
type="button"
onClick={onCancel}
className="inline-flex h-9 items-center justify-center rounded-xl border border-white/[0.08] px-4 text-[12px] font-medium text-white/50 transition hover:border-white/20 hover:text-white/80"
>
Cancel
</button>
</div>
</div>
);
}