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.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)
|
||||||
|
|
||||||
|
|||||||
@@ -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) && (
|
||||||
|
|||||||
@@ -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>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
Reference in New Issue
Block a user