feat(content): pattern comparisons
This commit is contained in:
25
backend/alembic/versions/007_pattern_comparison.py
Normal file
25
backend/alembic/versions/007_pattern_comparison.py
Normal 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")
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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) && (
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user