@ash-ai/dashboard 0.0.8 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/app/agents/config/page.tsx +59 -0
  2. package/app/agents/detail/page.tsx +8 -1
  3. package/app/agents/evals/page.tsx +14 -403
  4. package/app/agents/knowledge/page.tsx +14 -157
  5. package/app/agents/page.tsx +34 -1
  6. package/app/agents/versions/page.tsx +14 -235
  7. package/app/sessions/page.tsx +51 -1
  8. package/out/404/index.html +1 -1
  9. package/out/404.html +1 -1
  10. package/out/_next/static/7bfIRYNSIrLnufuHDu6SP/_buildManifest.js +1 -0
  11. package/out/_next/static/chunks/447.ff2905689aee4ecc.js +1 -0
  12. package/out/_next/static/chunks/{929-9cd7b87bfba85ee1.js → 929-f69b424a1300c015.js} +1 -1
  13. package/out/_next/static/chunks/app/agents/config/page-bd4b1aa9c56ac3bf.js +1 -0
  14. package/out/_next/static/chunks/app/agents/detail/page-7ddf96bb1bf30c84.js +1 -0
  15. package/out/_next/static/chunks/app/agents/eval-run/page-eb21414673ee3a61.js +1 -0
  16. package/out/_next/static/chunks/app/agents/eval-runs/page-4737a360beffdd17.js +1 -0
  17. package/out/_next/static/chunks/app/agents/evals/page-e764fd7f7c4e7dd0.js +1 -0
  18. package/out/_next/static/chunks/app/agents/knowledge/page-825e40e50aeca7b4.js +1 -0
  19. package/out/_next/static/chunks/app/agents/page-c70b7f8a4639d430.js +1 -0
  20. package/out/_next/static/chunks/app/agents/versions/page-16851d0f7e5c442c.js +1 -0
  21. package/out/_next/static/chunks/app/analytics/page-a1d93f279dc37c76.js +1 -0
  22. package/out/_next/static/chunks/app/layout-ccc59eb27dd73234.js +1 -0
  23. package/out/_next/static/chunks/app/logs/page-a47c75fb5c10da47.js +1 -0
  24. package/out/_next/static/chunks/app/page-8ce5c070587b5731.js +1 -0
  25. package/out/_next/static/chunks/app/playground/page-0fe012a6d6f00326.js +1 -0
  26. package/out/_next/static/chunks/app/queue/page-8413b5c0878aa6eb.js +1 -0
  27. package/out/_next/static/chunks/app/sessions/page-aac6f065bbb5b2ea.js +1 -0
  28. package/out/_next/static/chunks/app/settings/credentials/page-9ab78fa32e44448a.js +1 -0
  29. package/out/_next/static/chunks/{webpack-a50a78a04aed446d.js → webpack-2608afc863921e98.js} +1 -1
  30. package/out/_next/static/css/2c57b4d6ae47f2bd.css +1 -0
  31. package/out/agents/config/index.html +1 -0
  32. package/out/agents/config/index.txt +22 -0
  33. package/out/agents/detail/index.html +1 -1
  34. package/out/agents/detail/index.txt +6 -6
  35. package/out/agents/eval-compare/index.html +1 -1
  36. package/out/agents/eval-compare/index.txt +6 -6
  37. package/out/agents/eval-run/index.html +1 -1
  38. package/out/agents/eval-run/index.txt +6 -6
  39. package/out/agents/eval-runs/index.html +1 -1
  40. package/out/agents/eval-runs/index.txt +6 -6
  41. package/out/agents/evals/index.html +1 -1
  42. package/out/agents/evals/index.txt +6 -6
  43. package/out/agents/index.html +1 -1
  44. package/out/agents/index.txt +6 -6
  45. package/out/agents/knowledge/index.html +1 -1
  46. package/out/agents/knowledge/index.txt +6 -6
  47. package/out/agents/versions/index.html +1 -1
  48. package/out/agents/versions/index.txt +6 -6
  49. package/out/analytics/index.html +1 -1
  50. package/out/analytics/index.txt +6 -6
  51. package/out/index.html +1 -1
  52. package/out/index.txt +6 -6
  53. package/out/logs/index.html +1 -1
  54. package/out/logs/index.txt +6 -6
  55. package/out/playground/index.html +1 -1
  56. package/out/playground/index.txt +6 -6
  57. package/out/queue/index.html +1 -1
  58. package/out/queue/index.txt +6 -6
  59. package/out/sessions/index.html +1 -1
  60. package/out/sessions/index.txt +6 -6
  61. package/out/settings/api-keys/index.html +1 -1
  62. package/out/settings/api-keys/index.txt +6 -6
  63. package/out/settings/credentials/index.html +1 -1
  64. package/out/settings/credentials/index.txt +6 -6
  65. package/package.json +4 -4
  66. package/out/_next/static/I4FIhruRCEftVoflyg3Le/_buildManifest.js +0 -1
  67. package/out/_next/static/chunks/447.6d3368efa2d996b0.js +0 -1
  68. package/out/_next/static/chunks/app/agents/detail/page-7427483b9c74ad63.js +0 -1
  69. package/out/_next/static/chunks/app/agents/eval-run/page-a38dac74d1b3787d.js +0 -1
  70. package/out/_next/static/chunks/app/agents/eval-runs/page-af2fb33c0fce7934.js +0 -1
  71. package/out/_next/static/chunks/app/agents/evals/page-6bdc4b839a7a8eda.js +0 -1
  72. package/out/_next/static/chunks/app/agents/knowledge/page-0e02b14bfa2a6d04.js +0 -1
  73. package/out/_next/static/chunks/app/agents/page-99f179eb7c41ebd4.js +0 -1
  74. package/out/_next/static/chunks/app/agents/versions/page-c482d9bad8f35df6.js +0 -1
  75. package/out/_next/static/chunks/app/analytics/page-ca5d8c60e62118ed.js +0 -1
  76. package/out/_next/static/chunks/app/layout-b06d1caafc026d0c.js +0 -1
  77. package/out/_next/static/chunks/app/logs/page-1a7df17a605f36d3.js +0 -1
  78. package/out/_next/static/chunks/app/page-9e02cb0e8897ab5d.js +0 -1
  79. package/out/_next/static/chunks/app/playground/page-cb17c2ffaeb31b4e.js +0 -1
  80. package/out/_next/static/chunks/app/queue/page-6013b93817822c75.js +0 -1
  81. package/out/_next/static/chunks/app/sessions/page-add67d96ab66b690.js +0 -1
  82. package/out/_next/static/chunks/app/settings/credentials/page-ffe97ffb2f60229d.js +0 -1
  83. package/out/_next/static/css/ab505eeeff3f7df5.css +0 -1
  84. /package/out/_next/static/{I4FIhruRCEftVoflyg3Le → 7bfIRYNSIrLnufuHDu6SP}/_ssgManifest.js +0 -0
