Skip to content

Commit 266f386

Browse files
feat: redesigned Pipeline Progress + Stats Bar with premium UX
## Pipeline Progress - Full-width gradient strip with border-2 group cards - Progress bar per group showing completion percentage - Phase count badges (e.g. 3/5) - Active phase: colored dot with ping animation + scale-105 card - Completed: green checkmark + emerald border - Pending: gray dot + reduced opacity ## Stats Bar - Larger cards with rounded icon containers and shadow-sm - Gradient backgrounds, hover shadow transitions - Overall pipeline progress bar on the right - Running indicator with pulse animation - Always visible (not hidden when idle)
1 parent 94fcea7 commit 266f386

2 files changed

Lines changed: 85 additions & 160 deletions

File tree

‎cmd/slmcode/ui/index.html‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
99
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
1010
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='32' height='32' rx='6' fill='%237c3aed'/><text x='16' y='22' font-family='system-ui' font-weight='700' font-size='18' fill='white' text-anchor='middle'>SL</text></svg>" />
11-
<script type="module" crossorigin src="/assets/index-BWEhE6sN.js"></script>
12-
<link rel="stylesheet" crossorigin href="/assets/index-BE3mVxEB.css">
11+
<script type="module" crossorigin src="/assets/index-FRzHLKZ2.js"></script>
12+
<link rel="stylesheet" crossorigin href="/assets/index-A47pACXv.css">
1313
</head>
1414
<body class="antialiased">
1515
<div id="root"></div>

‎web/src/components/Live/LiveView.tsx‎

Lines changed: 83 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -366,179 +366,104 @@ export default function LiveView() {
366366
)}
367367

