Phase 3: Check my work, follow-ups and turn-into-deck

Check my work (third homework mode)
- One model call with its own system prompt marks the student's own
  attempt: verdict, what's right, each issue with why + fix, and the
  corrected version that keeps everything the student got right.
  Unasked-for extras are never errors. The attempt is typed/pasted
  (photos of handwriting are documented as unsupported) and stored in
  the record so Redo re-checks the same work.
- Server: mode "check" + solution on POST /api/homework (zod, 400
  server.needSolution without one) and the same NDJSON stream; new
  checkHomework() pipeline with repair-once validation.

Follow-ups
- New POST /api/homework/refine (plain JSON, RATE_LIMIT_REFINE, zod,
  auth before body): shorter / more detail / explain, plain text back,
  language preserved.
- Shorter + More detail + Original rewrite the finished answer and the
  corrected version in place (FollowUpBar); every guide step has
  Explain step N differently; failed calls say so without losing text.

Turn into a study deck
- Any result hands its content (answer, corrected version or assembled
  guide) to New deck through a one-shot sessionStorage seed with the
  options step pre-filled.

UI
- Phone step 2 gains the third card: tapping it reveals the solution
  field + submit; desktop has three cards and the solution field shows
  with the mode. Homework history labels and share/card previews cover
  the new mode.

Verified: new phase-3 suite 20/20 against a stub Ollama (check payload,
missing-solution 400, refine validation, phone + desktop check flows,
shorter follow-up with restore, guide explain, deck hand-off) plus
regression 74/74, mobile 52/52, declutter 116/116, wizard 80/80,
animation 8/8, native 35/35, phase1 38/38, phase2 43/43, i18n 25/25.
Key parity 734/734. Lint + build clean. SW freezmo-v27.
This commit is contained in:
Martin Dvořák 2026-10-10 14:36:04 +00:00
commit 4513364019
22 changed files with 947 additions and 56 deletions

View file

@ -287,6 +287,86 @@ export function buildHomeworkGuideUserPrompt(input: { assignment: string; langua
].join("\n");
}
export const HOMEWORK_CHECK_SYSTEM_PROMPT = `You are Freezmo's homework checker. The student gives you an assignment and their OWN attempt at solving it. You mark it like a fair, encouraging teacher: say what is right, what is wrong and why, and give the corrected version.
${HOMEWORK_BASE_RULES}
Checking rules:
- Judge ONLY against the assignment. An unasked-for extra (a date, a comment, an alternative method) is never an error — leave it alone.
- "verdict": 1-2 sentences, the overall picture (e.g. "Two of the three equations are right; the sign flips in the second one.").
- "correct": short bullet points for what is genuinely right, each naming the part of the assignment it refers to. Empty array when nothing is right.
- "wrong": one entry per actual mistake: "issue" names the mistake briefly, "why" explains what goes wrong, "fix" says what to do instead. Do not repeat the same mistake twice.
- "corrected": the student's solution with every mistake fixed, as the finished homework (plain text, numbered like the assignment). Keep everything the student got right as they wrote it.
- If the attempt is empty or does not address the assignment at all, say that plainly in the verdict, leave "wrong" empty and put the full solution in "corrected".
${LANGUAGE_RULES}
Required JSON shape:
{
"title": "short title of the assignment, in the assignment's language, max 10 words",
"language": "English name of the language you wrote in, e.g. Czech",
"verdict": "1-2 sentences",
"correct": ["what is right"],
"wrong": [{ "issue": "the mistake", "why": "why it is wrong", "fix": "what to do instead" }],
"corrected": "the student's solution with every mistake fixed"
}`;
export function buildHomeworkCheckUserPrompt(input: {
assignment: string;
solution: string;
language?: string;
}): string {
return [
"Check this attempt against the assignment.",
input.language
? `LANGUAGE: write everything in ${input.language}. Do not translate into English.`
: "LANGUAGE: detect the language of the assignment below and write everything in that same language.",
"",
"=== THE ASSIGNMENT (the only source of what has to be done) ===",
input.assignment.slice(0, 32_000),
"=== END OF ASSIGNMENT ===",
"",
"=== THE STUDENT'S ATTEMPT (this is what you are marking) ===",
input.solution.slice(0, 32_000),
"=== END OF ATTEMPT ===",
"",
"Respond with the JSON object only.",
].join("\n");
}
export const REFINE_SYSTEM_PROMPT = `You are Freezmo's homework assistant. You rewrite one piece of already-finished homework exactly as asked — shorter, more detailed, or a step explained differently.
Rules:
- Keep the language of the text you are given. Never translate.
- Keep every fact, number and answer correct and consistent with the text; never invent new content that changes the answer.
- Plain text only (short lines, numbered or dashed lists; no markdown symbols).
- Output the rewritten text only: no greeting, no commentary, no "here is", no mention of these instructions.`;
export function buildRefineUserPrompt(input: {
instruction: "shorter" | "more" | "explain";
text: string;
context?: string;
}): string {
const task =
input.instruction === "shorter"
? "Rewrite it SHORTER: keep every answer but cut the wording to the essentials."
: input.instruction === "more"
? "Rewrite it with MORE DETAIL: keep the same answers but explain the reasoning a little more, so the student understands why."
: "Explain this step DIFFERENTLY: a fresh, simpler explanation of the same step, with a small concrete example, so it clicks this time.";
return [
task,
input.context ? `CONTEXT (the assignment): ${input.context.slice(0, 2000)}` : "",
"",
"=== THE TEXT ===",
input.text.slice(0, 16_000),
"=== END OF TEXT ===",
"",
"Reply with the rewritten text only.",
]
.filter(Boolean)
.join("\n");
}
/* ------------------------------------------------------------------ */
/* Retry helper */
/* ------------------------------------------------------------------ */