@@ -0,0 +1,59 @@
1
+ 'use client'
2
+
3
+ import { Suspense } from 'react'
4
+ import { useSearchParams } from 'next/navigation'
5
+ import Link from 'next/link'
6
+ import dynamic from 'next/dynamic'
7
+ import { getClient } from '@/lib/client'
8
+ import { ShimmerBlock } from '@/components/ui/shimmer'
9
+ import { ArrowLeft } from 'lucide-react'
10
+
11
+ const AgentConfigEditor = dynamic(
12
+ () => import('@ash-ai/ui').then((mod) => ({ default: mod.AgentConfigEditor })),
13
+ { ssr: false, loading: () => <ShimmerBlock height={200} /> }
14
+ )
15
+
16
+ function ConfigContent() {
17
+ const searchParams = useSearchParams()
18
+ const name = searchParams.get('name')
19
+
20
+ if (!name) {
21
+ return (
22
+ <div className="text-center py-16">
23
+ <p className="text-white/50">No agent name specified.</p>
24
+ <Link href="/agents" className="text-indigo-400 hover:text-indigo-300 text-sm mt-2 inline-block">
25
+ Back to agents
26
+ </Link>
27
+ </div>
28
+ )
29
+ }
30
+
31
+ return (
32
+ <div className="space-y-6">
33
+ <Link
34
+ href={`/agents/detail?name=${encodeURIComponent(name)}`}
35
+ className="inline-flex items-center gap-1.5 text-sm text-white/50 hover:text-white transition-colors"
36
+ >
37
+ <ArrowLeft className="h-4 w-4" />
38
+ Back to {name}
39
+ </Link>
40
+
41
+ <div>
42
+ <h1 className="text-2xl font-bold text-white">Configuration</h1>
43
+ <p className="mt-1 text-sm text-white/50">
44
+ Edit config for <span className="text-white/70">{name}</span>
45
+ </p>
46
+ </div>
47
+
48
+ <AgentConfigEditor client={getClient()} agentName={name} />
49
+ </div>
50
+ )
51
+ }
52
+
53
+ export default function ConfigPage() {
54
+ return (
55
+ <Suspense fallback={<ShimmerBlock height={200} />}>
56
+ <ConfigContent />
57
+ </Suspense>
58
+ )
59
+ }
@@ -16,6 +16,7 @@ import {
16
16
  FlaskConical,
17
17
  FolderOpen,
18
18
  Clock,
19
+ Settings,
19
20
  } from 'lucide-react'
