@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.
- 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-9cd7b87bfba85ee1.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/I4FIhruRCEftVoflyg3Le/_buildManifest.js +0 -1
- 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/{I4FIhruRCEftVoflyg3Le → 7bfIRYNSIrLnufuHDu6SP}/_ssgManifest.js +0 -0
|
@@ -1,70 +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 { EmptyState } from '@/components/ui/empty-state'
|
|
11
8
|
import { ShimmerBlock } from '@/components/ui/shimmer'
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
FileText,
|
|
19
|
-
ChevronDown,
|
|
20
|
-
ChevronRight,
|
|
21
|
-
X,
|
|
22
|
-
Loader2,
|
|
23
|
-
} from 'lucide-react'
|
|
9
|
+
import { ArrowLeft } from 'lucide-react'
|
|
10
|
+
|
|
11
|
+
const AgentKnowledgeBase = dynamic(
|
|
12
|
+
() => import('@ash-ai/ui').then((mod) => ({ default: mod.AgentKnowledgeBase })),
|
|
13
|
+
{ ssr: false, loading: () => <ShimmerBlock height={200} /> }
|
|
14
|
+
)
|
|
24
15
|
|
|
25
16
|
function KnowledgeContent() {
|
|
26
17
|
const searchParams = useSearchParams()
|
|
27
18
|
const name = searchParams.get('name')
|
|
28
|
-
const { files, loading, refresh } = useAgentFiles(name)
|
|
29
|
-
const [expandedFile, setExpandedFile] = useState<string | null>(null)
|
|
30
|
-
const [deleting, setDeleting] = useState<string | null>(null)
|
|
31
|
-
const [uploading, setUploading] = useState(false)
|
|
32
|
-
const [error, setError] = useState<string | null>(null)
|
|
33
|
-
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
34
|
-
|
|
35
|
-
const handleUpload = useCallback(async (fileList: FileList) => {
|
|
36
|
-
if (!name) return
|
|
37
|
-
setUploading(true)
|
|
38
|
-
setError(null)
|
|
39
|
-
try {
|
|
40
|
-
const filesToUpload: Array<{ path: string; content: string }> = []
|
|
41
|
-
for (const file of Array.from(fileList)) {
|
|
42
|
-
const text = await file.text()
|
|
43
|
-
filesToUpload.push({ path: file.name, content: text })
|
|
44
|
-
}
|
|
45
|
-
await getClient().uploadAgentFiles(name, filesToUpload)
|
|
46
|
-
refresh()
|
|
47
|
-
} catch (e) {
|
|
48
|
-
setError(e instanceof Error ? e.message : 'Failed to upload files')
|
|
49
|
-
} finally {
|
|
50
|
-
setUploading(false)
|
|
51
|
-
}
|
|
52
|
-
}, [name, refresh])
|
|
53
|
-
|
|
54
|
-
async function handleDelete(filePath: string) {
|
|
55
|
-
if (!name) return
|
|
56
|
-
setDeleting(filePath)
|
|
57
|
-
setError(null)
|
|
58
|
-
try {
|
|
59
|
-
await getClient().deleteAgentFile(name, filePath)
|
|
60
|
-
setExpandedFile(null)
|
|
61
|
-
refresh()
|
|
62
|
-
} catch (e) {
|
|
63
|
-
setError(e instanceof Error ? e.message : 'Failed to delete file')
|
|
64
|
-
} finally {
|
|
65
|
-
setDeleting(null)
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
19
|
|
|
69
20
|
if (!name) {
|
|
70
21
|
return (
|
|
@@ -79,7 +30,6 @@ function KnowledgeContent() {
|
|
|
79
30
|
|
|
80
31
|
return (
|
|
81
32
|
<div className="space-y-6">
|
|
82
|
-
{/* Back link */}
|
|
83
33
|
<Link
|
|
84
34
|
href={`/agents/detail?name=${encodeURIComponent(name)}`}
|
|
85
35
|
className="inline-flex items-center gap-1.5 text-sm text-white/50 hover:text-white transition-colors"
|
|
@@ -88,107 +38,14 @@ function KnowledgeContent() {
|
|
|
88
38
|
Back to {name}
|
|
89
39
|
</Link>
|
|
90
40
|
|
|
91
|
-
<div
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
</p>
|
|
97
|
-
</div>
|
|
98
|
-
<Button onClick={() => fileInputRef.current?.click()} disabled={uploading}>
|
|
99
|
-
{uploading ? (
|
|
100
|
-
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
|
101
|
-
) : (
|
|
102
|
-
<Upload className="h-4 w-4 mr-2" />
|
|
103
|
-
)}
|
|
104
|
-
{uploading ? 'Uploading...' : 'Upload Files'}
|
|
105
|
-
</Button>
|
|
106
|
-
<input
|
|
107
|
-
ref={fileInputRef}
|
|
108
|
-
type="file"
|
|
109
|
-
multiple
|
|
110
|
-
className="hidden"
|
|
111
|
-
onChange={(e) => {
|
|
112
|
-
if (e.target.files && e.target.files.length > 0) {
|
|
113
|
-
handleUpload(e.target.files)
|
|
114
|
-
e.target.value = ''
|
|
115
|
-
}
|
|
116
|
-
}}
|
|
117
|
-
/>
|
|
41
|
+
<div>
|
|
42
|
+
<h1 className="text-2xl font-bold text-white">Knowledge Base</h1>
|
|
43
|
+
<p className="mt-1 text-sm text-white/50">
|
|
44
|
+
Manage files for <span className="text-white/70">{name}</span>
|
|
45
|
+
</p>
|
|
118
46
|
</div>
|
|
119
47
|
|
|
120
|
-
{
|
|
121
|
-
<div className="text-sm text-red-400 bg-red-500/10 border border-red-500/20 rounded-lg px-4 py-2">
|
|
122
|
-
{error}
|
|
123
|
-
</div>
|
|
124
|
-
)}
|
|
125
|
-
|
|
126
|
-
{loading ? (
|
|
127
|
-
<div className="space-y-3">
|
|
128
|
-
{[1, 2, 3].map((i) => (
|
|
129
|
-
<ShimmerBlock key={i} height={60} />
|
|
130
|
-
))}
|
|
131
|
-
</div>
|
|
132
|
-
) : files.length === 0 ? (
|
|
133
|
-
<EmptyState
|
|
134
|
-
icon={<BookOpen className="h-12 w-12" />}
|
|
135
|
-
title="No files yet"
|
|
136
|
-
description="Upload knowledge base files for your agent. These files will be available in the agent's working directory."
|
|
137
|
-
action={
|
|
138
|
-
<Button onClick={() => fileInputRef.current?.click()}>
|
|
139
|
-
<Upload className="h-4 w-4 mr-2" />
|
|
140
|
-
Upload Files
|
|
141
|
-
</Button>
|
|
142
|
-
}
|
|
143
|
-
/>
|
|
144
|
-
) : (
|
|
145
|
-
<div className="space-y-2">
|
|
146
|
-
{files.map((file: any) => {
|
|
147
|
-
const filePath = typeof file === 'string' ? file : file.path || file.name
|
|
148
|
-
const isExpanded = expandedFile === filePath
|
|
149
|
-
return (
|
|
150
|
-
<Card key={filePath}>
|
|
151
|
-
<CardContent className="!py-3">
|
|
152
|
-
<div className="flex items-center justify-between">
|
|
153
|
-
<button
|
|
154
|
-
onClick={() => setExpandedFile(isExpanded ? null : filePath)}
|
|
155
|
-
className="flex items-center gap-2 min-w-0 flex-1 text-left"
|
|
156
|
-
>
|
|
157
|
-
{isExpanded ? (
|
|
158
|
-
<ChevronDown className="h-4 w-4 text-white/40 flex-shrink-0" />
|
|
159
|
-
) : (
|
|
160
|
-
<ChevronRight className="h-4 w-4 text-white/40 flex-shrink-0" />
|
|
161
|
-
)}
|
|
162
|
-
<FileText className="h-4 w-4 text-indigo-400 flex-shrink-0" />
|
|
163
|
-
<span className="text-sm text-white truncate">{filePath}</span>
|
|
164
|
-
</button>
|
|
165
|
-
<Button
|
|
166
|
-
variant="ghost"
|
|
167
|
-
size="sm"
|
|
168
|
-
onClick={() => handleDelete(filePath)}
|
|
169
|
-
disabled={deleting === filePath}
|
|
170
|
-
className="text-white/30 hover:text-red-400 flex-shrink-0 ml-2"
|
|
171
|
-
>
|
|
172
|
-
{deleting === filePath ? (
|
|
173
|
-
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
|
174
|
-
) : (
|
|
175
|
-
<Trash2 className="h-3.5 w-3.5" />
|
|
176
|
-
)}
|
|
177
|
-
</Button>
|
|
178
|
-
</div>
|
|
179
|
-
{isExpanded && typeof file === 'object' && file.content && (
|
|
180
|
-
<div className="mt-3 pt-3 border-t border-white/5">
|
|
181
|
-
<pre className="text-xs text-white/60 bg-black/20 rounded-lg p-3 overflow-x-auto max-h-64 overflow-y-auto whitespace-pre-wrap">
|
|
182
|
-
{file.content}
|
|
183
|
-
</pre>
|
|
184
|
-
</div>
|
|
185
|
-
)}
|
|
186
|
-
</CardContent>
|
|
187
|
-
</Card>
|
|
188
|
-
)
|
|
189
|
-
})}
|
|
190
|
-
</div>
|
|
191
|
-
)}
|
|
48
|
+
<AgentKnowledgeBase client={getClient()} agentName={name} />
|
|
192
49
|
</div>
|
|
193
50
|
)
|
|
194
51
|
}
|
package/app/agents/page.tsx
CHANGED
|
@@ -12,10 +12,14 @@ import { ShimmerBlock } from '@/components/ui/shimmer'
|
|
|
12
12
|
import { formatRelativeTime } from '@/lib/utils'
|
|
13
13
|
import {
|
|
14
14
|
Bot,
|
|
15
|
+
BookOpen,
|
|
15
16
|
Code2,
|
|
16
17
|
Copy,
|
|
18
|
+
FlaskConical,
|
|
19
|
+
GitBranch,
|
|
17
20
|
MoreVertical,
|
|
18
21
|
Plus,
|
|
22
|
+
Settings,
|
|
19
23
|
Terminal,
|
|
20
24
|
Trash2,
|
|
21
25
|
Upload,
|
|
@@ -168,7 +172,36 @@ function AgentCard({
|
|
|
168
172
|
{showMenu && (
|
|
169
173
|
<>
|
|
170
174
|
<div className="fixed inset-0" onClick={() => setShowMenu(false)} />
|
|
171
|
-
<div className="absolute right-0 mt-1 w-
|
|
175
|
+
<div className="absolute right-0 mt-1 w-44 rounded-lg border border-white/10 bg-[#1c2129] shadow-xl z-10 py-1">
|
|
176
|
+
<Link
|
|
177
|
+
href={`/agents/config?name=${encodeURIComponent(agent.name)}`}
|
|
178
|
+
onClick={() => setShowMenu(false)}
|
|
179
|
+
className="flex w-full items-center gap-2 px-3 py-2 text-sm text-white/70 hover:bg-white/5"
|
|
180
|
+
>
|
|
181
|
+
<Settings className="h-3.5 w-3.5" /> Config
|
|
182
|
+
</Link>
|
|
183
|
+
<Link
|
|
184
|
+
href={`/agents/versions?name=${encodeURIComponent(agent.name)}`}
|
|
185
|
+
onClick={() => setShowMenu(false)}
|
|
186
|
+
className="flex w-full items-center gap-2 px-3 py-2 text-sm text-white/70 hover:bg-white/5"
|
|
187
|
+
>
|
|
188
|
+
<GitBranch className="h-3.5 w-3.5" /> Versions
|
|
189
|
+
</Link>
|
|
190
|
+
<Link
|
|
191
|
+
href={`/agents/knowledge?name=${encodeURIComponent(agent.name)}`}
|
|
192
|
+
onClick={() => setShowMenu(false)}
|
|
193
|
+
className="flex w-full items-center gap-2 px-3 py-2 text-sm text-white/70 hover:bg-white/5"
|
|
194
|
+
>
|
|
195
|
+
<BookOpen className="h-3.5 w-3.5" /> Knowledge
|
|
196
|
+
</Link>
|
|
197
|
+
<Link
|
|
198
|
+
href={`/agents/evals?name=${encodeURIComponent(agent.name)}`}
|
|
199
|
+
onClick={() => setShowMenu(false)}
|
|
200
|
+
className="flex w-full items-center gap-2 px-3 py-2 text-sm text-white/70 hover:bg-white/5"
|
|
201
|
+
>
|
|
202
|
+
<FlaskConical className="h-3.5 w-3.5" /> Evals
|
|
203
|
+
</Link>
|
|
204
|
+
<div className="my-1 border-t border-white/5" />
|
|
172
205
|
<button
|
|
173
206
|
onClick={() => {
|
|
174
207
|
navigator.clipboard.writeText(agent.name)
|
|
@@ -1,47 +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
|
-
Loader2,
|
|
21
|
-
X,
|
|
22
|
-
} from 'lucide-react'
|
|
9
|
+
import { ArrowLeft } from 'lucide-react'
|
|
10
|
+
|
|
11
|
+
const AgentVersionManager = dynamic(
|
|
12
|
+
() => import('@ash-ai/ui').then((mod) => ({ default: mod.AgentVersionManager })),
|
|
13
|
+
{ ssr: false, loading: () => <ShimmerBlock height={200} /> }
|
|
14
|
+
)
|
|
23
15
|
|
|
24
16
|
function VersionsContent() {
|
|
25
17
|
const searchParams = useSearchParams()
|
|
26
18
|
const name = searchParams.get('name')
|
|
27
|
-
const { versions, loading, refresh } = useAgentVersions(name)
|
|
28
|
-
const [showCreate, setShowCreate] = useState(false)
|
|
29
|
-
const [activating, setActivating] = useState<number | null>(null)
|
|
30
|
-
const [error, setError] = useState<string | null>(null)
|
|
31
|
-
|
|
32
|
-
async function handleActivate(versionNumber: number) {
|
|
33
|
-
if (!name) return
|
|
34
|
-
setActivating(versionNumber)
|
|
35
|
-
setError(null)
|
|
36
|
-
try {
|
|
37
|
-
await getClient().activateAgentVersion(name, versionNumber)
|
|
38
|
-
refresh()
|
|
39
|
-
} catch (e) {
|
|
40
|
-
setError(e instanceof Error ? e.message : 'Failed to activate version')
|
|
41
|
-
} finally {
|
|
42
|
-
setActivating(null)
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
19
|
|
|
46
20
|
if (!name) {
|
|
47
21
|
return (
|
|
@@ -56,7 +30,6 @@ function VersionsContent() {
|
|
|
56
30
|
|
|
57
31
|
return (
|
|
58
32
|
<div className="space-y-6">
|
|
59
|
-
{/* Back link */}
|
|
60
33
|
<Link
|
|
61
34
|
href={`/agents/detail?name=${encodeURIComponent(name)}`}
|
|
62
35
|
className="inline-flex items-center gap-1.5 text-sm text-white/50 hover:text-white transition-colors"
|
|
@@ -65,208 +38,14 @@ function VersionsContent() {
|
|
|
65
38
|
Back to {name}
|
|
66
39
|
</Link>
|
|
67
40
|
|
|
68
|
-
<div
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
</p>
|
|
74
|
-
</div>
|
|
75
|
-
<Button onClick={() => setShowCreate(true)}>
|
|
76
|
-
<Plus className="h-4 w-4 mr-2" />
|
|
77
|
-
Create Version
|
|
78
|
-
</Button>
|
|
41
|
+
<div>
|
|
42
|
+
<h1 className="text-2xl font-bold text-white">Versions</h1>
|
|
43
|
+
<p className="mt-1 text-sm text-white/50">
|
|
44
|
+
Manage versions for <span className="text-white/70">{name}</span>
|
|
45
|
+
</p>
|
|
79
46
|
</div>
|
|
80
47
|
|
|
81
|
-
{
|
|
82
|
-
<div className="text-sm text-red-400 bg-red-500/10 border border-red-500/20 rounded-lg px-4 py-2">
|
|
83
|
-
{error}
|
|
84
|
-
</div>
|
|
85
|
-
)}
|
|
86
|
-
|
|
87
|
-
{loading ? (
|
|
88
|
-
<div className="space-y-3">
|
|
89
|
-
{[1, 2, 3].map((i) => (
|
|
90
|
-
<ShimmerBlock key={i} height={80} />
|
|
91
|
-
))}
|
|
92
|
-
</div>
|
|
93
|
-
) : versions.length === 0 ? (
|
|
94
|
-
<EmptyState
|
|
95
|
-
icon={<GitBranch className="h-12 w-12" />}
|
|
96
|
-
title="No versions yet"
|
|
97
|
-
description="Create your first version to snapshot the agent's configuration."
|
|
98
|
-
action={
|
|
99
|
-
<Button onClick={() => setShowCreate(true)}>
|
|
100
|
-
<Plus className="h-4 w-4 mr-2" />
|
|
101
|
-
Create Version
|
|
102
|
-
</Button>
|
|
103
|
-
}
|
|
104
|
-
/>
|
|
105
|
-
) : (
|
|
106
|
-
<div className="space-y-3">
|
|
107
|
-
{versions.map((version) => (
|
|
108
|
-
<Card key={version.id}>
|
|
109
|
-
<CardContent>
|
|
110
|
-
<div className="flex items-center justify-between">
|
|
111
|
-
<div className="min-w-0 flex-1">
|
|
112
|
-
<div className="flex items-center gap-2">
|
|
113
|
-
<h3 className="text-sm font-semibold text-white">
|
|
114
|
-
v{version.versionNumber}
|
|
115
|
-
</h3>
|
|
116
|
-
{version.name && (
|
|
117
|
-
<span className="text-sm text-white/50">{version.name}</span>
|
|
118
|
-
)}
|
|
119
|
-
{version.isActive && (
|
|
120
|
-
<Badge variant="success">
|
|
121
|
-
<CheckCircle2 className="h-3 w-3 mr-1" />
|
|
122
|
-
Active
|
|
123
|
-
</Badge>
|
|
124
|
-
)}
|
|
125
|
-
</div>
|
|
126
|
-
{version.releaseNotes && (
|
|
127
|
-
<p className="text-xs text-white/40 mt-1">{version.releaseNotes}</p>
|
|
128
|
-
)}
|
|
129
|
-
<div className="flex items-center gap-3 mt-2">
|
|
130
|
-
<span className="text-xs text-white/30">
|
|
131
|
-
{formatRelativeTime(version.createdAt)}
|
|
132
|
-
</span>
|
|
133
|
-
{version.systemPrompt && (
|
|
134
|
-
<span className="text-xs text-white/30">Has system prompt</span>
|
|
135
|
-
)}
|
|
136
|
-
{version.knowledgeFiles && version.knowledgeFiles.length > 0 && (
|
|
137
|
-
<span className="text-xs text-white/30">
|
|
138
|
-
{version.knowledgeFiles.length} knowledge file{version.knowledgeFiles.length !== 1 ? 's' : ''}
|
|
139
|
-
</span>
|
|
140
|
-
)}
|
|
141
|
-
</div>
|
|
142
|
-
</div>
|
|
143
|
-
{!version.isActive && (
|
|
144
|
-
<Button
|
|
145
|
-
variant="secondary"
|
|
146
|
-
size="sm"
|
|
147
|
-
onClick={() => handleActivate(version.versionNumber)}
|
|
148
|
-
disabled={activating === version.versionNumber}
|
|
149
|
-
>
|
|
150
|
-
{activating === version.versionNumber ? (
|
|
151
|
-
<Loader2 className="h-3 w-3 animate-spin mr-1" />
|
|
152
|
-
) : (
|
|
153
|
-
<CheckCircle2 className="h-3 w-3 mr-1" />
|
|
154
|
-
)}
|
|
155
|
-
Activate
|
|
156
|
-
</Button>
|
|
157
|
-
)}
|
|
158
|
-
</div>
|
|
159
|
-
</CardContent>
|
|
160
|
-
</Card>
|
|
161
|
-
))}
|
|
162
|
-
</div>
|
|
163
|
-
)}
|
|
164
|
-
|
|
165
|
-
{/* Create Version Modal */}
|
|
166
|
-
{showCreate && (
|
|
167
|
-
<CreateVersionModal
|
|
168
|
-
agentName={name}
|
|
169
|
-
onClose={() => setShowCreate(false)}
|
|
170
|
-
onCreated={() => {
|
|
171
|
-
setShowCreate(false)
|
|
172
|
-
refresh()
|
|
173
|
-
}}
|
|
174
|
-
/>
|
|
175
|
-
)}
|
|
176
|
-
</div>
|
|
177
|
-
)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// ─── Create Version Modal ───
|
|
181
|
-
|
|
182
|
-
function CreateVersionModal({
|
|
183
|
-
agentName,
|
|
184
|
-
onClose,
|
|
185
|
-
onCreated,
|
|
186
|
-
}: {
|
|
187
|
-
agentName: string
|
|
188
|
-
onClose: () => void
|
|
189
|
-
onCreated: () => void
|
|
190
|
-
}) {
|
|
191
|
-
const [versionName, setVersionName] = useState('')
|
|
192
|
-
const [systemPrompt, setSystemPrompt] = useState('')
|
|
193
|
-
const [releaseNotes, setReleaseNotes] = useState('')
|
|
194
|
-
const [creating, setCreating] = useState(false)
|
|
195
|
-
const [error, setError] = useState<string | null>(null)
|
|
196
|
-
|
|
197
|
-
async function handleCreate() {
|
|
198
|
-
setCreating(true)
|
|
199
|
-
setError(null)
|
|
200
|
-
try {
|
|
201
|
-
await getClient().createAgentVersion(agentName, {
|
|
202
|
-
name: versionName || undefined,
|
|
203
|
-
systemPrompt: systemPrompt || undefined,
|
|
204
|
-
releaseNotes: releaseNotes || undefined,
|
|
205
|
-
})
|
|
206
|
-
onCreated()
|
|
207
|
-
} catch (e) {
|
|
208
|
-
setError(e instanceof Error ? e.message : 'Failed to create version')
|
|
209
|
-
} finally {
|
|
210
|
-
setCreating(false)
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return (
|
|
215
|
-
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
|
|
216
|
-
<Card className="w-full max-w-lg max-h-[90vh] overflow-auto">
|
|
217
|
-
<CardContent>
|
|
218
|
-
<div className="flex items-center justify-between mb-6">
|
|
219
|
-
<h2 className="text-lg font-semibold text-white">Create Version</h2>
|
|
220
|
-
<button onClick={onClose} className="text-white/40 hover:text-white">
|
|
221
|
-
<X className="h-5 w-5" />
|
|
222
|
-
</button>
|
|
223
|
-
</div>
|
|
224
|
-
|
|
225
|
-
<div className="space-y-4">
|
|
226
|
-
<Input
|
|
227
|
-
label="Version Name (optional)"
|
|
228
|
-
placeholder="e.g. v2 - improved grounding"
|
|
229
|
-
value={versionName}
|
|
230
|
-
onChange={(e) => setVersionName(e.target.value)}
|
|
231
|
-
/>
|
|
232
|
-
<div className="space-y-1.5">
|
|
233
|
-
<label className="block text-sm font-medium text-white/70">
|
|
234
|
-
System Prompt (optional)
|
|
235
|
-
</label>
|
|
236
|
-
<textarea
|
|
237
|
-
value={systemPrompt}
|
|
238
|
-
onChange={(e) => setSystemPrompt(e.target.value)}
|
|
239
|
-
rows={4}
|
|
240
|
-
placeholder="System prompt for this version..."
|
|
241
|
-
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"
|
|
242
|
-
/>
|
|
243
|
-
</div>
|
|
244
|
-
<div className="space-y-1.5">
|
|
245
|
-
<label className="block text-sm font-medium text-white/70">
|
|
246
|
-
Release Notes (optional)
|
|
247
|
-
</label>
|
|
248
|
-
<textarea
|
|
249
|
-
value={releaseNotes}
|
|
250
|
-
onChange={(e) => setReleaseNotes(e.target.value)}
|
|
251
|
-
rows={2}
|
|
252
|
-
placeholder="What changed in this version..."
|
|
253
|
-
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"
|
|
254
|
-
/>
|
|
255
|
-
</div>
|
|
256
|
-
|
|
257
|
-
{error && <p className="text-sm text-red-400">{error}</p>}
|
|
258
|
-
|
|
259
|
-
<div className="flex justify-end gap-3 pt-2">
|
|
260
|
-
<Button variant="ghost" onClick={onClose}>
|
|
261
|
-
Cancel
|
|
262
|
-
</Button>
|
|
263
|
-
<Button onClick={handleCreate} disabled={creating}>
|
|
264
|
-
{creating ? 'Creating...' : 'Create Version'}
|
|
265
|
-
</Button>
|
|
266
|
-
</div>
|
|
267
|
-
</div>
|
|
268
|
-
</CardContent>
|
|
269
|
-
</Card>
|
|
48
|
+
<AgentVersionManager client={getClient()} agentName={name} />
|
|
270
49
|
</div>
|
|
271
50
|
)
|
|
272
51
|
}
|
package/app/sessions/page.tsx
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
MessageSquare,
|
|
22
22
|
Pause,
|
|
23
23
|
Play,
|
|
24
|
+
Plug,
|
|
24
25
|
Square,
|
|
25
26
|
Terminal,
|
|
26
27
|
} from 'lucide-react'
|
|
@@ -244,6 +245,7 @@ function SessionDetail({ session }: { session: Session }) {
|
|
|
244
245
|
{formatRelativeTime(session.createdAt)}
|
|
245
246
|
</span>
|
|
246
247
|
{session.model && <Badge variant="info">{session.model}</Badge>}
|
|
248
|
+
<McpStatusBadge events={events} config={session.config} />
|
|
247
249
|
</div>
|
|
248
250
|
</div>
|
|
249
251
|
<div className="flex items-center gap-2">
|
|
@@ -478,6 +480,7 @@ function EventsTab({ events }: { events: SessionEvent[] }) {
|
|
|
478
480
|
error: 'text-red-400',
|
|
479
481
|
turn_complete: 'text-green-400',
|
|
480
482
|
lifecycle: 'text-zinc-400',
|
|
483
|
+
mcp_status: 'text-cyan-400',
|
|
481
484
|
}
|
|
482
485
|
|
|
483
486
|
return (
|
|
@@ -505,7 +508,11 @@ function EventRow({
|
|
|
505
508
|
typeof event.data === 'string' ? JSON.parse(event.data) : event.data
|
|
506
509
|
if (data && typeof data === 'object') {
|
|
507
510
|
const d = data as Record<string, unknown>
|
|
508
|
-
if (typeof d.
|
|
511
|
+
if (typeof d.action === 'string' && d.action === 'configured' && Array.isArray(d.servers)) {
|
|
512
|
+
summary = `${d.servers.length} server(s): ${(d.servers as Array<{name?: string}>).map(s => s.name).join(', ')}`
|
|
513
|
+
} else if (typeof d.action === 'string' && d.action === 'error' && typeof d.error === 'string') {
|
|
514
|
+
summary = `MCP error: ${d.error.slice(0, 80)}`
|
|
515
|
+
} else if (typeof d.text === 'string') summary = d.text.slice(0, 100)
|
|
509
516
|
else if (typeof d.name === 'string') summary = d.name
|
|
510
517
|
else if (typeof d.error === 'string') summary = d.error.slice(0, 100)
|
|
511
518
|
}
|
|
@@ -539,6 +546,49 @@ function EventRow({
|
|
|
539
546
|
)
|
|
540
547
|
}
|
|
541
548
|
|
|
549
|
+
// ─── MCP Status Badge ───
|
|
550
|
+
|
|
551
|
+
function McpStatusBadge({ events, config }: { events: SessionEvent[]; config?: { mcpServers?: Record<string, unknown> } | null }) {
|
|
552
|
+
const mcpEvents = events.filter(e => e.type === 'mcp_status')
|
|
553
|
+
const mcpServers = config?.mcpServers
|
|
554
|
+
|
|
555
|
+
// No MCP configured at all
|
|
556
|
+
if (!mcpServers && mcpEvents.length === 0) return null
|
|
557
|
+
|
|
558
|
+
const hasError = mcpEvents.some(e => {
|
|
559
|
+
try {
|
|
560
|
+
const d = typeof e.data === 'string' ? JSON.parse(e.data) : e.data
|
|
561
|
+
return d?.action === 'error'
|
|
562
|
+
} catch { return false }
|
|
563
|
+
})
|
|
564
|
+
|
|
565
|
+
const serverCount = mcpServers ? Object.keys(mcpServers).length : 0
|
|
566
|
+
const configuredEvent = mcpEvents.find(e => {
|
|
567
|
+
try {
|
|
568
|
+
const d = typeof e.data === 'string' ? JSON.parse(e.data) : e.data
|
|
569
|
+
return d?.action === 'configured'
|
|
570
|
+
} catch { return false }
|
|
571
|
+
})
|
|
572
|
+
const configuredCount = configuredEvent ? (() => {
|
|
573
|
+
try {
|
|
574
|
+
const d = typeof configuredEvent.data === 'string' ? JSON.parse(configuredEvent.data) : configuredEvent.data
|
|
575
|
+
return d?.servers?.length ?? 0
|
|
576
|
+
} catch { return 0 }
|
|
577
|
+
})() : serverCount
|
|
578
|
+
|
|
579
|
+
return (
|
|
580
|
+
<span className={cn(
|
|
581
|
+
'inline-flex items-center gap-1.5 px-2 py-0.5 rounded text-xs font-medium',
|
|
582
|
+
hasError
|
|
583
|
+
? 'bg-red-500/10 text-red-400 border border-red-500/20'
|
|
584
|
+
: 'bg-cyan-500/10 text-cyan-400 border border-cyan-500/20'
|
|
585
|
+
)}>
|
|
586
|
+
<Plug className="h-3 w-3" />
|
|
587
|
+
{hasError ? `MCP Error` : `MCP: ${configuredCount} server${configuredCount !== 1 ? 's' : ''}`}
|
|
588
|
+
</span>
|
|
589
|
+
)
|
|
590
|
+
}
|
|
591
|
+
|
|
542
592
|
// ─── Terminal Tab ───
|
|
543
593
|
|
|
544
594
|
function TerminalTab({ logs }: { logs: string[] }) {
|