368368
{/* ═══════════════════════════════════════════════════════════════ */}
369-
{/* Pipeline Progress Strip — always visible */}
369+
{/* Pipeline Progress — full-width animated strip */}
370370
{/* ═══════════════════════════════════════════════════════════════ */}
371-
<div className="px-4 py-3 border-b border-gray-200 dark:border-gray-800 bg-white/50 dark:bg-gray-900/50">
372-
<div className="flex items-center gap-2 mb-2">
373-
<Layers size={14} className="text-gray-400" />
374-
<span className="text-[10px] font-semibold uppercase tracking-wider text-gray-400">
375-
Pipeline Progress
376-
</span>
377-
</div>
378-
<div className="flex items-center gap-2 flex-wrap">
379-
{groups.map((group, gi) => {
380-
const groupPhaseStates = group.phases.map((p) => phaseStateMap[p] || 'pending');
381-
const hasActive = groupPhaseStates.includes('active');
382-
const allDone = groupPhaseStates.every((s) => s === 'completed');
383-
const allPending = groupPhaseStates.every((s) => s === 'pending');
384-
385-
return (
386-
<div key={group.id} className="flex items-center gap-2">
387-
{/* Separator arrow between groups */}
388-
{gi > 0 && (
389-
<ChevronDown
390-
size={12}
391-
className="text-gray-300 dark:text-gray-600 rotate-[-90deg] shrink-0"
392-
/>
393-
)}
394-
395-
{/* Group card */}
396-
<div
397-
className={clsx(
398-
'flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg border transition-all duration-300',
399-
GROUP_BG_COLORS[group.color],
400-
hasActive
401-
? clsx(GROUP_BORDER_COLORS[group.color], 'shadow-sm')
402-
: allDone
403-
? 'border-emerald-300 dark:border-emerald-700'
404-
: 'border-gray-200 dark:border-gray-700',
405-
)}
406-
>
407-
{/* Group label */}
408-
<span
409-
className={clsx(
410-
'text-[10px] font-semibold uppercase tracking-wider mr-1',
411-
allDone
412-
? 'text-emerald-600 dark:text-emerald-400'
413-
: hasActive
414-
? GROUP_TEXT_COLORS[group.color]
415-
: 'text-gray-400 dark:text-gray-500',
416-
)}
417-
>
418-
{group.label}
419-
</span>
420-
421-
{/* Phase dots */}
422-
{group.phases.map((phase) => {
423-
const state = phaseStateMap[phase] || 'pending';
424-
const isActive = state === 'active';
425-
const isCompleted = state === 'completed';
426-
const dotColor = PHASE_DOT_COLORS[phase] || 'bg-gray-400';
427-
428-
return (
429-
<div
430-
key={phase}
431-
className="relative flex items-center justify-center"
432-
title={`${phase}${isActive ? ' (active)' : isCompleted ? ' (completed)' : ''}`}
433-
>
434-
{isCompleted ? (
435-
<CheckCircle2
436-
size={14}
437-
className="text-emerald-500 dark:text-emerald-400"
438-
/>
439-
) : isActive ? (
440-
<div className="relative">
441-
<Circle
442-
size={14}
443-
className={clsx(dotColor, 'animate-pulse')}
444-
fill="currentColor"
445-
/>
446-
{/* Outer pulse ring */}
447-
<span
448-
className={clsx(
449-
'absolute inset-0 rounded-full animate-ping opacity-40',
450-
dotColor.replace('bg-', 'bg-'),
451-
)}
452-
/>
453-
</div>
454-
) : (
455-
<Circle
456-
size={10}
457-
className="text-gray-300 dark:text-gray-600"
458-
fill="currentColor"
459-
/>
460-
)}
461-
</div>
462-
);
371+
<div className="px-4 py-2.5 border-b border-gray-200 dark:border-gray-800 bg-gradient-to-r from-gray-50 via-white to-gray-50 dark:from-gray-900 dark:via-gray-850 dark:to-gray-900">
372+
<div className="flex items-center gap-1.5 flex-wrap">
373+
{groups.map((group, gi) => {
374+
const done = group.phases.filter((p) => phaseStateMap[p] === 'completed').length;
375+
const active = group.phases.some((p) => phaseStateMap[p] === 'active');
376+
const allDone = done === group.phases.length;
377+
const pct = group.phases.length > 0 ? Math.round((done / group.phases.length) * 100) : 0;
378+
379+
return (
380+
<div key={group.id} className="flex items-center gap-1">
381+
{gi > 0 && <ChevronDown size={10} className="text-gray-300 dark:text-gray-600 -rotate-90 shrink-0" />}
382+
<div className={clsx(
383+
'relative flex items-center gap-2 px-3 py-2 rounded-xl border-2 transition-all duration-500 min-w-[120px]',
384+
active ? clsx(GROUP_BG_COLORS[group.color], 'shadow-md scale-105 border-current') :
385+
allDone ? 'bg-emerald-50/50 dark:bg-emerald-950/20 border-emerald-300 dark:border-emerald-700' :
386+
'bg-white dark:bg-gray-900 border-gray-200 dark:border-gray-700 opacity-75'
387+
)}>
388+
<div className="flex flex-col gap-0.5">
389+
<span className={clsx('text-[10px] font-bold uppercase tracking-wider leading-none', allDone ? 'text-emerald-600' : active ? 'text-current opacity-80' : 'text-gray-400')}>{group.label}</span>
390+
<span className="text-[9px] text-gray-400 font-mono tabular-nums leading-none">{done}/{group.phases.length}</span>
391+
</div>
392+
{/* Progress bar */}
393+
<div className="flex-1 h-1.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden min-w-[40px]">
394+
<div className={clsx('h-full rounded-full transition-all duration-700', allDone ? 'bg-emerald-500' : active ? 'bg-current opacity-70' : 'bg-gray-300')} style={{ width: `${pct}%` }} />
395+
</div>
396+
{/* Phase dots */}
397+
<div className="flex items-center gap-0.5 shrink-0">
398+
{group.phases.map((p) => {
399+
const s = phaseStateMap[p] || 'pending';
400+
return s === 'completed' ? <CheckCircle2 key={p} size={12} className="text-emerald-500" /> :
401+
s === 'active' ? <div key={p} className="relative"><div className={clsx('w-2.5 h-2.5 rounded-full animate-pulse', PHASE_DOT_COLORS[p] || 'bg-blue-500')} /></div> :
402+
<div key={p} className="w-2 h-2 rounded-full bg-gray-300 dark:bg-gray-600" />;
463403
})}
464404
</div>
405+
{active && <div className="absolute -top-1 -right-1 w-2 h-2 rounded-full bg-brand-500 animate-ping" />}
465406
</div>
466-
);
467-
})}
468-
</div>
407+
</div>
408+
);
409+
})}
469410
</div>
411+
</div>
470412

471413
{/* ═══════════════════════════════════════════════════════════════ */}
472-
{/* Stats Dashboard Row — always visible */}
414+
{/* Live Stats Bar — prominent cards */}
473415
{/* ═══════════════════════════════════════════════════════════════ */}
474-
<div className="px-4 py-2.5 border-b border-gray-200 dark:border-gray-800 glass-alt">
475-
<div className="flex items-center gap-4 flex-wrap">
476-
{/* Phases completed */}
477-
<div className="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700">
478-
<Target size={14} className="text-sky-500" />
479-
<div>
480-
<span className="text-[10px] text-gray-400 font-medium">Phases</span>
481-
<span className="ml-1.5 text-sm font-bold text-gray-800 dark:text-gray-200 tabular-nums">
482-
{stats.phasesCompleted}
483-
<span className="text-gray-400 font-normal">/{stats.totalPhases}</span>
484-
</span>
485-
</div>
416+
<div className="px-4 py-2 border-b border-gray-200 dark:border-gray-800 bg-gradient-to-r from-gray-50 via-white to-gray-50 dark:from-gray-900 dark:via-gray-850 dark:to-gray-900">
417+
<div className="flex items-center gap-3">
418+
{/* Phases */}
419+
<div className="flex items-center gap-2 px-4 py-2 rounded-xl bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 shadow-sm hover:shadow-md transition-shadow">
420+
<div className="w-8 h-8 rounded-lg bg-sky-100 dark:bg-sky-900/30 flex items-center justify-center"><Target size={16} className="text-sky-600" /></div>
421+
<div>
422+
<div className="text-[10px] text-gray-400 font-medium leading-none">Phases</div>
423+
<div className="text-base font-bold text-gray-800 dark:text-white tabular-nums leading-tight">{stats.phasesCompleted}<span className="text-sm text-gray-400 font-normal">/{stats.totalPhases}</span></div>
486424
</div>
487-
488-
{/* Active agent */}
489-
<div className="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700">
490-
<Bot size={14} className="text-violet-500" />
491-
<div>
492-
<span className="text-[10px] text-gray-400 font-medium">Agent</span>
493-
<span className="ml-1.5 text-sm font-bold text-gray-800 dark:text-gray-200">
494-
{activeAgentId ? (
495-
<span className="text-violet-600 dark:text-violet-400">
496-
{activeAgentSpec?.title || activeAgentId}
497-
</span>
498-
) : (
499-
<span className="text-gray-400">—</span>
500-
)}
501-
</span>
502-
</div>
425+
</div>
426+
{/* Agent */}
427+
<div className="flex items-center gap-2 px-4 py-2 rounded-xl bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 shadow-sm hover:shadow-md transition-shadow">
428+
<div className={clsx('w-8 h-8 rounded-lg flex items-center justify-center', activeAgentId ? 'bg-violet-100 dark:bg-violet-900/30' : 'bg-gray-100 dark:bg-gray-800')}>
429+
<Bot size={16} className={activeAgentId ? 'text-violet-600' : 'text-gray-400'} />
503430
</div>
504-
505-
{/* Tasks */}
506-
<div className="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700">
507-
<Zap size={14} className="text-amber-500" />
508-
<div>
509-
<span className="text-[10px] text-gray-400 font-medium">Tasks</span>
510-
<span className="ml-1.5 text-sm font-bold text-gray-800 dark:text-gray-200 tabular-nums">
511-
{stats.tasksSeen > 0 ? (
512-
<span className="text-amber-600 dark:text-amber-400">{stats.tasksSeen}</span>
513-
) : (
514-
<span className="text-gray-400">0</span>
515-
)}
516-
</span>
431+
<div>
432+
<div className="text-[10px] text-gray-400 font-medium leading-none">Agent</div>
433+
<div className="text-sm font-bold text-gray-800 dark:text-white leading-tight truncate max-w-[120px]">
434+
{activeAgentId ? (activeAgentSpec?.title || activeAgentId) : <span className="text-gray-400 font-normal">idle</span>}
517435
</div>
518436
</div>
519-
520-
{/* Events count */}
521-
<div className="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700">
522-
<Activity size={14} className="text-emerald-500" />
523-
<div>
524-
<span className="text-[10px] text-gray-400 font-medium">Events</span>
525-
<span className="ml-1.5 text-sm font-bold text-gray-800 dark:text-gray-200 tabular-nums">
526-
{stats.eventsCount}
527-
</span>
528-
</div>
437+
</div>
438+
{/* Tasks */}
439+
<div className="flex items-center gap-2 px-4 py-2 rounded-xl bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 shadow-sm hover:shadow-md transition-shadow">
440+
<div className="w-8 h-8 rounded-lg bg-amber-100 dark:bg-amber-900/30 flex items-center justify-center"><Zap size={16} className="text-amber-600" /></div>
441+
<div>
442+
<div className="text-[10px] text-gray-400 font-medium leading-none">Tasks</div>
443+
<div className={clsx('text-base font-bold tabular-nums leading-tight', stats.tasksSeen > 0 ? 'text-amber-600' : 'text-gray-400')}>{stats.tasksSeen}</div>
444+
</div>
445+
</div>
446+
{/* Events */}
447+
<div className="flex items-center gap-2 px-4 py-2 rounded-xl bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 shadow-sm hover:shadow-md transition-shadow">
448+
<div className="w-8 h-8 rounded-lg bg-emerald-100 dark:bg-emerald-900/30 flex items-center justify-center"><Activity size={16} className="text-emerald-600" /></div>
449+
<div>
450+
<div className="text-[10px] text-gray-400 font-medium leading-none">Events</div>
451+
<div className="text-base font-bold text-gray-800 dark:text-white tabular-nums leading-tight">{stats.eventsCount}</div>
529452
</div>
530-
531-
{/* Running indicator */}
532-
{running && (
533-
<div className="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-brand-50 dark:bg-brand-950/30 border border-brand-200 dark:border-brand-800">
534-
<Loader2 size={14} className="text-brand-500 animate-spin" />
535-
<span className="text-[11px] font-semibold text-brand-600 dark:text-brand-400">
536-
Running…
537-
</span>
538-
</div>
539-
)}
453+
</div>
454+
{/* Running */}
455+
{running && (
456+
<div className="flex items-center gap-2 px-4 py-2 rounded-xl bg-brand-50 dark:bg-brand-950/30 border-2 border-brand-300 dark:border-brand-700 shadow-sm animate-pulse">
457+
<Loader2 size={18} className="text-brand-500 animate-spin" />
458+
<span className="text-sm font-bold text-brand-700 dark:text-brand-300">Running</span>
459+
</div>
460+
)}
461+
{/* Progress bar for overall pipeline */}
462+
<div className="flex-1 h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden min-w-[60px]">
463+
<div className="h-full bg-gradient-to-r from-brand-400 to-brand-600 rounded-full transition-all duration-1000" style={{ width: `${totalPhases > 0 ? Math.round((stats.phasesCompleted / totalPhases) * 100) : 0}%` }} />
540464
</div>
541465
</div>
466+
</div>
542467

543468
{/* ═══════════════════════════════════════════════════════════════ */}
544469
{/* Active Agent Panel */}

0 commit comments

Comments
 (0)