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

View File

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

View File

@@ -12,6 +12,7 @@ import {
Code,
Clock,
HardDrive,
GitCompare,
} from "lucide-react";
import Link from "next/link";
import type { QuestionDetail as QuestionDetailType } from "@/types";
@@ -216,6 +217,14 @@ export function QuestionDetail({ question }: QuestionDetailProps) {
</ul>
</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,
ClipboardList,
Brain,
GitCompare,
type LucideIcon,
} from "lucide-react";
type CalloutVariant = "warning" | "success" | "info" | "insight";
type CalloutVariant = "warning" | "success" | "info" | "insight" | "pattern";
interface CalloutProps {
children: React.ReactNode;
@@ -45,6 +46,12 @@ const variantConfig: Record<
bgClass: "bg-[var(--callout-insight-bg)]",
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({