20
21
  import type { Agent } from '@ash-ai/shared'
21
22
 
@@ -85,6 +86,12 @@ function AgentDetailContent() {
85
86
  }
86
87
 
87
88
  const tabs = [
89
+ {
90
+ label: 'Config',
91
+ href: `/agents/config?name=${encodeURIComponent(agent.name)}`,
92
+ icon: Settings,
93
+ description: 'View and edit agent configuration',
94
+ },
88
95
  {
89
96
  label: 'Versions',
90
97
  href: `/agents/versions?name=${encodeURIComponent(agent.name)}`,
@@ -154,7 +161,7 @@ function AgentDetailContent() {
154
161
  </Card>
155
162
 
156
163
  {/* Tab cards */}
157
- <div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
164
+ <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
158
165
  {tabs.map((tab) => (
159
166
  <Link key={tab.label} href={tab.href}>
160
167
  <Card className="hover:border-white/20 hover:bg-white/[0.02] transition-all cursor-pointer h-full">
@@ -1,88 +1,21 @@
1
1
  'use client'
2
2
 
3
- import { Suspense, useState } from 'react'
3
+ import { Suspense } from 'react'
4
4
  import { useSearchParams } from 'next/navigation'
5
5
  import Link from 'next/link'
6
- import { useEvalCases } from '@/lib/hooks'
6
+ import dynamic from 'next/dynamic'
7
7
  import { getClient } from '@/lib/client'
8
- import { Card, CardContent } from '@/components/ui/card'
9
- import { Button } from '@/components/ui/button'
10
- import { Input } from '@/components/ui/input'
11
- import { Badge } from '@/components/ui/badge'
12
- import { EmptyState } from '@/components/ui/empty-state'
13
8
  import { ShimmerBlock } from '@/components/ui/shimmer'
14
- import {
15
- ArrowLeft,
16
- FlaskConical,
17
- Plus,
18
- Trash2,
19
- Pencil,
20
- Download,
21
- Upload,
22
- Play,
23
- X,
24
- Loader2,
25
- ChevronDown,
26
- ChevronRight,
27
- } from 'lucide-react'
9
+ import { ArrowLeft } from 'lucide-react'
10
+
11
+ const AgentEvalRunner = dynamic(
12
+ () => import('@ash-ai/ui').then((mod) => ({ default: mod.AgentEvalRunner })),
13
+ { ssr: false, loading: () => <ShimmerBlock height={200} /> }
14
+ )
28
15
 
29
16
  function EvalsContent() {
30
17
  const searchParams = useSearchParams()
31
18
  const name = searchParams.get('name')
32
- const { cases, loading, refresh } = useEvalCases(name)
33
- const [showCreate, setShowCreate] = useState(false)
34
- const [editCase, setEditCase] = useState<any | null>(null)
35
- const [deleteConfirm, setDeleteConfirm] = useState<string | null>(null)
36
- const [expandedCase, setExpandedCase] = useState<string | null>(null)
37
- const [importing, setImporting] = useState(false)
38
- const [error, setError] = useState<string | null>(null)
39
-
40
- async function handleDelete(caseId: string) {
41
- if (!name) return
42
- setError(null)
43
- try {
44
- await getClient().deleteEvalCase(name, caseId)
45
- setDeleteConfirm(null)
46
- refresh()
47
- } catch (e) {
48
- setError(e instanceof Error ? e.message : 'Failed to delete eval case')
49
- setDeleteConfirm(null)
50
- }
51
- }
52
-
53
- async function handleExport() {
54
- if (!name) return
55
- setError(null)
56
- try {
57
- const data = await getClient().exportEvalCases(name)
58
- const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' })
59
- const url = URL.createObjectURL(blob)
60
- const a = document.createElement('a')
61
- a.href = url
62
- a.download = `${name}-eval-cases.json`
63
- a.click()
64
- URL.revokeObjectURL(url)
65
- } catch (e) {
66
- setError(e instanceof Error ? e.message : 'Failed to export eval cases')
67
- }
68
- }
69
-
70
- async function handleImport(fileList: FileList) {
71
- if (!name || fileList.length === 0) return
72
- setImporting(true)
73
- setError(null)
74
- try {
75
- const text = await fileList[0].text()
76
- const parsed = JSON.parse(text)
77
- const casesToImport = Array.isArray(parsed) ? parsed : parsed.cases || []
78
- await getClient().importEvalCases(name, casesToImport)
79
- refresh()
80
- } catch (e) {
81
- setError(e instanceof Error ? e.message : 'Failed to import eval cases')
82
- } finally {
83
- setImporting(false)
84
- }
85
- }
86
19
 
87
20
  if (!name) {
88
21
  return (
@@ -97,7 +30,6 @@ function EvalsContent() {
97
30
 
98
31
  return (
99
32
  <div className="space-y-6">
100
- {/* Back link */}
101
33
  <Link
102
34
  href={`/agents/detail?name=${encodeURIComponent(name)}`}
103
35
  className="inline-flex items-center gap-1.5 text-sm text-white/50 hover:text-white transition-colors"
@@ -106,335 +38,14 @@ function EvalsContent() {
106
38
  Back to {name}
107
39
  </Link>
108
40
 
109
- <div className="flex items-center justify-between">
110
- <div>
111
- <h1 className="text-2xl font-bold text-white">Eval Cases</h1>
112
- <p className="mt-1 text-sm text-white/50">
113
- Test cases for <span className="text-white/70">{name}</span>
114
- </p>
115
- </div>
116
- <div className="flex items-center gap-2">
117
- <Link href={`/agents/eval-runs?name=${encodeURIComponent(name)}`}>
118
- <Button variant="secondary">
119
- <Play className="h-4 w-4 mr-2" />
120
- Eval Runs
121
- </Button>
122
- </Link>
123
- <Button variant="secondary" onClick={handleExport}>
124
- <Download className="h-4 w-4 mr-2" />
125
- Export
126
- </Button>
127
- <label className="cursor-pointer">
128
- <span className="inline-flex items-center justify-center font-medium transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500/50 disabled:pointer-events-none disabled:opacity-50 border border-white/20 bg-white/5 text-white hover:bg-white/10 hover:border-white/30 h-9 px-4 text-sm rounded-xl">
129
- <Upload className="h-4 w-4 mr-2" />
130
- {importing ? 'Importing...' : 'Import'}
131
- </span>
132
- <input
133
- type="file"
134
- accept=".json"
135
- className="hidden"
136
- onChange={(e) => {
137
- if (e.target.files) {
138
- handleImport(e.target.files)
139
- e.target.value = ''
140
- }
141
- }}
142
- />
143
- </label>
144
- <Button onClick={() => setShowCreate(true)}>
145
- <Plus className="h-4 w-4 mr-2" />
146
- Add Case
147
- </Button>
148
- </div>
41
+ <div>
42
+ <h1 className="text-2xl font-bold text-white">Evaluations</h1>
43
+ <p className="mt-1 text-sm text-white/50">
44
+ Test cases and runs for <span className="text-white/70">{name}</span>
45
+ </p>
149
46
  </div>
150
47
 
151
- {error && (
152
- <div className="text-sm text-red-400 bg-red-500/10 border border-red-500/20 rounded-lg px-4 py-2">
153
- {error}
154
- </div>
155
- )}
156
-
157
- {loading ? (
158
- <div className="space-y-3">
159
- {[1, 2, 3].map((i) => (
160
- <ShimmerBlock key={i} height={70} />
161
- ))}
162
- </div>
163
- ) : cases.length === 0 ? (
164
- <EmptyState
165
- icon={<FlaskConical className="h-12 w-12" />}
166
- title="No eval cases yet"
167
- description="Create test cases to evaluate your agent's responses. Define questions, expected topics, and reference answers."
168
- action={
169
- <Button onClick={() => setShowCreate(true)}>
170
- <Plus className="h-4 w-4 mr-2" />
171
- Add Eval Case
172
- </Button>
173
- }
174
- />
175
- ) : (
176
- <div className="space-y-2">
177
- {cases.map((evalCase: any) => {
178
- const isExpanded = expandedCase === evalCase.id
179
- return (
180
- <Card key={evalCase.id}>
181
- <CardContent className="!py-3">
182
- <div className="flex items-center justify-between">
183
- <button
184
- onClick={() => setExpandedCase(isExpanded ? null : evalCase.id)}
185
- className="flex items-center gap-2 min-w-0 flex-1 text-left"
186
- >
187
- {isExpanded ? (
188
- <ChevronDown className="h-4 w-4 text-white/40 flex-shrink-0" />
189
- ) : (
190
- <ChevronRight className="h-4 w-4 text-white/40 flex-shrink-0" />
191
- )}
192
- <span className="text-sm text-white truncate">{evalCase.question}</span>
193
- </button>
194
- <div className="flex items-center gap-2 flex-shrink-0 ml-2">
195
- {evalCase.category && (
196
- <Badge variant="info">{evalCase.category}</Badge>
197
- )}
198
- {!evalCase.isActive && (
199
- <Badge variant="warning">Inactive</Badge>
200
- )}
201
- <Button
202
- variant="ghost"
203
- size="sm"
204
- onClick={() => setEditCase(evalCase)}
205
- className="text-white/30 hover:text-white"
206
- >
207
- <Pencil className="h-3.5 w-3.5" />
208
- </Button>
209
- <Button
210
- variant="ghost"
211
- size="sm"
212
- onClick={() => setDeleteConfirm(evalCase.id)}
213
- className="text-white/30 hover:text-red-400"
214
- >
215
- <Trash2 className="h-3.5 w-3.5" />
216
- </Button>
217
- </div>
218
- </div>
219
- {isExpanded && (
220
- <div className="mt-3 pt-3 border-t border-white/5 space-y-2">
221
- {evalCase.expectedTopics && evalCase.expectedTopics.length > 0 && (
222
- <div>
223
- <span className="text-xs font-medium text-white/40">Expected topics: </span>
224
- <span className="text-xs text-white/60">
225
- {evalCase.expectedTopics.join(', ')}
226
- </span>
227
- </div>
228
- )}
229
- {evalCase.expectedNotTopics && evalCase.expectedNotTopics.length > 0 && (
230
- <div>
231
- <span className="text-xs font-medium text-white/40">Should NOT mention: </span>
232
- <span className="text-xs text-white/60">
233
- {evalCase.expectedNotTopics.join(', ')}
234
- </span>
235
- </div>
236
- )}
237
- {evalCase.referenceAnswer && (
238
- <div>
239
- <span className="text-xs font-medium text-white/40">Reference answer: </span>
240
- <p className="text-xs text-white/60 mt-0.5">{evalCase.referenceAnswer}</p>
241
- </div>
242
- )}
243
- {evalCase.tags && evalCase.tags.length > 0 && (
244
- <div className="flex items-center gap-1 mt-1">
245
- {evalCase.tags.map((tag: string) => (
246
- <Badge key={tag} variant="default">{tag}</Badge>
247
- ))}
248
- </div>
249
- )}
250
- </div>
251
- )}
252
- </CardContent>
253
- </Card>
254
- )
255
- })}
256
- </div>
257
- )}
258
-
259
- {/* Create / Edit Modal */}
260
- {(showCreate || editCase) && (
261
- <EvalCaseModal
262
- agentName={name}
263
- evalCase={editCase}
264
- onClose={() => {
265
- setShowCreate(false)
266
- setEditCase(null)
267
- }}
268
- onSaved={() => {
269
- setShowCreate(false)
270
- setEditCase(null)
271
- refresh()
272
- }}
273
- />
274
- )}
275
-
276
- {/* Delete Confirmation */}
277
- {deleteConfirm && (
278
- <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
279
- <Card className="w-full max-w-md">
280
- <CardContent>
281
- <h3 className="text-lg font-semibold text-white mb-2">Delete Eval Case</h3>
282
- <p className="text-sm text-white/60 mb-6">
283
- Are you sure you want to delete this eval case? This action cannot be undone.
284
- </p>
285
- <div className="flex justify-end gap-3">
286
- <Button variant="ghost" onClick={() => setDeleteConfirm(null)}>
287
- Cancel
288
- </Button>
289
- <Button variant="danger" onClick={() => handleDelete(deleteConfirm)}>
290
- Delete
291
- </Button>
292
- </div>
293
- </CardContent>
294
- </Card>
295
- </div>
296
- )}
297
- </div>
298
- )
299
- }
300
-
301
- // ─── Eval Case Create/Edit Modal ───
302
-
303
- function EvalCaseModal({
304
- agentName,
305
- evalCase,
306
- onClose,
307
- onSaved,
308
- }: {
309
- agentName: string
310
- evalCase: any | null
311
- onClose: () => void
312
- onSaved: () => void
313
- }) {
314
- const isEdit = !!evalCase
315
- const [question, setQuestion] = useState(evalCase?.question || '')
316
- const [expectedTopics, setExpectedTopics] = useState(
317
- evalCase?.expectedTopics?.join(', ') || ''
318
- )
319
- const [expectedNotTopics, setExpectedNotTopics] = useState(
320
- evalCase?.expectedNotTopics?.join(', ') || ''
321
- )
322
- const [referenceAnswer, setReferenceAnswer] = useState(evalCase?.referenceAnswer || '')
323
- const [category, setCategory] = useState(evalCase?.category || '')
324
- const [tags, setTags] = useState(evalCase?.tags?.join(', ') || '')
325
- const [saving, setSaving] = useState(false)
326
- const [error, setError] = useState<string | null>(null)
327
-
328
- function parseList(s: string): string[] {
329
- return s
330
- .split(',')
331
- .map((t) => t.trim())
332
- .filter(Boolean)
333
- }
334
-
335
- async function handleSave() {
336
- if (!question.trim()) {
337
- setError('Question is required')
338
- return
339
- }
340
- setSaving(true)
341
- setError(null)
342
- try {
343
- const data = {
344
- question: question.trim(),
345
- expectedTopics: expectedTopics ? parseList(expectedTopics) : undefined,
346
- expectedNotTopics: expectedNotTopics ? parseList(expectedNotTopics) : undefined,
347
- referenceAnswer: referenceAnswer || undefined,
348
- category: category || undefined,
349
- tags: tags ? parseList(tags) : undefined,
350
- }
351
- if (isEdit) {
352
- await getClient().updateEvalCase(agentName, evalCase.id, data)
353
- } else {
354
- await getClient().createEvalCase(agentName, data)
355
- }
356
- onSaved()
357
- } catch (e) {
358
- setError(e instanceof Error ? e.message : 'Failed to save eval case')
359
- } finally {
360
- setSaving(false)
361
- }
362
- }
363
-
364
- return (
365
- <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
366
- <Card className="w-full max-w-lg max-h-[90vh] overflow-auto">
367
- <CardContent>
368
- <div className="flex items-center justify-between mb-6">
369
- <h2 className="text-lg font-semibold text-white">
370
- {isEdit ? 'Edit Eval Case' : 'Add Eval Case'}
371
- </h2>
372
- <button onClick={onClose} className="text-white/40 hover:text-white">
373
- <X className="h-5 w-5" />
374
- </button>
375
- </div>
376
-
377
- <div className="space-y-4">
378
- <div className="space-y-1.5">
379
- <label className="block text-sm font-medium text-white/70">Question</label>
380
- <textarea
381
- value={question}
382
- onChange={(e) => setQuestion(e.target.value)}
383
- rows={3}
384
- placeholder="What question should the agent answer?"
385
- className="flex w-full rounded-xl border px-3 py-2 text-sm bg-white/5 border-white/10 text-white placeholder:text-white/40 focus-visible:outline-none focus-visible:border-indigo-500/50 resize-none"
386
- />
387
- </div>
388
- <Input
389
- label="Expected Topics (comma-separated)"
390
- placeholder="e.g. pricing, features, support"
391
- value={expectedTopics}
392
- onChange={(e) => setExpectedTopics(e.target.value)}
393
- />
394
- <Input
395
- label="Should NOT Mention (comma-separated)"
396
- placeholder="e.g. competitor names, internal details"
397
- value={expectedNotTopics}
398
- onChange={(e) => setExpectedNotTopics(e.target.value)}
399
- />
400
- <div className="space-y-1.5">
401
- <label className="block text-sm font-medium text-white/70">
402
- Reference Answer (optional)
403
- </label>
404
- <textarea
405
- value={referenceAnswer}
406
- onChange={(e) => setReferenceAnswer(e.target.value)}
407
- rows={3}
408
- placeholder="The ideal answer for comparison..."
409
- className="flex w-full rounded-xl border px-3 py-2 text-sm bg-white/5 border-white/10 text-white placeholder:text-white/40 focus-visible:outline-none focus-visible:border-indigo-500/50 resize-none"
410
- />
411
- </div>
412
- <Input
413
- label="Category"
414
- placeholder="e.g. accuracy, safety, edge_case"
415
- value={category}
416
- onChange={(e) => setCategory(e.target.value)}
417
- />
418
- <Input
419
- label="Tags (comma-separated)"
420
- placeholder="e.g. regression, critical"
421
- value={tags}
422
- onChange={(e) => setTags(e.target.value)}
423
- />
424
-
425
- {error && <p className="text-sm text-red-400">{error}</p>}
426
-
427
- <div className="flex justify-end gap-3 pt-2">
428
- <Button variant="ghost" onClick={onClose}>
429
- Cancel
430
- </Button>
431
- <Button onClick={handleSave} disabled={saving}>
432
- {saving ? 'Saving...' : isEdit ? 'Update' : 'Create'}
433
- </Button>
434
- </div>
435
- </div>
436
- </CardContent>
437
- </Card>
48
+ <AgentEvalRunner client={getClient()} agentName={name} />
438
49
  </div>
439
50
  )
440
51
  }