Skip to content

Commit e878be2

Browse files
committed
feat(ui): enhance agent interface with modal viewer and improved icon picker
- Add AgentRunOutputViewer modal component for inline run preview - Implement IconPicker component with expanded icon selection - Refactor AgentRunsList to use modal instead of full-page navigation - Improve CreateAgent form layout with better grid structure - Update agent icon system to support wider range of icons - Enhance UI components with better animations and styling - Add scroll-area component for better content scrolling - Remove unused AgentRunView in favor of modal approach
1 parent 5d69b44 commit e878be2

16 files changed

Lines changed: 1604 additions & 296 deletions

src/components/AgentRunOutputViewer.tsx

Lines changed: 665 additions & 0 deletions
Large diffs are not rendered by default.

src/components/AgentRunsList.tsx

Lines changed: 107 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useState } from "react";
2-
import { motion } from "framer-motion";
2+
import { motion, AnimatePresence } from "framer-motion";
33
import { Play, Clock, Hash, Bot } from "lucide-react";
44
import { Card, CardContent } from "@/components/ui/card";
55
import { Badge } from "@/components/ui/badge";
@@ -8,6 +8,7 @@ import { cn } from "@/lib/utils";
88
import { formatISOTimestamp } from "@/lib/date-utils";
99
import type { AgentRunWithMetrics } from "@/lib/api";
1010
import { AGENT_ICONS } from "./CCAgents";
11+
import { AgentRunOutputViewer } from "./AgentRunOutputViewer";
1112

