Skip to content

Commit 632c74c

Browse files
committed
refactor: improve analysis metrics UI
1 parent 052c55a commit 632c74c

File tree

8 files changed

+298
-133
lines changed

8 files changed

+298
-133
lines changed

src/app/page.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ export default function Home(): JSX.Element {
1919
isAnalyzing,
2020
progress,
2121
stage,
22+
message,
2223
logs,
2324
result,
2425
error,
2526
videoTitle,
2627
commentsFound,
2728
commentsAnalyzed,
2829
mlMetrics,
30+
pipelineModels,
2931
startAnalysis,
3032
cancelAnalysis,
3133
reset,
@@ -341,6 +343,7 @@ export default function Home(): JSX.Element {
341343
videoTitle={videoTitle || undefined}
342344
commentsFound={commentsFound || undefined}
343345
commentsAnalyzed={commentsAnalyzed || undefined}
346+
currentMessage={message || undefined}
344347
onCancel={cancelAnalysis}
345348
/>
346349
</div>
@@ -349,12 +352,9 @@ export default function Home(): JSX.Element {
349352
<div className="space-y-4">
350353
<MLInfoPanel
351354
isProcessing={true}
352-
modelName={mlMetrics.modelName}
355+
pipelineModels={pipelineModels}
353356
processingSpeed={mlMetrics.processingSpeed}
354357
tokensProcessed={mlMetrics.tokensProcessed}
355-
avgConfidence={mlMetrics.avgConfidence}
356-
currentBatch={mlMetrics.currentBatch}
357-
totalBatches={mlMetrics.totalBatches}
358358
processingTimeSeconds={mlMetrics.processingTimeSeconds}
359359
/>
360360

src/components/analysis/ml-info-panel.tsx

Lines changed: 156 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,125 @@
11
"use client";
22

3-
import * as React from "react";
4-
import { Cpu, Zap, Clock, Gauge, Database, Brain } from "lucide-react";
5-
import { Progress } from "@/components/ui/progress";
3+
import { Brain, MessageSquare, Tags, Sparkles, CheckCircle2, Loader2, Clock, Zap, Database } from "lucide-react";
4+
import { cn } from "@/lib/utils";
5+
import type { PipelineModels, ModelStage } from "@/types";
66

77
interface MLInfoPanelProps {
88
isProcessing: boolean;
9-
modelName?: string;
9+
pipelineModels: PipelineModels;
1010
processingSpeed?: number;
1111
tokensProcessed?: number;
12-
avgConfidence?: number;
13-
currentBatch?: number;
14-
totalBatches?: number;
1512
processingTimeSeconds?: number;
1613
}
1714

15+
const stageLabels: Record<ModelStage, string> = {
16+
pending: "Waiting",
17+
loading: "Loading",
18+
active: "Processing",
19+
embedding: "Embedding",
20+
clustering: "Clustering",
21+
connecting: "Connecting",
22+
generating: "Generating",
23+
complete: "Complete",
24+
unavailable: "Unavailable",
25+
};
26+
27+
function ModelCard({
28+
icon,
29+
label,
30+
modelName,
31+
stage,
32+
detail,
33+
color,
34+
}: Readonly<{
35+
icon: React.ReactNode;
36+
label: string;
37+
modelName: string;
38+
stage: ModelStage;
39+
detail?: string;
40+
color: string;
41+
}>): JSX.Element {
42+
const isActive = stage !== "pending" && stage !== "complete" && stage !== "unavailable";
43+
const isComplete = stage === "complete";
44+
const isUnavailable = stage === "unavailable";
45+
46+
const renderIcon = (): React.ReactNode => {
47+
if (isComplete) {
48+
return <CheckCircle2 className="h-4 w-4 text-emerald-600" />;
49+
}
50+
if (isActive) {
51+
return <Loader2 className="h-4 w-4 animate-spin" style={{ color }} />;
52+
}
53+
return <span className={isUnavailable ? "text-slate-400" : "text-slate-500"}>{icon}</span>;
54+
};
55+
56+
return (
57+
<div
58+
className={cn(
59+
"flex items-center gap-3 p-2.5 rounded-lg border transition-all",
60+
isActive && "bg-[#D4714E]/5 border-[#D4714E]/20",
61+
isComplete && "bg-emerald-50/50 border-emerald-200/50",
62+
isUnavailable && "bg-slate-50 border-slate-200 opacity-60",
63+
!isActive && !isComplete && !isUnavailable && "bg-slate-50/50 border-slate-200/50"
64+
)}
65+
>
66+
{/* Icon */}
67+
<div
68+
className={cn(
69+
"h-8 w-8 rounded-lg flex items-center justify-center flex-shrink-0",
70+
isActive && `bg-[${color}]/10`,
71+
isComplete && "bg-emerald-100",
72+
!isActive && !isComplete && "bg-slate-100"
73+
)}
74+
style={isActive ? { backgroundColor: `${color}15` } : undefined}
75+
>
76+
{renderIcon()}
77+
</div>
78+
79+
{/* Info */}
80+
<div className="flex-1 min-w-0">
81+
<div className="flex items-center gap-2">
82+
<span className="text-xs font-semibold text-slate-700">{label}</span>
83+
{isActive && (
84+
<span className="text-[10px] font-medium px-1.5 py-0.5 rounded-full bg-[#D4714E]/10 text-[#D4714E]">
85+
{stageLabels[stage]}
86+
</span>
87+
)}
88+
</div>
89+
<p className="text-[10px] font-mono text-slate-500 truncate" title={modelName}>
90+
{formatModelName(modelName)}
91+
</p>
92+
</div>
93+
94+
{/* Detail/Status */}
95+
<div className="text-right flex-shrink-0">
96+
{detail && isActive && (
97+
<span className="text-[10px] font-medium text-[#D4714E] tabular-nums">{detail}</span>
98+
)}
99+
{isComplete && <span className="text-[10px] font-medium text-emerald-600">Done</span>}
100+
{isUnavailable && <span className="text-[10px] text-slate-400">Skipped</span>}
101+
</div>
102+
</div>
103+
);
104+
}
105+
106+
function formatModelName(name: string): string {
107+
const parts = name.split("/");
108+
return parts.at(-1) ?? name;
109+
}
110+
18111
export function MLInfoPanel({
19112
isProcessing,
20-
modelName = "nlptown/bert-base-multilingual-uncased-sentiment",
113+
pipelineModels,
21114
processingSpeed = 0,
22115
tokensProcessed = 0,
23-
avgConfidence = 0,
24-
currentBatch = 0,
25-
totalBatches = 0,
26116
processingTimeSeconds = 0,
27-
}: MLInfoPanelProps): JSX.Element {
28-
const formatModelName = (name: string) => {
29-
// Show only the model name part for readability
30-
const parts = name.split("/");
31-
return parts[parts.length - 1];
32-
};
117+
}: Readonly<MLInfoPanelProps>): JSX.Element {
118+
const hasStarted = processingTimeSeconds > 0 || isProcessing;
33119

34120
return (
35121
<div className="rounded-lg border bg-white overflow-hidden">
122+
{/* Header */}
36123
<div className="px-4 py-2.5 border-b bg-[#FAFAFA] flex items-center gap-2">
37124
<Brain className="h-4 w-4 text-[#D4714E]" />
38125
<h3 className="text-sm font-semibold tracking-tight">ML Pipeline</h3>
@@ -48,110 +135,64 @@ export function MLInfoPanel({
48135
</div>
49136

50137
<div className="p-3 space-y-3">
51-
{/* Model Info */}
52-
<div className="flex items-start gap-2">
53-
<div className="h-7 w-7 rounded bg-slate-100 flex items-center justify-center flex-shrink-0">
54-
<Cpu className="h-4 w-4 text-slate-600" />
55-
</div>
56-
<div className="min-w-0">
57-
<p className="text-[10px] text-muted-foreground uppercase tracking-wider">
58-
Model
59-
</p>
60-
<p className="text-xs font-mono font-medium truncate" title={modelName}>
61-
{formatModelName(modelName)}
62-
</p>
63-
</div>
138+
{/* Model Cards */}
139+
<div className="space-y-2">
140+
<ModelCard
141+
icon={<MessageSquare className="h-4 w-4" />}
142+
label="Sentiment Analysis"
143+
modelName={pipelineModels.sentiment.name}
144+
stage={pipelineModels.sentiment.stage}
145+
detail={pipelineModels.sentiment.detail}
146+
color="#2D7A5E"
147+
/>
148+
<ModelCard
149+
icon={<Tags className="h-4 w-4" />}
150+
label="Topic Detection"
151+
modelName={pipelineModels.topics.name}
152+
stage={pipelineModels.topics.stage}
153+
detail={pipelineModels.topics.detail}
154+
color="#9B7B5B"
155+
/>
156+
<ModelCard
157+
icon={<Sparkles className="h-4 w-4" />}
158+
label="AI Summaries"
159+
modelName={pipelineModels.summaries.name}
160+
stage={pipelineModels.summaries.stage}
161+
detail={pipelineModels.summaries.detail}
162+
color="#D4714E"
163+
/>
64164
</div>
65165

66-
{/* Processing Stats - Only show when processing or after */}
67-
{(isProcessing || processingTimeSeconds > 0) && (
68-
<>
69-
<div className="grid grid-cols-2 gap-2">
70-
{/* Speed */}
71-
<div className="rounded bg-slate-50 p-2">
72-
<div className="flex items-center gap-1.5">
73-
<Zap className="h-3 w-3 text-amber-500" />
74-
<span className="text-[10px] text-muted-foreground">Speed</span>
75-
</div>
76-
<p className="text-sm font-bold tabular-nums mt-0.5">
77-
{processingSpeed.toFixed(1)}
78-
<span className="text-[10px] font-normal text-muted-foreground ml-1">
79-
/sec
80-
</span>
81-
</p>
82-
</div>
83-
84-
{/* Tokens */}
85-
<div className="rounded bg-slate-50 p-2">
86-
<div className="flex items-center gap-1.5">
87-
<Database className="h-3 w-3 text-blue-500" />
88-
<span className="text-[10px] text-muted-foreground">Tokens</span>
89-
</div>
90-
<p className="text-sm font-bold tabular-nums mt-0.5">
91-
{tokensProcessed.toLocaleString()}
92-
</p>
166+
{/* Stats - Only show when processing or after */}
167+
{hasStarted && (
168+
<div className="grid grid-cols-3 gap-2 pt-2 border-t border-slate-100">
169+
<div className="text-center">
170+
<div className="flex items-center justify-center gap-1 text-slate-400">
171+
<Zap className="h-3 w-3" />
93172
</div>
94-
95-
{/* Confidence */}
96-
<div className="rounded bg-slate-50 p-2">
97-
<div className="flex items-center gap-1.5">
98-
<Gauge className="h-3 w-3 text-emerald-500" />
99-
<span className="text-[10px] text-muted-foreground">Confidence</span>
100-
</div>
101-
<p className="text-sm font-bold tabular-nums mt-0.5">
102-
{(avgConfidence * 100).toFixed(1)}%
103-
</p>
104-
</div>
105-
106-
{/* Time */}
107-
<div className="rounded bg-slate-50 p-2">
108-
<div className="flex items-center gap-1.5">
109-
<Clock className="h-3 w-3 text-[#D4714E]" />
110-
<span className="text-[10px] text-muted-foreground">Time</span>
111-
</div>
112-
<p className="text-sm font-bold tabular-nums mt-0.5">
113-
{processingTimeSeconds.toFixed(1)}s
114-
</p>
173+
<p className="text-sm font-bold tabular-nums text-slate-700">
174+
{processingSpeed.toFixed(1)}
175+
<span className="text-[9px] font-normal text-slate-400">/s</span>
176+
</p>
177+
<p className="text-[9px] text-slate-400">Speed</p>
178+
</div>
179+
<div className="text-center">
180+
<div className="flex items-center justify-center gap-1 text-slate-400">
181+
<Database className="h-3 w-3" />
115182
</div>
183+
<p className="text-sm font-bold tabular-nums text-slate-700">
184+
{tokensProcessed.toLocaleString()}
185+
</p>
186+
<p className="text-[9px] text-slate-400">Tokens</p>
116187
</div>
117-
118-
{/* Batch Progress */}
119-
{isProcessing && totalBatches > 0 && (
120-
<div className="space-y-1.5">
121-
<div className="flex items-center justify-between">
122-
<span className="text-[10px] text-muted-foreground">
123-
Batch Progress
124-
</span>
125-
<span className="text-[10px] font-medium tabular-nums">
126-
{currentBatch}/{totalBatches}
127-
</span>
128-
</div>
129-
<Progress
130-
value={(currentBatch / totalBatches) * 100}
131-
className="h-1.5"
132-
/>
188+
<div className="text-center">
189+
<div className="flex items-center justify-center gap-1 text-slate-400">
190+
<Clock className="h-3 w-3" />
133191
</div>
134-
)}
135-
</>
136-
)}
137-
138-
{/* Embedding visualization when processing */}
139-
{isProcessing && (
140-
<div className="rounded bg-[#D4714E]/5 p-2">
141-
<p className="text-[10px] text-[#D4714E] font-medium mb-1.5">
142-
Generating Embeddings
143-
</p>
144-
<div className="flex gap-0.5">
145-
{Array.from({ length: 20 }).map((_, i) => (
146-
<div
147-
key={i}
148-
className="flex-1 h-3 rounded-sm bg-[#D4714E]/20"
149-
style={{
150-
animation: `pulse 1.5s ease-in-out ${i * 0.1}s infinite`,
151-
opacity: 0.3 + Math.random() * 0.7,
152-
}}
153-
/>
154-
))}
192+
<p className="text-sm font-bold tabular-nums text-slate-700">
193+
{processingTimeSeconds.toFixed(1)}s
194+
</p>
195+
<p className="text-[9px] text-slate-400">Time</p>
155196
</div>
156197
</div>
157198
)}

src/components/analysis/progress-terminal.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ interface ProgressTerminalProps {
2424
videoTitle?: string;
2525
commentsFound?: number;
2626
commentsAnalyzed?: number;
27+
currentMessage?: string;
2728
onCancel?: () => void;
2829
}
2930

@@ -72,6 +73,7 @@ export function ProgressTerminal({
7273
videoTitle,
7374
commentsFound,
7475
commentsAnalyzed,
76+
currentMessage,
7577
onCancel,
7678
}: ProgressTerminalProps): JSX.Element {
7779
const getStageStatus = (stageId: AnalysisStage) => {
@@ -196,6 +198,12 @@ export function ProgressTerminal({
196198
</div>
197199
</div>
198200
)}
201+
{/* Show detailed message for topic detection and summarization */}
202+
{status === "active" && (stage.id === "detecting_topics" || stage.id === "generating_summaries") && currentMessage && (
203+
<p className="text-xs mt-1 text-[#D4714E] font-medium">
204+
{currentMessage}
205+
</p>
206+
)}
199207
</div>
200208
</div>
201209
);

0 commit comments

Comments
 (0)