- {!loading && !explanation && !error && (
-
-
-
- AI Analysis
-
- NIM
-
-
+
+ {/* Top Telemetry: Quality Gauge + Score Radar */}
+
+
+
+
+
+ {/* Tab Controls */}
+
+ {TABS.map((tab) => (
-
- )}
-
- {loading && !explanation && (
-
- )}
-
- {!loading && !explanation && error && (
-
- )}
-
- {explanation && (
-
-
-
-
- AI Analysis
-
- NIM
-
-
- {cached && (
-
-
- cached
-
- )}
-
-
-
-
-
-
-
-
-
-
-
+ ))}
+
+
+ {/* Scrollable Tab Content */}
+
+ {activeTab === "overview" && (
+ <>
+
+
+
+ >
+ )}
+
+ {activeTab === "approach" && (
+ <>
+
+
+ >
+ )}
+
+ {activeTab === "points" && (
-
- )}
-
-
setChatOpen(false)}
- title={solution.problem_title || solution.problem_slug || "Solution"}
- messages={messages}
- loading={chatLoading}
- streaming={streamingAnswer}
- question={question}
- onQuestionChange={setQuestion}
- onSend={sendChat}
- />
+ )}
+
);
}
\ No newline at end of file
diff --git a/frontend/components/best-practices/QualityGauge.tsx b/frontend/components/best-practices/QualityGauge.tsx
new file mode 100644
index 0000000..c49de72
--- /dev/null
+++ b/frontend/components/best-practices/QualityGauge.tsx
@@ -0,0 +1,132 @@
+"use client";
+
+import { Star } from "lucide-react";
+import { cn } from "@/lib/utils";
+
+const SIZE = 110;
+const STROKE = 8;
+const RADIUS = (SIZE - STROKE) / 2;
+const CIRCUMFERENCE = 2 * Math.PI * RADIUS;
+
+export function gradeScore(score: number): { label: string; color: string } {
+ if (score >= 85) return { label: "Excellent", color: "#10b981" };
+ if (score >= 70) return { label: "Good", color: "#14b8a6" };
+ if (score >= 50) return { label: "Fair", color: "#f59e0b" };
+ return { label: "Needs work", color: "#f43f5e" };
+}
+
+function Stars({ value }: { value: number }) {
+ return (
+
+ {Array.from({ length: 5 }).map((_, i) => {
+ const fill = Math.max(0, Math.min(1, value - i));
+ return (
+
+
+
+
+
+
+ );
+ })}
+
+ );
+}
+
+export function QualityGauge({ score }: { score: number }) {
+ const grade = gradeScore(score);
+ const stars = (score / 100) * 5;
+
+ return (
+
+
+
+
+
+ {score}
+
+
+ {grade.label}
+
+
+
+
+
+
+
+ Quality Score
+
+
+
+ );
+}
+
+export function QualityGaugeSkeleton({ className }: { className?: string }) {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/components/best-practices/ScoreRadar.tsx b/frontend/components/best-practices/ScoreRadar.tsx
new file mode 100644
index 0000000..a6e04f1
--- /dev/null
+++ b/frontend/components/best-practices/ScoreRadar.tsx
@@ -0,0 +1,55 @@
+"use client";
+
+import {
+ PolarAngleAxis,
+ PolarGrid,
+ Radar,
+ RadarChart,
+ ResponsiveContainer,
+} from "recharts";
+
+export function ScoreRadar({
+ efficiency,
+ readability,
+ correctness,
+ bestPractices,
+}: {
+ efficiency: number;
+ readability: number;
+ correctness: number;
+ bestPractices: number;
+}) {
+ const data = [
+ { subject: "Efficiency", score: efficiency },
+ { subject: "Readability", score: readability },
+ { subject: "Correctness", score: correctness },
+ { subject: "Best practices", score: bestPractices },
+ ];
+
+ return (
+
+
+ Skill Breakdown
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/components/best-practices/SolutionCard.tsx b/frontend/components/best-practices/SolutionCard.tsx
index 539ea5f..5fcbb99 100644
--- a/frontend/components/best-practices/SolutionCard.tsx
+++ b/frontend/components/best-practices/SolutionCard.tsx
@@ -6,7 +6,7 @@ import { Check, ChevronDown, Copy, Zap } from "lucide-react";
import { CommunitySolution } from "@/lib/types";
import { cn, formatRelativeTime } from "@/lib/utils";
import { CodeSnippet } from "@/components/application/code-snippet";
-import { ExplainPanel } from "./ExplainPanel";
+import { AnalysisModal } from "./AnalysisModal";
import {
AIAnalysisPill,
LikeButton,
@@ -44,7 +44,7 @@ export function SolutionCard({
onLike: (id: string, currentlyLiked: boolean) => void;
}) {
const [expanded, setExpanded] = useState(false);
- const [aiRequested, setAiRequested] = useState(false);
+ const [analysisOpen, setAnalysisOpen] = useState(false);
const [copied, setCopied] = useState(false);
const copyTimer = useRef
| null>(null);
@@ -55,11 +55,6 @@ export function SolutionCard({
[],
);
- const expandWithAI = () => {
- setAiRequested(true);
- setExpanded(true);
- };
-
const toggleExpanded = () => setExpanded((v) => !v);
const copyCode = async () => {
@@ -120,7 +115,7 @@ export function SolutionCard({
)}
{!expanded && (
-
+ setAnalysisOpen(true)} />
)}
@@ -229,12 +224,17 @@ export function SolutionCard({
}}
/>