1213
interface AgentRunsListProps {
1314
/**
@@ -41,6 +42,7 @@ export const AgentRunsList: React.FC<AgentRunsListProps> = ({
4142
className,
4243
}) => {
4344
const [currentPage, setCurrentPage] = useState(1);
45+
const [selectedRun, setSelectedRun] = useState<AgentRunWithMetrics | null>(null);
4446

4547
// Calculate pagination
4648
const totalPages = Math.ceil(runs.length / ITEMS_PER_PAGE);
@@ -75,6 +77,16 @@ export const AgentRunsList: React.FC<AgentRunsListProps> = ({
7577
return tokens.toString();
7678
};
7779

80+
const handleRunClick = (run: AgentRunWithMetrics) => {
81+
// If there's a callback, use it (for full-page navigation)
82+
if (onRunClick) {
83+
onRunClick(run);
84+
} else {
85+
// Otherwise, open in modal preview
86+
setSelectedRun(run);
87+
}
88+
};
89+
7890
if (runs.length === 0) {
7991
return (
8092
<div className={cn("text-center py-8 text-muted-foreground", className)}>
@@ -83,92 +95,114 @@ export const AgentRunsList: React.FC<AgentRunsListProps> = ({
8395
</div>
8496
);
8597
}
86-
98+
8799
return (
88-
<div className={cn("space-y-4", className)}>
89-
<div className="space-y-2">
90-
{currentRuns.map((run, index) => (
91-
<motion.div
92-
key={run.id}
93-
initial={{ opacity: 0, y: 20 }}
94-
animate={{ opacity: 1, y: 0 }}
95-
transition={{
96-
duration: 0.3,
97-
delay: index * 0.05,
98-
ease: [0.4, 0, 0.2, 1],
99-
}}
100-
>
101-
<Card
102-
className={cn(
103-
"transition-all hover:shadow-md cursor-pointer",
104-
onRunClick && "hover:shadow-lg hover:border-primary/50 active:scale-[0.99]"
105-
)}
106-
onClick={() => onRunClick?.(run)}
100+
<>
101+
<div className={cn("space-y-2", className)}>
102+
<AnimatePresence mode="popLayout">
103+
{currentRuns.map((run, index) => (
104+
<motion.div
105+
key={run.id}
106+
initial={{ opacity: 0, y: 20 }}
107+
animate={{ opacity: 1, y: 0 }}
108+
exit={{ opacity: 0, y: -20 }}
109+
transition={{
110+
duration: 0.3,
111+
delay: index * 0.05,
112+
ease: [0.4, 0, 0.2, 1],
113+
}}
107114
>
108-
<CardContent className="p-3">
109-
<div className="flex items-start justify-between gap-3">
110-
<div className="flex items-start gap-3 flex-1 min-w-0">
111-
<div className="mt-0.5">
115+
<Card
116+
className={cn(
117+
"cursor-pointer transition-all hover:shadow-md hover:scale-[1.01] active:scale-[0.99]",
118+
run.status === "running" && "border-green-500/50"
119+
)}
120+
onClick={() => handleRunClick(run)}
121+
>
122+
<CardContent className="p-3">
123+
<div className="flex items-center gap-3">
124+
<div className="flex-shrink-0">
112125
{renderIcon(run.agent_icon)}
113126
</div>
114-
<div className="flex-1 min-w-0 space-y-1">
115-
<div className="flex items-center gap-2">
116-
<p className="text-sm font-medium truncate">{run.task}</p>
117-
<Badge variant="outline" className="text-xs">
118-
{run.model === 'opus' ? 'Claude 4 Opus' : 'Claude 4 Sonnet'}
119-
</Badge>
127+
128+
<div className="flex-1 min-w-0">
129+
<div className="flex items-center gap-2 mb-1">
130+
<h4 className="text-sm font-medium truncate">
131+
{run.agent_name}
132+
</h4>
133+
{run.status === "running" && (
134+
<div className="flex items-center gap-1">
135+
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
136+
<span className="text-xs text-green-600 font-medium">Running</span>
137+
</div>
138+
)}
120139
</div>
140+
141+
<p className="text-xs text-muted-foreground truncate mb-1">
142+
{run.task}
143+
</p>
144+
121145
<div className="flex items-center gap-3 text-xs text-muted-foreground">
122-
<span className="truncate">by {run.agent_name}</span>
123-
{run.completed_at && (
124-
<>
125-
<span></span>
126-
<div className="flex items-center gap-1">
127-
<Clock className="h-3 w-3" />
128-
<span>{formatDuration(run.metrics?.duration_ms)}</span>
129-
</div>
130-
</>
146+
<div className="flex items-center gap-1">
147+
<Clock className="h-3 w-3" />
148+
<span>{formatISOTimestamp(run.created_at)}</span>
149+
</div>
150+
151+
{run.metrics?.duration_ms && (
152+
<span>{formatDuration(run.metrics.duration_ms)}</span>
131153
)}
154+
132155
{run.metrics?.total_tokens && (
133-
<>
134-
<span></span>
135-
<div className="flex items-center gap-1">
136-
<Hash className="h-3 w-3" />
137-
<span>{formatTokens(run.metrics?.total_tokens)}</span>
138-
</div>
139-
</>
140-
)}
141-
{run.metrics?.cost_usd && (
142-
<>
143-
<span></span>
144-
<span>${run.metrics?.cost_usd?.toFixed(4)}</span>
145-
</>
156+
<div className="flex items-center gap-1">
157+
<Hash className="h-3 w-3" />
158+
<span>{formatTokens(run.metrics.total_tokens)}</span>
159+
</div>
146160
)}
147161
</div>
148-
<p className="text-xs text-muted-foreground">
149-
{formatISOTimestamp(run.created_at)}
150-
</p>
162+
</div>
163+
164+
<div className="flex-shrink-0">
165+
<Badge
166+
variant={
167+
run.status === "completed" ? "default" :
168+
run.status === "running" ? "secondary" :
169+
run.status === "failed" ? "destructive" :
170+
"outline"
171+
}
172+
className="text-xs"
173+
>
174+
{run.status === "completed" ? "Completed" :
175+
run.status === "running" ? "Running" :
176+
run.status === "failed" ? "Failed" :
177+
"Pending"}
178+
</Badge>
151179
</div>
152180
</div>
153-
{!run.completed_at && (
154-
<Badge variant="secondary" className="text-xs">
155-
Running
156-
</Badge>
157-
)}
158-
</div>
159-
</CardContent>
160-
</Card>
161-
</motion.div>
162-
))}
181+
</CardContent>
182+
</Card>
183+
</motion.div>
184+
))}
185+
</AnimatePresence>
186+
187+
{/* Pagination */}
188+
{totalPages > 1 && (
189+
<div className="pt-2">
190+
<Pagination
191+
currentPage={currentPage}
192+
totalPages={totalPages}
193+
onPageChange={setCurrentPage}
194+
/>
195+
</div>
196+
)}
163197
</div>
164-
165-
{totalPages > 1 && (
166-
<Pagination
167-
currentPage={currentPage}
168-
totalPages={totalPages}
169-
onPageChange={setCurrentPage}
198+
199+
{/* Agent Run Output Viewer Modal */}
200+
{selectedRun && (
201+
<AgentRunOutputViewer
202+
run={selectedRun}
203+
onClose={() => setSelectedRun(null)}
170204
/>
171205
)}
172-
</div>
206+
</>
173207
);
174208
};

src/components/CCAgents.tsx

