feat(content): pattern comparisons

This commit is contained in:
2025-09-10 18:46:50 +01:00
parent 0c43a4a95d
commit 5f223b3ce2
5 changed files with 70 additions and 2 deletions

View File

@@ -0,0 +1,25 @@
"""add pattern_comparison to explanations
Revision ID: 007
Revises: 006
Create Date: 2025-07-05
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "007"
down_revision: str | None = "006"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.add_column("explanations", sa.Column("pattern_comparison", sa.Text(), nullable=True))
def downgrade() -> None:
op.drop_column("explanations", "pattern_comparison")

View File

@@ -236,6 +236,7 @@ async def load_question(
explanation.time_complexity = exp_data["time_complexity"] explanation.time_complexity = exp_data["time_complexity"]
explanation.space_complexity = exp_data["space_complexity"] explanation.space_complexity = exp_data["space_complexity"]
explanation.complexity_explanation = exp_data.get("complexity_explanation") explanation.complexity_explanation = exp_data.get("complexity_explanation")
explanation.pattern_comparison = exp_data.get("pattern_comparison")
else: else:
explanation = Explanation( explanation = Explanation(
question_id=question.id, question_id=question.id,
@@ -246,6 +247,7 @@ async def load_question(
time_complexity=exp_data["time_complexity"], time_complexity=exp_data["time_complexity"],
space_complexity=exp_data["space_complexity"], space_complexity=exp_data["space_complexity"],
complexity_explanation=exp_data.get("complexity_explanation"), complexity_explanation=exp_data.get("complexity_explanation"),
pattern_comparison=exp_data.get("pattern_comparison"),
) )
session.add(explanation) session.add(explanation)

View File

@@ -4,6 +4,7 @@ import { useState, useCallback, useMemo, useEffect } from "react";
import { CodeEditor } from "./code-editor"; import { CodeEditor } from "./code-editor";
import { TestResults } from "./test-results"; import { TestResults } from "./test-results";
import { usePyodide } from "@/hooks/use-pyodide"; import { usePyodide } from "@/hooks/use-pyodide";
import { useTimeTracker, getTimeTrackerElapsed } from "@/hooks/use-time-tracker";
import { submitSolution } from "@/lib/api"; import { submitSolution } from "@/lib/api";
import { markQuestionCompleted, getSavedSolution, isQuestionCompleted } from "@/lib/progress"; import { markQuestionCompleted, getSavedSolution, isQuestionCompleted } from "@/lib/progress";
import type { QuestionDetail, TestResult, HiddenTestOutput } from "@/types"; import type { QuestionDetail, TestResult, HiddenTestOutput } from "@/types";
@@ -45,6 +46,7 @@ import {
ChevronRight, ChevronRight,
CheckCircle, CheckCircle,
XCircle, XCircle,
GitCompare,
} from "lucide-react"; } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
@@ -115,6 +117,9 @@ type RightTab = "code" | "results";
type ViewMode = "split" | "left" | "right"; type ViewMode = "split" | "left" | "right";
export function ProblemWorkspace({ question }: ProblemWorkspaceProps) { export function ProblemWorkspace({ question }: ProblemWorkspaceProps) {
// Start time tracking for this question
useTimeTracker(question.slug);
const starterCode = useMemo( const starterCode = useMemo(
() => generateStarterCode(question.function_signature!), () => generateStarterCode(question.function_signature!),
[question.function_signature] [question.function_signature]
@@ -462,7 +467,14 @@ json.dumps(__result)
const allVisiblePassed = visibleTestResults.every((r) => r.passed); const allVisiblePassed = visibleTestResults.every((r) => r.passed);
const allHiddenPassed = response.total_passed === response.total_tests; const allHiddenPassed = response.total_passed === response.total_tests;
if (allVisiblePassed && allHiddenPassed) { if (allVisiblePassed && allHiddenPassed) {
markQuestionCompleted(question.slug, code); const timeSpentMs = getTimeTrackerElapsed();
const primaryPattern = question.patterns[0]?.slug || "";
markQuestionCompleted(question.slug, {
primaryPattern,
difficulty: question.difficulty,
code,
timeSpentMs,
});
setIsCompleted(true); setIsCompleted(true);
} }
@@ -480,6 +492,8 @@ json.dumps(__result)
hiddenTestInputs, hiddenTestInputs,
runTests, runTests,
question.slug, question.slug,
question.difficulty,
question.patterns,
code, code,
analyzeComplexity, analyzeComplexity,
]); ]);
@@ -719,6 +733,17 @@ json.dumps(__result)
</Callout> </Callout>
)} )}
{/* Why This Pattern? */}
{question.explanation?.pattern_comparison && (
<Callout
variant="pattern"
icon={GitCompare}
title="Why This Pattern?"
>
<Markdown>{question.explanation.pattern_comparison}</Markdown>
</Callout>
)}
{/* Complexity Analysis */} {/* Complexity Analysis */}
{(question.explanation?.time_complexity || {(question.explanation?.time_complexity ||
question.explanation?.space_complexity) && ( question.explanation?.space_complexity) && (

View File

@@ -12,6 +12,7 @@ import {
Code, Code,
Clock, Clock,
HardDrive, HardDrive,
GitCompare,
} from "lucide-react"; } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import type { QuestionDetail as QuestionDetailType } from "@/types"; import type { QuestionDetail as QuestionDetailType } from "@/types";
@@ -216,6 +217,14 @@ export function QuestionDetail({ question }: QuestionDetailProps) {
</ul> </ul>
</Callout> </Callout>
)} )}
{question.explanation.pattern_comparison && (
<Callout variant="pattern" icon={GitCompare} title="Why This Pattern?">
<div className="prose-content">
<Markdown>{question.explanation.pattern_comparison}</Markdown>
</div>
</Callout>
)}
</> </>
)} )}

View File

@@ -4,10 +4,11 @@ import {
Lightbulb, Lightbulb,
ClipboardList, ClipboardList,
Brain, Brain,
GitCompare,
type LucideIcon, type LucideIcon,
} from "lucide-react"; } from "lucide-react";
type CalloutVariant = "warning" | "success" | "info" | "insight"; type CalloutVariant = "warning" | "success" | "info" | "insight" | "pattern";
interface CalloutProps { interface CalloutProps {
children: React.ReactNode; children: React.ReactNode;
@@ -45,6 +46,12 @@ const variantConfig: Record<
bgClass: "bg-[var(--callout-insight-bg)]", bgClass: "bg-[var(--callout-insight-bg)]",
iconClass: "text-[var(--callout-insight-fg)]", iconClass: "text-[var(--callout-insight-fg)]",
}, },
pattern: {
icon: GitCompare,
borderClass: "border-purple-500/50",
bgClass: "bg-purple-500/10",
iconClass: "text-purple-500",
},
}; };
export function Callout({ export function Callout({