@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.
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-b631fe082fe4e852.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/chunks/447.6d3368efa2d996b0.js +0 -1
  67. package/out/_next/static/chunks/app/agents/detail/page-7427483b9c74ad63.js +0 -1
  68. package/out/_next/static/chunks/app/agents/eval-run/page-a38dac74d1b3787d.js +0 -1
  69. package/out/_next/static/chunks/app/agents/eval-runs/page-af2fb33c0fce7934.js +0 -1
  70. package/out/_next/static/chunks/app/agents/evals/page-6bdc4b839a7a8eda.js +0 -1
  71. package/out/_next/static/chunks/app/agents/knowledge/page-0e02b14bfa2a6d04.js +0 -1
  72. package/out/_next/static/chunks/app/agents/page-99f179eb7c41ebd4.js +0 -1
  73. package/out/_next/static/chunks/app/agents/versions/page-c482d9bad8f35df6.js +0 -1
  74. package/out/_next/static/chunks/app/analytics/page-ca5d8c60e62118ed.js +0 -1
  75. package/out/_next/static/chunks/app/layout-b06d1caafc026d0c.js +0 -1
  76. package/out/_next/static/chunks/app/logs/page-1a7df17a605f36d3.js +0 -1
  77. package/out/_next/static/chunks/app/page-9e02cb0e8897ab5d.js +0 -1
  78. package/out/_next/static/chunks/app/playground/page-cb17c2ffaeb31b4e.js +0 -1
  79. package/out/_next/static/chunks/app/queue/page-6013b93817822c75.js +0 -1
  80. package/out/_next/static/chunks/app/sessions/page-add67d96ab66b690.js +0 -1
  81. package/out/_next/static/chunks/app/settings/credentials/page-ffe97ffb2f60229d.js +0 -1
  82. package/out/_next/static/css/ab505eeeff3f7df5.css +0 -1
  83. package/out/_next/static/sXYgh3eUKXRKt1T_1T3tk/_buildManifest.js +0 -1
  84. /package/out/_next/static/{sXYgh3eUKXRKt1T_1T3tk → 7bfIRYNSIrLnufuHDu6SP}/_ssgManifest.js +0 -0
@@ -1,70 +1,21 @@
1
1
  'use client'
2
2
 
3
- import { Suspense, useState, useRef, useCallback } from 'react'
3
+ import { Suspense } from 'react'
4
4
  import { useSearchParams } from 'next/navigation'
5
5
  import Link from 'next/link'
6
- import { useAgentFiles } 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 { EmptyState } from '@/components/ui/empty-state'
11
8
  import { ShimmerBlock } from '@/components/ui/shimmer'
12
- import { formatRelativeTime } from '@/lib/utils'
13
- import {
14
- ArrowLeft,
15
- BookOpen,
16
- Upload,
17
- Trash2,
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 className="flex items-center justify-between">
92
- <div>
93
- <h1 className="text-2xl font-bold text-white">Knowledge Base</h1>
94
- <p className="mt-1 text-sm text-white/50">
95
- Files for <span className="text-white/70">{name}</span>
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
- {error && (
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
  }
@@ -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-40 rounded-lg border border-white/10 bg-[#1c2129] shadow-xl z-10 py-1">
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, useState } from 'react'
3
+ import { Suspense } from 'react'
4
4
  import { useSearchParams } from 'next/navigation'
5
5
  import Link from 'next/link'
6
- import { useAgentVersions } 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 { formatRelativeTime } from '@/lib/utils'
15
- import {
16
- ArrowLeft,
17
- GitBranch,
18
- Plus,
19
- CheckCircle2,
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 className="flex items-center justify-between">
69
- <div>
70
- <h1 className="text-2xl font-bold text-white">Versions</h1>
71
- <p className="mt-1 text-sm text-white/50">
72
- Manage versions for <span className="text-white/70">{name}</span>
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
- {error && (
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
  }
@@ -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.text === 'string') summary = d.text.slice(0, 100)
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[] }) {