@ash-ai/dashboard 0.0.7 → 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.
- package/app/agents/config/page.tsx +59 -0
- package/app/agents/detail/page.tsx +8 -1
- package/app/agents/evals/page.tsx +14 -403
- package/app/agents/knowledge/page.tsx +14 -157
- package/app/agents/page.tsx +34 -1
- package/app/agents/versions/page.tsx +14 -235
- package/app/sessions/page.tsx +51 -1
- package/out/404/index.html +1 -1
- package/out/404.html +1 -1
- package/out/_next/static/7bfIRYNSIrLnufuHDu6SP/_buildManifest.js +1 -0
- package/out/_next/static/chunks/447.ff2905689aee4ecc.js +1 -0
- package/out/_next/static/chunks/{929-b631fe082fe4e852.js → 929-f69b424a1300c015.js} +1 -1
- package/out/_next/static/chunks/app/agents/config/page-bd4b1aa9c56ac3bf.js +1 -0
- package/out/_next/static/chunks/app/agents/detail/page-7ddf96bb1bf30c84.js +1 -0
- package/out/_next/static/chunks/app/agents/eval-run/page-eb21414673ee3a61.js +1 -0
- package/out/_next/static/chunks/app/agents/eval-runs/page-4737a360beffdd17.js +1 -0
- package/out/_next/static/chunks/app/agents/evals/page-e764fd7f7c4e7dd0.js +1 -0
- package/out/_next/static/chunks/app/agents/knowledge/page-825e40e50aeca7b4.js +1 -0
- package/out/_next/static/chunks/app/agents/page-c70b7f8a4639d430.js +1 -0
- package/out/_next/static/chunks/app/agents/versions/page-16851d0f7e5c442c.js +1 -0
- package/out/_next/static/chunks/app/analytics/page-a1d93f279dc37c76.js +1 -0
- package/out/_next/static/chunks/app/layout-ccc59eb27dd73234.js +1 -0
- package/out/_next/static/chunks/app/logs/page-a47c75fb5c10da47.js +1 -0
- package/out/_next/static/chunks/app/page-8ce5c070587b5731.js +1 -0
- package/out/_next/static/chunks/app/playground/page-0fe012a6d6f00326.js +1 -0
- package/out/_next/static/chunks/app/queue/page-8413b5c0878aa6eb.js +1 -0
- package/out/_next/static/chunks/app/sessions/page-aac6f065bbb5b2ea.js +1 -0
- package/out/_next/static/chunks/app/settings/credentials/page-9ab78fa32e44448a.js +1 -0
- package/out/_next/static/chunks/{webpack-a50a78a04aed446d.js → webpack-2608afc863921e98.js} +1 -1
- package/out/_next/static/css/2c57b4d6ae47f2bd.css +1 -0
- package/out/agents/config/index.html +1 -0
- package/out/agents/config/index.txt +22 -0
- package/out/agents/detail/index.html +1 -1
- package/out/agents/detail/index.txt +6 -6
- package/out/agents/eval-compare/index.html +1 -1
- package/out/agents/eval-compare/index.txt +6 -6
- package/out/agents/eval-run/index.html +1 -1
- package/out/agents/eval-run/index.txt +6 -6
- package/out/agents/eval-runs/index.html +1 -1
- package/out/agents/eval-runs/index.txt +6 -6
- package/out/agents/evals/index.html +1 -1
- package/out/agents/evals/index.txt +6 -6
- package/out/agents/index.html +1 -1
- package/out/agents/index.txt +6 -6
- package/out/agents/knowledge/index.html +1 -1
- package/out/agents/knowledge/index.txt +6 -6
- package/out/agents/versions/index.html +1 -1
- package/out/agents/versions/index.txt +6 -6
- package/out/analytics/index.html +1 -1
- package/out/analytics/index.txt +6 -6
- package/out/index.html +1 -1
- package/out/index.txt +6 -6
- package/out/logs/index.html +1 -1
- package/out/logs/index.txt +6 -6
- package/out/playground/index.html +1 -1
- package/out/playground/index.txt +6 -6
- package/out/queue/index.html +1 -1
- package/out/queue/index.txt +6 -6
- package/out/sessions/index.html +1 -1
- package/out/sessions/index.txt +6 -6
- package/out/settings/api-keys/index.html +1 -1
- package/out/settings/api-keys/index.txt +6 -6
- package/out/settings/credentials/index.html +1 -1
- package/out/settings/credentials/index.txt +6 -6
- package/package.json +4 -4
- package/out/_next/static/chunks/447.6d3368efa2d996b0.js +0 -1
- package/out/_next/static/chunks/app/agents/detail/page-7427483b9c74ad63.js +0 -1
- package/out/_next/static/chunks/app/agents/eval-run/page-a38dac74d1b3787d.js +0 -1
- package/out/_next/static/chunks/app/agents/eval-runs/page-af2fb33c0fce7934.js +0 -1
- package/out/_next/static/chunks/app/agents/evals/page-6bdc4b839a7a8eda.js +0 -1
- package/out/_next/static/chunks/app/agents/knowledge/page-0e02b14bfa2a6d04.js +0 -1
- package/out/_next/static/chunks/app/agents/page-99f179eb7c41ebd4.js +0 -1
- package/out/_next/static/chunks/app/agents/versions/page-c482d9bad8f35df6.js +0 -1
- package/out/_next/static/chunks/app/analytics/page-ca5d8c60e62118ed.js +0 -1
- package/out/_next/static/chunks/app/layout-b06d1caafc026d0c.js +0 -1
- package/out/_next/static/chunks/app/logs/page-1a7df17a605f36d3.js +0 -1
- package/out/_next/static/chunks/app/page-9e02cb0e8897ab5d.js +0 -1
- package/out/_next/static/chunks/app/playground/page-cb17c2ffaeb31b4e.js +0 -1
- package/out/_next/static/chunks/app/queue/page-6013b93817822c75.js +0 -1
- package/out/_next/static/chunks/app/sessions/page-add67d96ab66b690.js +0 -1
- package/out/_next/static/chunks/app/settings/credentials/page-ffe97ffb2f60229d.js +0 -1
- package/out/_next/static/css/ab505eeeff3f7df5.css +0 -1
- package/out/_next/static/sXYgh3eUKXRKt1T_1T3tk/_buildManifest.js +0 -1
- /package/out/_next/static/{sXYgh3eUKXRKt1T_1T3tk → 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-
|
|
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
|
|
3
|
+
import { Suspense } from 'react'
|
|
4
4
|
import { useSearchParams } from 'next/navigation'
|
|
5
5
|
import Link from 'next/link'
|
|
6
|
-
import
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
110
|
-
<
|
|
111
|
-
|
|
112
|
-
<
|
|
113
|
-
|
|
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
|
-
{
|
|
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
|
}
|