Skip to content

Commit 616fe88

Browse files
abossardCopilot
andcommitted
feat: redesign workbench with agent cards, runs side panel, and tabbed layout
- Split WorkbenchPage into tabbed UI: Agents (cards grid) + Create Agent - AgentCardsPanel: icon cards with Run/Edit/Delete buttons per agent - RunsSidePanel: scrollable run history with click-to-view output - AgentEditDialog: edit existing agents via dialog - AgentCreateForm: extracted creation form (reusable for create + edit) - Added API functions: updateWorkbenchAgent, listAllRuns, getRun - All 47 Playwright tests pass (12 workbench tests updated for new UI) - Removed Ollama references from setup.sh and package.json Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 31da4df commit 616fe88

8 files changed

Lines changed: 1178 additions & 634 deletions

File tree

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
import {
2+
Badge,
3+
Button,
4+
Card,
5+
CardHeader,
6+
Input,
7+
Spinner,
8+
Text,
9+
makeStyles,
10+
tokens,
11+
} from '@fluentui/react-components'
12+
import {
13+
Bot24Regular,
14+
Delete24Regular,
15+
Edit24Regular,
16+
Play24Regular,
17+
} from '@fluentui/react-icons'
18+
import { useState } from 'react'
19+
import { runWorkbenchAgent } from '../../services/api'
20+
21+
const useStyles = makeStyles({
22+
grid: {
23+
display: 'grid',
24+
gridTemplateColumns: 'repeat(3, 1fr)',
25+
gap: tokens.spacingHorizontalL,
26+
'@media (max-width: 1200px)': {
27+
gridTemplateColumns: 'repeat(2, 1fr)',
28+
},
29+
'@media (max-width: 768px)': {
30+
gridTemplateColumns: '1fr',
31+
},
32+
},
33+
card: {
34+
padding: tokens.spacingVerticalM,
35+
display: 'flex',
36+
flexDirection: 'column',
37+
gap: tokens.spacingVerticalS,
38+
},
39+
description: {
40+
color: tokens.colorNeutralForeground3,
41+
fontSize: tokens.fontSizeBase200,
42+
overflow: 'hidden',
43+
textOverflow: 'ellipsis',
44+
display: '-webkit-box',
45+
WebkitLineClamp: 2,
46+
WebkitBoxOrient: 'vertical',
47+
},
48+
meta: {
49+
display: 'flex',
50+
alignItems: 'center',
51+
gap: tokens.spacingHorizontalS,
52+
flexWrap: 'wrap',
53+
},
54+
actions: {
55+
display: 'flex',
56+
alignItems: 'center',
57+
gap: tokens.spacingHorizontalXS,
58+
marginTop: 'auto',
59+
},
60+
inputRow: {
61+
display: 'flex',
62+
gap: tokens.spacingHorizontalXS,
63+
alignItems: 'center',
64+
},
65+
runButton: {
66+
flexShrink: 0,
67+
},
68+
})
69+
70+
export default function AgentCardsPanel({
71+
agents,
72+
onEdit,
73+
onDelete,
74+
onRunStarted,
75+
onRefresh,
76+
}) {
77+
const styles = useStyles()
78+
const [runningIds, setRunningIds] = useState({})
79+
const [inputVisibleIds, setInputVisibleIds] = useState({})
80+
const [inputValues, setInputValues] = useState({})
81+
82+
async function handleRun(agent) {
83+
if (agent.requires_input && !inputVisibleIds[agent.id]) {
84+
setInputVisibleIds((prev) => ({ ...prev, [agent.id]: true }))
85+
return
86+
}
87+
88+
setRunningIds((prev) => ({ ...prev, [agent.id]: true }))
89+
try {
90+
const run = await runWorkbenchAgent(agent.id, {
91+
requiredInputValue: inputValues[agent.id] || '',
92+
})
93+
onRunStarted?.(run)
94+
} catch (err) {
95+
console.error(`Failed to run agent ${agent.id}:`, err)
96+
} finally {
97+
setRunningIds((prev) => ({ ...prev, [agent.id]: false }))
98+
setInputVisibleIds((prev) => ({ ...prev, [agent.id]: false }))
99+
setInputValues((prev) => ({ ...prev, [agent.id]: '' }))
100+
}
101+
}
102+
103+
if (!agents || agents.length === 0) {
104+
return <Text italic>No agents configured. Create one to get started.</Text>
105+
}
106+
107+
return (
108+
<div className={styles.grid}>
109+
{agents.map((agent) => (
110+
<Card
111+
key={agent.id}
112+
className={styles.card}
113+
data-testid={`agent-card-${agent.id}`}
114+
>
115+
<CardHeader
116+
image={<Bot24Regular />}
117+
header={<Text weight="semibold">{agent.name}</Text>}
118+
action={
119+
runningIds[agent.id] ? (
120+
<Spinner size="tiny" />
121+
) : null
122+
}
123+
/>
124+
125+
{agent.description && (
126+
<Text className={styles.description}>{agent.description}</Text>
127+
)}
128+
129+
<div className={styles.meta}>
130+
{agent.tool_names?.length > 0 && (
131+
<Badge appearance="outline" size="small">
132+
{agent.tool_names.length} tools
133+
</Badge>
134+
)}
135+
{agent.show_in_menu && (
136+
<Badge appearance="tint" color="brand" size="small">
137+
Menu
138+
</Badge>
139+
)}
140+
</div>
141+
142+
{inputVisibleIds[agent.id] && (
143+
<div className={styles.inputRow}>
144+
<Input
145+
size="small"
146+
placeholder={agent.required_input_description || 'Enter input…'}
147+
value={inputValues[agent.id] || ''}
148+
onChange={(_, data) =>
149+
setInputValues((prev) => ({ ...prev, [agent.id]: data.value }))
150+
}
151+
onKeyDown={(e) => {
152+
if (e.key === 'Enter') handleRun(agent)
153+
}}
154+
/>
155+
<Button
156+
size="small"
157+
appearance="primary"
158+
onClick={() => handleRun(agent)}
159+
disabled={runningIds[agent.id]}
160+
>
161+
Go
162+
</Button>
163+
</div>
164+
)}
165+
166+
<div className={styles.actions}>
167+
<Button
168+
className={styles.runButton}
169+
appearance="primary"
170+
size="small"
171+
icon={<Play24Regular />}
172+
onClick={() => handleRun(agent)}
173+
disabled={runningIds[agent.id]}
174+
data-testid={`agent-card-run-${agent.id}`}
175+
>
176+
Run
177+
</Button>
178+
<Button
179+
appearance="subtle"
180+
size="small"
181+
icon={<Edit24Regular />}
182+
onClick={() => onEdit?.(agent)}
183+
data-testid={`agent-card-edit-${agent.id}`}
184+
/>
185+
<Button
186+
appearance="subtle"
187+
size="small"
188+
icon={<Delete24Regular />}
189+
onClick={() => onDelete?.(agent.id)}
190+
data-testid={`agent-card-delete-${agent.id}`}
191+
/>
192+
</div>
193+
</Card>
194+
))}
195+
</div>
196+
)
197+
}

0 commit comments

Comments
 (0)