"use client"; import { MessageResponse } from "@/components/ai-elements/message"; import { cn } from "@/lib/utils"; import type { ArtifactPartProps } from "@/src/core"; /** * The practice transcript cards (practice sessions, owner design * 2026-10-04): three quiet records in the stream — * * - ProblemCard — a posed problem: the statement, compact-expandable * (like every card here; the dock is the working surface, this is the * record). The commitment NEVER renders — only the call's statement. * - ConcludeCard — the conclusion: outcome badge + verdict + the reveal * (the committed solution, shown when the problem ended). A dismissal * synthesis (outcome:"dismissed") renders as one muted line. * - ThinkingCard — the learner's own submission, user-side: the register * is the two buttons' (Nudge = working trace, Commit = answer). The * UNIFIED ThinkingBlock (owner round 2026-10-05): the quiz quote-block * language in a faint user-side shell, never collapsible. */ import { ThinkingBlock } from "@/components/thinking-block"; /** The practice-thinking marker's details (native-messages.ts). */ export interface PracticeThinkingData { problemCallId: string; /** Legacy records (pre 2026-10-05) carry the old two-lane flag; ignored. */ commit?: boolean; text: string; } export function ProblemCard({ part }: ArtifactPartProps) { // Always open, no chevron (owner round, 2026-10-05): the record IS the // statement — same container + eyebrow discipline as the answered card // and the dock (which mirrors this in Slice D). The commitment never // renders — only the call's statement. const input = (part.input ?? {}) as { statement?: string }; const statement = typeof input.statement === "string" ? input.statement : ""; return (
Problem
{statement}
); } const OUTCOME_LABEL: Record = { solved: "Solved", solvedWithHints: "Solved with hints", movedOn: "Moved on", surrendered: "Surrendered", dismissed: "Dismissed", }; /* The outcome chips (owner round 3, 2026-10-05): the EXACT register of the * quiz cards' correct/incorrect bubbles. Colors per outcome — solved reads * green (the "correct" twin), solved-with-hints amber (succeeded with * cost), surrendered red (the problem beat the learner), moved on muted * (the agent's call, not a verdict against him). */ const OUTCOME_CHIP: Record = { solved: "bg-green-500/15 text-green-500", solvedWithHints: "bg-amber-500/15 text-amber-500", movedOn: "bg-muted text-muted-foreground", surrendered: "bg-red-500/15 text-red-500", dismissed: "bg-muted text-muted-foreground", }; export function ConcludeCard({ part }: ArtifactPartProps) { // The conclusion redesigned (owner round, 2026-10-05): the SOLUTION is // the hero — the modelSolution committed at pose time (part.reveal, the // view-seam join), shown verbatim, never re-authored. The verdict sits // below in the quiz-explanation register (small, muted, low-key). // Nothing collapsible. Legacy inputs (pre-slim records) that carry // their own solutionReveal fall back to it. const input = (part.input ?? {}) as { outcome?: string; verdict?: string; solutionReveal?: string }; const outcome = typeof input.outcome === "string" ? input.outcome : ""; const verdict = typeof input.verdict === "string" ? input.verdict : ""; const reveal = typeof part.reveal === "string" ? part.reveal : typeof input.solutionReveal === "string" ? input.solutionReveal : ""; // The dismissal synthesis: no verdict, no reveal — one muted line (the // learner clicked; the record needs only the fact). if (outcome === "dismissed") { return (
Problem dismissed.
); } return (
{/* The heading row (owner round 3, 2026-10-05): plain 'Concluded' eyebrow + the outcome in the quiz cards' EXACT chip register — no more two same-weight headings stacked. */}
Concluded {(OUTCOME_LABEL[outcome] ?? "") !== "" && outcome !== "dismissed" && ( {OUTCOME_LABEL[outcome]} )}
{/* The verdict — plain prose under the heading, no 'Verdict:' label (owner round 3): the same quiet 50% register as quiz explanations. */} {verdict.length > 0 && (
{verdict}
)} {/* Then the divider, then the labeled model solution (Slice G: the hero is the pose's commitment, verbatim). */} {reveal.length > 0 && ( <>
Model solution
{reveal}
)}
); } export function ThinkingCard({ data }: { data: PracticeThinkingData }) { // The unified row (owner rounds, 2026-10-05): one style, one label — // collapsed arrival, "Your thinking", 50%-italic quote block on expand. // Legacy records' commit flag is deliberately unread (one lane now). return ; }