refactor: compact Progress card and merge agent thought into it

- Redesign ProgressDashboard from tall stacked layout to compact inline:
  title/badge left, passing/total right, progress bar with percentage below
- Absorb AgentThought functionality directly into ProgressDashboard,
  showing the agent's current thought below the progress bar
- Remove standalone AgentThought usage from App.tsx (component now unused)
- Pass logs/agentStatus to ProgressDashboard in single-agent mode only

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Auto
2026-02-01 14:57:33 +02:00
parent 24481d474d
commit e1e5209866
2 changed files with 126 additions and 62 deletions

View File

@@ -13,7 +13,6 @@ import { SetupWizard } from './components/SetupWizard'
import { AddFeatureForm } from './components/AddFeatureForm'
import { FeatureModal } from './components/FeatureModal'
import { DebugLogViewer, type TabType } from './components/DebugLogViewer'
import { AgentThought } from './components/AgentThought'
import { AgentMissionControl } from './components/AgentMissionControl'
import { CelebrationOverlay } from './components/CelebrationOverlay'
import { AssistantFAB } from './components/AssistantFAB'
@@ -390,6 +389,8 @@ function App() {
total={progress.total}
percentage={progress.percentage}
isConnected={wsState.isConnected}
logs={wsState.activeAgents.length === 0 ? wsState.logs : undefined}
agentStatus={wsState.activeAgents.length === 0 ? wsState.agentStatus : undefined}
/>
{/* Agent Mission Control - shows orchestrator status and active agents in parallel mode */}
@@ -400,13 +401,6 @@ function App() {
getAgentLogs={wsState.getAgentLogs}
/>
{/* Agent Thought - shows latest agent narrative (single agent mode) */}
{wsState.activeAgents.length === 0 && (
<AgentThought
logs={wsState.logs}
agentStatus={wsState.agentStatus}
/>
)}
{/* Initializing Features State - show when agent is running but no features yet */}
{features &&

View File

@@ -1,12 +1,40 @@
import { Wifi, WifiOff } from 'lucide-react'
import { useMemo, useState, useEffect } from 'react'
import { Wifi, WifiOff, Brain, Sparkles } from 'lucide-react'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import type { AgentStatus } from '../lib/types'
interface ProgressDashboardProps {
passing: number
total: number
percentage: number
isConnected: boolean
logs?: Array<{ line: string; timestamp: string }>
agentStatus?: AgentStatus
}
const IDLE_TIMEOUT = 30000
function isAgentThought(line: string): boolean {
const trimmed = line.trim()
if (/^\[Tool:/.test(trimmed)) return false
if (/^\s*Input:\s*\{/.test(trimmed)) return false
if (/^\[(Done|Error)\]/.test(trimmed)) return false
if (/^Output:/.test(trimmed)) return false
if (/^[[{]/.test(trimmed)) return false
if (trimmed.length < 10) return false
if (/^[A-Za-z]:\\/.test(trimmed)) return false
if (/^\/[a-z]/.test(trimmed)) return false
return true
}
function getLatestThought(logs: Array<{ line: string; timestamp: string }>): string | null {
for (let i = logs.length - 1; i >= 0; i--) {
if (isAgentThought(logs[i].line)) {
return logs[i].line.trim()
}
}
return null
}
export function ProgressDashboard({
@@ -14,67 +42,109 @@ export function ProgressDashboard({
total,
percentage,
isConnected,
logs = [],
agentStatus,
}: ProgressDashboardProps) {
const thought = useMemo(() => getLatestThought(logs), [logs])
const [displayedThought, setDisplayedThought] = useState<string | null>(null)
const [textVisible, setTextVisible] = useState(true)
const lastLogTimestamp = logs.length > 0
? new Date(logs[logs.length - 1].timestamp).getTime()
: 0
const showThought = useMemo(() => {
if (!thought) return false
if (agentStatus === 'running') return true
if (agentStatus === 'paused') {
return Date.now() - lastLogTimestamp < IDLE_TIMEOUT
}
return false
}, [thought, agentStatus, lastLogTimestamp])
useEffect(() => {
if (thought !== displayedThought && thought) {
setTextVisible(false)
const timeout = setTimeout(() => {
setDisplayedThought(thought)
setTextVisible(true)
}, 150)
return () => clearTimeout(timeout)
}
}, [thought, displayedThought])
const isRunning = agentStatus === 'running'
return (
<Card>
<CardHeader className="flex-row items-center justify-between space-y-0 pb-4">
<CardTitle className="text-xl uppercase tracking-wide">
Progress
</CardTitle>
<Badge variant={isConnected ? 'default' : 'destructive'} className="gap-1">
{isConnected ? (
<>
<Wifi size={14} />
Live
</>
) : (
<>
<WifiOff size={14} />
Offline
</>
)}
</Badge>
<CardHeader className="flex-row items-center justify-between space-y-0 pb-0">
<div className="flex items-center gap-3">
<CardTitle className="text-xl uppercase tracking-wide">
Progress
</CardTitle>
<Badge variant={isConnected ? 'default' : 'destructive'} className="gap-1">
{isConnected ? (
<>
<Wifi size={14} />
Live
</>
) : (
<>
<WifiOff size={14} />
Offline
</>
)}
</Badge>
</div>
<div className="flex items-baseline gap-1">
<span className="font-mono text-lg font-bold text-primary">
{passing}
</span>
<span className="text-sm text-muted-foreground">/</span>
<span className="font-mono text-lg font-bold">
{total}
</span>
</div>
</CardHeader>
<CardContent>
{/* Large Percentage */}
<div className="text-center mb-6">
<span className="inline-flex items-baseline">
<span className="text-6xl font-bold tabular-nums">
{percentage.toFixed(1)}
</span>
<span className="text-3xl font-semibold text-muted-foreground">
%
</span>
<CardContent className="pt-3 pb-3">
<div className="flex items-center gap-4">
{/* Progress Bar */}
<div className="h-2.5 bg-muted rounded-full overflow-hidden flex-1">
<div
className="h-full bg-primary rounded-full transition-all duration-500 ease-out"
style={{ width: `${percentage}%` }}
/>
</div>
{/* Percentage */}
<span className="text-sm font-bold tabular-nums text-muted-foreground w-12 text-right">
{percentage.toFixed(1)}%
</span>
</div>
{/* Progress Bar */}
<div className="h-3 bg-muted rounded-full overflow-hidden mb-6">
<div
className="h-full bg-primary rounded-full transition-all duration-500 ease-out"
style={{ width: `${percentage}%` }}
/>
</div>
{/* Stats */}
<div className="flex justify-center gap-8 text-center">
<div>
<span className="font-mono text-3xl font-bold text-primary">
{passing}
</span>
<span className="block text-sm text-muted-foreground uppercase">
Passing
</span>
</div>
<div className="text-4xl text-muted-foreground">/</div>
<div>
<span className="font-mono text-3xl font-bold">
{total}
</span>
<span className="block text-sm text-muted-foreground uppercase">
Total
</span>
{/* Agent Thought */}
<div
className={`
transition-all duration-300 ease-out overflow-hidden
${showThought && displayedThought ? 'opacity-100 max-h-10 mt-3' : 'opacity-0 max-h-0 mt-0'}
`}
>
<div className="flex items-center gap-2">
<div className="relative shrink-0">
<Brain size={16} className="text-primary" strokeWidth={2.5} />
{isRunning && (
<Sparkles size={8} className="absolute -top-1 -right-1 text-yellow-500 animate-pulse" />
)}
</div>
<p
className="font-mono text-sm truncate text-muted-foreground transition-all duration-150 ease-out"
style={{
opacity: textVisible ? 1 : 0,
transform: textVisible ? 'translateY(0)' : 'translateY(-4px)',
}}
>
{displayedThought?.replace(/:$/, '')}
</p>
</div>
</div>
</CardContent>