Lines changed: 16 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,6 @@ import {
66
Trash2,
77
Play,
88
Bot,
9-
Brain,
10-
Code,
11-
Sparkles,
12-
Zap,
13-
Cpu,
14-
Rocket,
15-
Shield,
16-
Terminal,
179
ArrowLeft,
1810
History,
1911
Download,
@@ -46,9 +38,9 @@ import { Toast, ToastContainer } from "@/components/ui/toast";
4638
import { CreateAgent } from "./CreateAgent";
4739
import { AgentExecution } from "./AgentExecution";
4840
import { AgentRunsList } from "./AgentRunsList";
49-
import { AgentRunView } from "./AgentRunView";
5041
import { RunningSessionsView } from "./RunningSessionsView";
5142
import { GitHubAgentBrowser } from "./GitHubAgentBrowser";
43+
import { ICON_MAP } from "./IconPicker";
5244

5345
interface CCAgentsProps {
5446
/**
@@ -61,18 +53,8 @@ interface CCAgentsProps {
6153
className?: string;
6254
}
6355

64-
// Available icons for agents
65-
export const AGENT_ICONS = {
66-
bot: Bot,
67-
brain: Brain,
68-
code: Code,
69-
sparkles: Sparkles,
70-
zap: Zap,
71-
cpu: Cpu,
72-
rocket: Rocket,
73-
shield: Shield,
74-
terminal: Terminal,
75-
};
56+
// Available icons for agents - now using all icons from IconPicker
57+
export const AGENT_ICONS = ICON_MAP;
7658

7759
export type AgentIconName = keyof typeof AGENT_ICONS;
7860

@@ -90,10 +72,10 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
9072
const [error, setError] = useState<string | null>(null);
9173
const [toast, setToast] = useState<{ message: string; type: "success" | "error" } | null>(null);
9274
const [currentPage, setCurrentPage] = useState(1);
93-
const [view, setView] = useState<"list" | "create" | "edit" | "execute" | "viewRun">("list");
75+
const [view, setView] = useState<"list" | "create" | "edit" | "execute">("list");
9476
const [activeTab, setActiveTab] = useState<"agents" | "running">("agents");
9577
const [selectedAgent, setSelectedAgent] = useState<Agent | null>(null);
96-
const [selectedRunId, setSelectedRunId] = useState<number | null>(null);
78+
// const [selectedRunId, setSelectedRunId] = useState<number | null>(null);
9779
const [showGitHubBrowser, setShowGitHubBrowser] = useState(false);
9880
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
9981
const [agentToDelete, setAgentToDelete] = useState<Agent | null>(null);
@@ -195,12 +177,12 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
195177
setToast({ message: "Agent updated successfully", type: "success" });
196178
};
197179

198-
const handleRunClick = (run: AgentRunWithMetrics) => {
199-
if (run.id) {
200-
setSelectedRunId(run.id);
201-
setView("viewRun");
202-
}
203-
};
180+
// const handleRunClick = (run: AgentRunWithMetrics) => {
181+
// if (run.id) {
182+
// setSelectedRunId(run.id);
183+
// setView("viewRun");
184+
// }
185+
// };
204186

205187
const handleExecutionComplete = async () => {
206188
// Reload runs when returning from execution
@@ -270,7 +252,7 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
270252
const paginatedAgents = agents.slice(startIndex, startIndex + AGENTS_PER_PAGE);
271253

272254
const renderIcon = (iconName: string) => {
273-
const Icon = AGENT_ICONS[iconName as AgentIconName] || Bot;
255+
const Icon = AGENT_ICONS[iconName as AgentIconName] || AGENT_ICONS.bot;
274256
return <Icon className="h-12 w-12" />;
275257
};
276258

@@ -305,14 +287,7 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
305287
);
306288
}
307289

308-
if (view === "viewRun" && selectedRunId) {
309-
return (
310-
<AgentRunView
311-
runId={selectedRunId}
312-
onBack={() => setView("list")}
313-
/>
314-
);
315-
}
290+
// Removed viewRun case - now using modal preview in AgentRunsList
316291

317292
return (
318293
<div className={cn("flex flex-col h-full bg-background", className)}>
@@ -564,7 +539,9 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
564539
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-primary"></div>
565540
</div>
566541
) : (
567-
<AgentRunsList runs={runs} onRunClick={handleRunClick} />
542+
<AgentRunsList
543+
runs={runs}
544+
/>
568545
)}
569546
</div>
570547
)}

0 commit comments

Comments
 (0)