@geminilight/mindos 0.5.68 → 0.5.70
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/app/api/ask/route.ts +12 -4
- package/app/app/api/file/import/route.ts +197 -0
- package/app/app/api/mcp/install/route.ts +99 -12
- package/app/app/api/mcp/status/route.ts +1 -1
- package/app/components/ActivityBar.tsx +3 -4
- package/app/components/FileTree.tsx +35 -9
- package/app/components/ImportModal.tsx +415 -0
- package/app/components/OnboardingView.tsx +9 -0
- package/app/components/Panel.tsx +4 -2
- package/app/components/SidebarLayout.tsx +83 -8
- package/app/components/TableOfContents.tsx +1 -0
- package/app/components/agents/AgentDetailContent.tsx +37 -28
- package/app/components/agents/AgentsMcpSection.tsx +16 -12
- package/app/components/agents/AgentsOverviewSection.tsx +48 -34
- package/app/components/agents/AgentsPrimitives.tsx +41 -20
- package/app/components/agents/AgentsSkillsSection.tsx +16 -7
- package/app/components/agents/SkillDetailPopover.tsx +13 -11
- package/app/components/ask/AskContent.tsx +11 -0
- package/app/components/panels/AgentsPanelAgentGroups.tsx +8 -6
- package/app/components/panels/AgentsPanelHubNav.tsx +3 -3
- package/app/components/panels/DiscoverPanel.tsx +88 -2
- package/app/hooks/useFileImport.ts +191 -0
- package/app/hooks/useFileUpload.ts +11 -0
- package/app/lib/agent/context.ts +7 -2
- package/app/lib/agent/tools.ts +245 -6
- package/app/lib/core/backlinks.ts +12 -4
- package/app/lib/core/file-convert.ts +97 -0
- package/app/lib/core/organize.ts +105 -0
- package/app/lib/core/search.ts +17 -3
- package/app/lib/fs.ts +5 -3
- package/app/lib/i18n-en.ts +51 -0
- package/app/lib/i18n-zh.ts +51 -0
- package/package.json +1 -1
|
@@ -214,34 +214,36 @@ export default function AgentDetailContent({ agentKey }: { agentKey: string }) {
|
|
|
214
214
|
</Link>
|
|
215
215
|
|
|
216
216
|
{/* ═══════════ AGENT PROFILE (consolidated header) ═══════════ */}
|
|
217
|
-
<section className="rounded-
|
|
218
|
-
<div className="flex items-center gap-
|
|
217
|
+
<section className="rounded-xl border border-border bg-gradient-to-b from-card to-card/80 overflow-hidden">
|
|
218
|
+
<div className="flex items-center gap-4 p-5">
|
|
219
219
|
<AgentAvatar name={agent.name} status={status} size="md" />
|
|
220
|
-
<div className="min-w-0">
|
|
220
|
+
<div className="min-w-0 flex-1">
|
|
221
221
|
<h1 className="text-xl font-semibold tracking-tight font-display text-foreground">{agent.name}</h1>
|
|
222
|
-
<div className="flex flex-wrap items-center gap-x-2 gap-y-0.5 mt-
|
|
223
|
-
<span className={`text-2xs font-medium px-
|
|
222
|
+
<div className="flex flex-wrap items-center gap-x-2 gap-y-0.5 mt-1">
|
|
223
|
+
<span className={`text-2xs font-medium px-2 py-0.5 rounded-full ${
|
|
224
224
|
status === 'connected' ? 'bg-success/10 text-success'
|
|
225
225
|
: status === 'detected' ? 'bg-[var(--amber-subtle)] text-[var(--amber)]'
|
|
226
226
|
: 'bg-muted text-muted-foreground'
|
|
227
227
|
}`}>{status}</span>
|
|
228
|
-
<span className="text-2xs text-muted-foreground font-mono">{agent.transport ?? agent.preferredTransport}</span>
|
|
229
|
-
<span className="text-2xs text-muted-foreground">·</span>
|
|
230
|
-
<span className="text-2xs text-muted-foreground">{agent.skillMode ?? a.na}</span>
|
|
228
|
+
<span className="text-2xs text-muted-foreground/60 font-mono">{agent.transport ?? agent.preferredTransport}</span>
|
|
229
|
+
<span className="text-2xs text-muted-foreground/30" aria-hidden="true">·</span>
|
|
230
|
+
<span className="text-2xs text-muted-foreground/60">{agent.skillMode ?? a.na}</span>
|
|
231
231
|
</div>
|
|
232
232
|
</div>
|
|
233
233
|
</div>
|
|
234
|
-
<div className="flex flex-wrap items-center gap-x-
|
|
235
|
-
<span>{a.detail.format}: <span className="text-foreground">{agent.format}</span></span>
|
|
236
|
-
<span>{a.detail.lastActivityAt}: <span className="text-foreground tabular-nums">{agent.runtimeLastActivityAt ?? a.na}</span></span>
|
|
237
|
-
<span>{configuredMcpServers.length} MCP · {nativeInstalledSkills.length} skills</span>
|
|
234
|
+
<div className="flex flex-wrap items-center gap-x-5 gap-y-1 text-xs text-muted-foreground/70 px-5 py-3 border-t border-border/50 bg-muted/[0.03]">
|
|
235
|
+
<span>{a.detail.format}: <span className="text-foreground/80 font-medium">{agent.format}</span></span>
|
|
236
|
+
<span>{a.detail.lastActivityAt}: <span className="text-foreground/80 tabular-nums font-medium">{agent.runtimeLastActivityAt ?? a.na}</span></span>
|
|
237
|
+
<span className="font-medium text-foreground/80 tabular-nums">{configuredMcpServers.length} MCP · {nativeInstalledSkills.length} skills</span>
|
|
238
238
|
</div>
|
|
239
239
|
</section>
|
|
240
240
|
|
|
241
241
|
{/* ═══════════ MCP MANAGEMENT ═══════════ */}
|
|
242
|
-
<section className="rounded-
|
|
243
|
-
<h2 className="text-sm font-
|
|
244
|
-
<
|
|
242
|
+
<section className="rounded-xl border border-border bg-card p-5 space-y-4">
|
|
243
|
+
<h2 className="text-sm font-semibold text-foreground flex items-center gap-2">
|
|
244
|
+
<div className="w-6 h-6 rounded-md bg-muted/50 flex items-center justify-center">
|
|
245
|
+
<Server size={13} className="text-muted-foreground/70" />
|
|
246
|
+
</div>
|
|
245
247
|
{a.detail.mcpManagement}
|
|
246
248
|
</h2>
|
|
247
249
|
|
|
@@ -253,10 +255,10 @@ export default function AgentDetailContent({ agentKey }: { agentKey: string }) {
|
|
|
253
255
|
</div>
|
|
254
256
|
|
|
255
257
|
{/* Configured MCP servers with management */}
|
|
256
|
-
<div className="rounded-
|
|
258
|
+
<div className="rounded-xl border border-border/60 bg-background/50 p-4 space-y-2.5">
|
|
257
259
|
<div className="flex items-center justify-between">
|
|
258
260
|
<p className="text-xs font-semibold text-foreground">{a.detail.configuredMcpServers}</p>
|
|
259
|
-
<span className="text-2xs text-muted-foreground tabular-nums">{a.detail.configuredMcpServersCount(configuredMcpServers.length)}</span>
|
|
261
|
+
<span className="text-2xs text-muted-foreground/60 tabular-nums">{a.detail.configuredMcpServersCount(configuredMcpServers.length)}</span>
|
|
260
262
|
</div>
|
|
261
263
|
|
|
262
264
|
{mcpHint && (
|
|
@@ -272,8 +274,10 @@ export default function AgentDetailContent({ agentKey }: { agentKey: string }) {
|
|
|
272
274
|
{configuredMcpServers.map((name) => {
|
|
273
275
|
const sharedWith = (crossAgentMcpMap.get(name) ?? []).filter((n) => n !== agent.name);
|
|
274
276
|
return (
|
|
275
|
-
<div key={name} className="flex items-center gap-2 rounded-
|
|
276
|
-
<
|
|
277
|
+
<div key={name} className="flex items-center gap-2.5 rounded-lg border border-border/40 bg-muted/[0.02] px-3 py-2.5 group/mcp hover:border-border/60 hover:bg-muted/[0.06] hover:shadow-[0_1px_3px_rgba(0,0,0,0.02)] transition-all duration-150">
|
|
278
|
+
<div className="w-5 h-5 rounded-md bg-[var(--amber)]/[0.08] flex items-center justify-center shrink-0">
|
|
279
|
+
<Server size={10} className="text-[var(--amber)]" />
|
|
280
|
+
</div>
|
|
277
281
|
<span className="text-xs font-medium text-foreground flex-1 min-w-0 truncate">{name}</span>
|
|
278
282
|
{sharedWith.length > 0 && (
|
|
279
283
|
<div className="flex items-center gap-1">
|
|
@@ -325,13 +329,18 @@ export default function AgentDetailContent({ agentKey }: { agentKey: string }) {
|
|
|
325
329
|
</section>
|
|
326
330
|
|
|
327
331
|
{/* ═══════════ SKILL ASSIGNMENTS ═══════════ */}
|
|
328
|
-
<section className="rounded-
|
|
332
|
+
<section className="rounded-xl border border-border bg-card p-5 space-y-4">
|
|
329
333
|
<div className="flex items-center justify-between">
|
|
330
|
-
<h2 className="text-sm font-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
334
|
+
<h2 className="text-sm font-semibold text-foreground flex items-center gap-2">
|
|
335
|
+
<div className="w-6 h-6 rounded-md bg-muted/50 flex items-center justify-center">
|
|
336
|
+
<Zap size={13} className="text-muted-foreground/70" />
|
|
337
|
+
</div>
|
|
338
|
+
{a.detail.skillAssignments}
|
|
339
|
+
</h2>
|
|
340
|
+
<div className="flex items-center gap-2 text-2xs text-muted-foreground/60 tabular-nums">
|
|
341
|
+
<span className="px-1.5 py-0.5 rounded bg-muted/40">MindOS {skillSummary.total}</span>
|
|
342
|
+
<span className="px-1.5 py-0.5 rounded bg-emerald-500/[0.06] text-emerald-600 dark:text-emerald-400">{a.detail.skillsEnabled.split(' ')[0]} {skillSummary.enabled}</span>
|
|
343
|
+
<span className="px-1.5 py-0.5 rounded bg-muted/40">{a.detail.nativeInstalledSkills} {nativeInstalledSkills.length}</span>
|
|
335
344
|
</div>
|
|
336
345
|
</div>
|
|
337
346
|
|
|
@@ -504,9 +513,9 @@ export default function AgentDetailContent({ agentKey }: { agentKey: string }) {
|
|
|
504
513
|
|
|
505
514
|
function DetailLine({ label, value }: { label: string; value: string }) {
|
|
506
515
|
return (
|
|
507
|
-
<div className="rounded-
|
|
508
|
-
<p className="text-2xs text-muted-foreground mb-1">{label}</p>
|
|
509
|
-
<p className="text-sm text-foreground truncate">{value}</p>
|
|
516
|
+
<div className="rounded-lg border border-border/60 bg-muted/[0.02] px-3.5 py-2.5 hover:bg-muted/[0.06] transition-colors duration-100">
|
|
517
|
+
<p className="text-2xs text-muted-foreground/60 mb-1 uppercase tracking-wider">{label}</p>
|
|
518
|
+
<p className="text-sm text-foreground font-medium truncate">{value}</p>
|
|
510
519
|
</div>
|
|
511
520
|
);
|
|
512
521
|
}
|
|
@@ -142,8 +142,10 @@ export default function AgentsMcpSection({
|
|
|
142
142
|
{/* Header */}
|
|
143
143
|
<div className="flex items-center justify-between gap-2">
|
|
144
144
|
<div className="flex items-center gap-2.5">
|
|
145
|
-
<h2 className="text-sm font-
|
|
146
|
-
<
|
|
145
|
+
<h2 className="text-sm font-semibold text-foreground flex items-center gap-2">
|
|
146
|
+
<div className="w-6 h-6 rounded-md bg-muted/50 flex items-center justify-center">
|
|
147
|
+
<Server size={13} className="text-muted-foreground/70" aria-hidden="true" />
|
|
148
|
+
</div>
|
|
147
149
|
{copy.title}
|
|
148
150
|
</h2>
|
|
149
151
|
<button
|
|
@@ -164,8 +166,8 @@ export default function AgentsMcpSection({
|
|
|
164
166
|
</div>
|
|
165
167
|
|
|
166
168
|
{/* Compact status strip + risk alerts */}
|
|
167
|
-
<div className="rounded-
|
|
168
|
-
<div className="flex flex-wrap items-center gap-x-
|
|
169
|
+
<div className="rounded-xl border border-border/60 bg-gradient-to-r from-card to-card/80 p-3.5">
|
|
170
|
+
<div className="flex flex-wrap items-center gap-x-5 gap-y-2 text-xs">
|
|
169
171
|
<StatusDot tone="ok" label={copy.filters.connected} count={buckets.connected.length} />
|
|
170
172
|
<StatusDot tone="warn" label={copy.filters.detected} count={buckets.detected.length} />
|
|
171
173
|
{buckets.notFound.length > 0 && (
|
|
@@ -294,7 +296,7 @@ function ByAgentView({
|
|
|
294
296
|
const mcpServers = agent.configuredMcpServers ?? [];
|
|
295
297
|
const nativeSkillCount = (agent.installedSkillNames ?? []).length;
|
|
296
298
|
return (
|
|
297
|
-
<div key={agent.key} className=
|
|
299
|
+
<div key={agent.key} className={`rounded-xl border bg-card group hover:shadow-[0_2px_8px_rgba(0,0,0,0.04)] transition-all duration-150 ${status === 'connected' ? 'border-l-2 border-l-[var(--success)] border-border' : status === 'detected' ? 'border-l-2 border-l-[var(--amber)] border-border' : 'border-border'}`}>
|
|
298
300
|
{/* Card header with avatar */}
|
|
299
301
|
<div className="flex items-center gap-3 p-3">
|
|
300
302
|
<AgentAvatar name={agent.name} status={status} />
|
|
@@ -344,8 +346,8 @@ function ByAgentView({
|
|
|
344
346
|
{mcpServers.length > 0 && (
|
|
345
347
|
<div className="flex flex-wrap gap-1 px-3 pb-3 ml-12">
|
|
346
348
|
{mcpServers.map((name) => (
|
|
347
|
-
<span key={name} className="inline-flex items-center gap-1 rounded-full bg-muted/
|
|
348
|
-
<
|
|
349
|
+
<span key={name} className="inline-flex items-center gap-1.5 rounded-full bg-muted/40 border border-border/30 px-2.5 py-0.5 text-2xs text-muted-foreground hover:bg-muted/60 transition-colors duration-100">
|
|
350
|
+
<span className="w-1 h-1 rounded-full bg-[var(--amber)]" aria-hidden="true" />
|
|
349
351
|
{name}
|
|
350
352
|
</span>
|
|
351
353
|
))}
|
|
@@ -450,12 +452,14 @@ function ByServerView({
|
|
|
450
452
|
const notFoundCount = agentDetails.length - connectedCount - detectedCount + orphanNames.length;
|
|
451
453
|
|
|
452
454
|
return (
|
|
453
|
-
<div key={srv.serverName} className="rounded-
|
|
455
|
+
<div key={srv.serverName} className="rounded-xl border border-border bg-card p-4 hover:shadow-[0_2px_8px_rgba(0,0,0,0.04)] transition-all duration-200">
|
|
454
456
|
{/* Server header */}
|
|
455
|
-
<div className="flex items-center justify-between gap-2 mb-
|
|
456
|
-
<div className="flex items-center gap-2 min-w-0">
|
|
457
|
-
<
|
|
458
|
-
|
|
457
|
+
<div className="flex items-center justify-between gap-2 mb-3">
|
|
458
|
+
<div className="flex items-center gap-2.5 min-w-0">
|
|
459
|
+
<div className="w-7 h-7 rounded-lg bg-[var(--amber)]/[0.08] flex items-center justify-center shrink-0">
|
|
460
|
+
<Server size={13} className="text-[var(--amber)]" aria-hidden="true" />
|
|
461
|
+
</div>
|
|
462
|
+
<span className="text-sm font-semibold text-foreground truncate">{srv.serverName}</span>
|
|
459
463
|
</div>
|
|
460
464
|
<div className="flex items-center gap-1.5 shrink-0">
|
|
461
465
|
{canManage && agentDetails.length > 0 && (
|
|
@@ -91,10 +91,10 @@ export default function AgentsOverviewSection({
|
|
|
91
91
|
<div className="space-y-5">
|
|
92
92
|
{/* ═══════════ HERO STATS BAR ═══════════ */}
|
|
93
93
|
<section
|
|
94
|
-
className="rounded-xl border border-border bg-card overflow-hidden"
|
|
94
|
+
className="rounded-xl border border-border bg-gradient-to-b from-card to-card/80 overflow-hidden"
|
|
95
95
|
aria-label={pulseCopy.connected}
|
|
96
96
|
>
|
|
97
|
-
<div className="flex divide-x divide-border [&>*]:flex-1">
|
|
97
|
+
<div className="flex divide-x divide-border/50 [&>*]:flex-1">
|
|
98
98
|
<StatCell
|
|
99
99
|
icon={<Zap size={14} aria-hidden="true" />}
|
|
100
100
|
label={pulseCopy.connected}
|
|
@@ -231,13 +231,13 @@ export default function AgentsOverviewSection({
|
|
|
231
231
|
</section>
|
|
232
232
|
) : (
|
|
233
233
|
<section
|
|
234
|
-
className="rounded-xl border border-dashed border-border bg-card/
|
|
234
|
+
className="rounded-xl border border-dashed border-border/60 bg-gradient-to-b from-card/80 to-card/40 p-12 text-center"
|
|
235
235
|
aria-label={copy.usagePulse}
|
|
236
236
|
>
|
|
237
|
-
<div className="w-
|
|
238
|
-
<Cable size={
|
|
237
|
+
<div className="w-14 h-14 rounded-2xl bg-muted/40 flex items-center justify-center mx-auto mb-4">
|
|
238
|
+
<Cable size={22} className="text-muted-foreground/50" aria-hidden="true" />
|
|
239
239
|
</div>
|
|
240
|
-
<p className="text-sm text-muted-foreground leading-relaxed max-w-xs mx-auto">
|
|
240
|
+
<p className="text-sm text-muted-foreground/70 leading-relaxed max-w-xs mx-auto">
|
|
241
241
|
{copy.nextActionHint as string}
|
|
242
242
|
</p>
|
|
243
243
|
</section>
|
|
@@ -269,27 +269,33 @@ function StatCell({
|
|
|
269
269
|
: 'text-muted-foreground';
|
|
270
270
|
const iconColor =
|
|
271
271
|
tone === 'ok'
|
|
272
|
-
? 'text-
|
|
272
|
+
? 'text-emerald-500/70'
|
|
273
273
|
: tone === 'warn'
|
|
274
274
|
? 'text-amber-500/70'
|
|
275
|
-
: 'text-muted-foreground/
|
|
275
|
+
: 'text-muted-foreground/40';
|
|
276
|
+
const hoverBg =
|
|
277
|
+
tone === 'ok'
|
|
278
|
+
? 'hover:bg-emerald-500/[0.04]'
|
|
279
|
+
: tone === 'warn'
|
|
280
|
+
? 'hover:bg-amber-500/[0.04]'
|
|
281
|
+
: 'hover:bg-muted/20';
|
|
276
282
|
|
|
277
283
|
return (
|
|
278
284
|
<div
|
|
279
|
-
className=
|
|
285
|
+
className={`px-3 py-4 text-center ${hoverBg} transition-colors duration-150 group/stat`}
|
|
280
286
|
role="group"
|
|
281
287
|
aria-label={`${label}: ${value}${total !== undefined ? `/${total}` : ''}`}
|
|
282
288
|
>
|
|
283
|
-
<div className={`flex items-center justify-center gap-1.5 mb-
|
|
289
|
+
<div className={`flex items-center justify-center gap-1.5 mb-2 ${iconColor} group-hover/stat:opacity-100 transition-all duration-150`}>
|
|
284
290
|
{icon}
|
|
285
|
-
<span className="text-2xs text-muted-foreground truncate">{label}</span>
|
|
286
291
|
</div>
|
|
287
|
-
<p className={`text-
|
|
292
|
+
<p className={`text-xl font-semibold tabular-nums leading-none mb-1.5 ${textColor}`}>
|
|
288
293
|
{value}
|
|
289
294
|
{total !== undefined && (
|
|
290
295
|
<span className="text-xs font-normal text-muted-foreground ml-0.5">/{total}</span>
|
|
291
296
|
)}
|
|
292
297
|
</p>
|
|
298
|
+
<span className="text-2xs text-muted-foreground/70 truncate block">{label}</span>
|
|
293
299
|
</div>
|
|
294
300
|
);
|
|
295
301
|
}
|
|
@@ -315,19 +321,19 @@ function QuickNavCard({
|
|
|
315
321
|
<Link
|
|
316
322
|
href={href}
|
|
317
323
|
className="group rounded-xl border border-border bg-card p-4 flex items-start gap-3.5
|
|
318
|
-
hover:border-[var(--amber)]/30 hover:
|
|
324
|
+
hover:border-[var(--amber)]/30 hover:shadow-[0_2px_12px_rgba(0,0,0,0.04)]
|
|
319
325
|
active:scale-[0.99]
|
|
320
|
-
transition-all duration-
|
|
326
|
+
transition-all duration-200
|
|
321
327
|
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
322
328
|
>
|
|
323
|
-
<div className="shrink-0 w-10 h-10 rounded-
|
|
329
|
+
<div className="shrink-0 w-10 h-10 rounded-xl bg-muted/50 flex items-center justify-center text-muted-foreground/70 group-hover:text-[var(--amber)] group-hover:bg-[var(--amber)]/[0.08] transition-all duration-200">
|
|
324
330
|
{icon}
|
|
325
331
|
</div>
|
|
326
332
|
<div className="flex-1 min-w-0">
|
|
327
333
|
<div className="flex items-center gap-2 mb-1">
|
|
328
334
|
<span className="text-sm font-semibold text-foreground">{title}</span>
|
|
329
335
|
<span
|
|
330
|
-
className={`text-2xs px-
|
|
336
|
+
className={`text-2xs px-2 py-0.5 rounded-full font-medium select-none ${
|
|
331
337
|
statTone === 'ok'
|
|
332
338
|
? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'
|
|
333
339
|
: 'bg-amber-500/10 text-amber-600 dark:text-amber-400'
|
|
@@ -336,11 +342,11 @@ function QuickNavCard({
|
|
|
336
342
|
{stat}
|
|
337
343
|
</span>
|
|
338
344
|
</div>
|
|
339
|
-
<p className="text-xs text-muted-foreground leading-relaxed line-clamp-2">{description}</p>
|
|
345
|
+
<p className="text-xs text-muted-foreground/70 leading-relaxed line-clamp-2">{description}</p>
|
|
340
346
|
</div>
|
|
341
347
|
<ArrowRight
|
|
342
348
|
size={14}
|
|
343
|
-
className="shrink-0 mt-1.5 text-muted-foreground/
|
|
349
|
+
className="shrink-0 mt-1.5 text-muted-foreground/20 group-hover:text-[var(--amber)] group-hover:translate-x-0.5 transition-all duration-200"
|
|
344
350
|
aria-hidden="true"
|
|
345
351
|
/>
|
|
346
352
|
</Link>
|
|
@@ -372,14 +378,22 @@ function AgentCard({
|
|
|
372
378
|
? 'bg-amber-500/10 text-amber-600 dark:text-amber-400'
|
|
373
379
|
: 'bg-zinc-500/10 text-zinc-500';
|
|
374
380
|
|
|
381
|
+
const accentBorder =
|
|
382
|
+
status === 'connected'
|
|
383
|
+
? 'border-l-[var(--success)]'
|
|
384
|
+
: status === 'detected'
|
|
385
|
+
? 'border-l-[var(--amber)]'
|
|
386
|
+
: '';
|
|
387
|
+
|
|
375
388
|
return (
|
|
376
389
|
<Link
|
|
377
390
|
href={`/agents/${encodeURIComponent(agent.key)}`}
|
|
378
|
-
className=
|
|
379
|
-
|
|
391
|
+
className={`group rounded-xl border bg-card p-3.5
|
|
392
|
+
${accentBorder ? `border-l-2 ${accentBorder} border-border` : 'border-border'}
|
|
393
|
+
hover:border-[var(--amber)]/30 hover:shadow-[0_2px_8px_rgba(0,0,0,0.04)]
|
|
380
394
|
active:scale-[0.98]
|
|
381
395
|
transition-all duration-150 animate-in
|
|
382
|
-
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring
|
|
396
|
+
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring`}
|
|
383
397
|
style={{ animationDelay: `${Math.min(index * 30, 300)}ms` }}
|
|
384
398
|
>
|
|
385
399
|
{/* Top row: avatar + name + status */}
|
|
@@ -390,26 +404,22 @@ function AgentCard({
|
|
|
390
404
|
{agent.name}
|
|
391
405
|
</span>
|
|
392
406
|
{agent.transport && status === 'connected' && (
|
|
393
|
-
<span className="text-2xs text-muted-foreground font-mono">{agent.transport}</span>
|
|
407
|
+
<span className="text-2xs text-muted-foreground/60 font-mono">{agent.transport}</span>
|
|
394
408
|
)}
|
|
395
409
|
</div>
|
|
396
|
-
<span className={`text-2xs px-
|
|
410
|
+
<span className={`text-2xs px-2 py-0.5 rounded-full font-medium shrink-0 select-none ${statusColor}`}>
|
|
397
411
|
{statusLabel}
|
|
398
412
|
</span>
|
|
399
413
|
</div>
|
|
400
414
|
|
|
401
415
|
{/* Metrics row */}
|
|
402
|
-
<div className="flex items-center gap-
|
|
416
|
+
<div className="flex items-center gap-1 pt-2.5 border-t border-border/40">
|
|
403
417
|
<MetricChip icon={<Server size={11} aria-hidden="true" />} value={mcpCount} label={copy.colMcp as string} />
|
|
404
|
-
<span className="text-border mx-2 select-none" aria-hidden="true">·</span>
|
|
405
418
|
<MetricChip icon={<Zap size={11} aria-hidden="true" />} value={skillCount} label={copy.colSkills as string} />
|
|
406
419
|
{agent.skillMode && (
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
{agent.skillMode}
|
|
411
|
-
</span>
|
|
412
|
-
</>
|
|
420
|
+
<span className="text-2xs px-1.5 py-0.5 rounded-md bg-muted/50 text-muted-foreground/70 truncate select-none">
|
|
421
|
+
{agent.skillMode}
|
|
422
|
+
</span>
|
|
413
423
|
)}
|
|
414
424
|
<span className="flex-1 min-w-[4px]" />
|
|
415
425
|
{hasRuntime && (
|
|
@@ -438,9 +448,13 @@ function MetricChip({
|
|
|
438
448
|
label: string;
|
|
439
449
|
}) {
|
|
440
450
|
return (
|
|
441
|
-
<span
|
|
442
|
-
|
|
443
|
-
|
|
451
|
+
<span
|
|
452
|
+
className={`inline-flex items-center gap-1 px-1.5 py-0.5 rounded-md ${value > 0 ? 'bg-muted/40' : ''}`}
|
|
453
|
+
title={label}
|
|
454
|
+
aria-label={`${label}: ${value}`}
|
|
455
|
+
>
|
|
456
|
+
<span className={value > 0 ? 'text-muted-foreground' : 'text-muted-foreground/30'}>{icon}</span>
|
|
457
|
+
<span className={`tabular-nums text-xs ${value > 0 ? 'text-foreground font-medium' : 'text-muted-foreground/40'}`}>
|
|
444
458
|
{value}
|
|
445
459
|
</span>
|
|
446
460
|
</span>
|
|
@@ -11,8 +11,10 @@ export function PillButton({ active, label, onClick }: { active: boolean; label:
|
|
|
11
11
|
type="button"
|
|
12
12
|
onClick={onClick}
|
|
13
13
|
aria-pressed={active}
|
|
14
|
-
className={`px-2.5 min-h-[28px] rounded text-xs cursor-pointer transition-
|
|
15
|
-
active
|
|
14
|
+
className={`relative px-2.5 min-h-[28px] rounded text-xs cursor-pointer transition-all duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${
|
|
15
|
+
active
|
|
16
|
+
? 'bg-[var(--amber-dim)] text-[var(--amber)] font-medium shadow-[0_1px_2px_rgba(200,135,58,0.08)]'
|
|
17
|
+
: 'text-muted-foreground hover:text-foreground hover:bg-muted/60'
|
|
16
18
|
}`}
|
|
17
19
|
>
|
|
18
20
|
{label}
|
|
@@ -21,11 +23,13 @@ export function PillButton({ active, label, onClick }: { active: boolean; label:
|
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
export function StatusDot({ tone, label, count }: { tone: 'ok' | 'warn' | 'neutral'; label: string; count: number }) {
|
|
24
|
-
const dotCls = tone === 'ok' ? 'bg-[var(--success)]' : tone === 'warn' ? 'bg-[var(--amber)]' : 'bg-muted-foreground';
|
|
26
|
+
const dotCls = tone === 'ok' ? 'bg-[var(--success)]' : tone === 'warn' ? 'bg-[var(--amber)]' : 'bg-muted-foreground/60';
|
|
27
|
+
const countCls = tone === 'ok' ? 'text-foreground' : tone === 'warn' ? 'text-[var(--amber)]' : 'text-muted-foreground';
|
|
25
28
|
return (
|
|
26
29
|
<span className="inline-flex items-center gap-1.5 text-muted-foreground">
|
|
27
|
-
<span className={`w-
|
|
28
|
-
|
|
30
|
+
<span className={`w-2 h-2 rounded-full ${dotCls} ${tone === 'ok' ? 'ring-2 ring-[var(--success)]/20' : ''}`} aria-hidden="true" />
|
|
31
|
+
<span className="text-xs">{label}</span>
|
|
32
|
+
<span className={`tabular-nums font-medium ${countCls}`}>{count}</span>
|
|
29
33
|
</span>
|
|
30
34
|
);
|
|
31
35
|
}
|
|
@@ -44,23 +48,23 @@ export function SearchInput({
|
|
|
44
48
|
icon: React.ComponentType<{ size?: number; className?: string }>;
|
|
45
49
|
}) {
|
|
46
50
|
return (
|
|
47
|
-
<label className="relative block">
|
|
48
|
-
<Icon size={14} className="absolute left-
|
|
51
|
+
<label className="relative block group/search">
|
|
52
|
+
<Icon size={14} className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground/60 group-focus-within/search:text-[var(--amber)] pointer-events-none transition-colors duration-150" />
|
|
49
53
|
<input
|
|
50
54
|
value={value}
|
|
51
55
|
onChange={(e) => onChange(e.target.value)}
|
|
52
56
|
placeholder={placeholder}
|
|
53
57
|
aria-label={ariaLabel}
|
|
54
|
-
className="w-full h-9 rounded-
|
|
58
|
+
className="w-full h-9 rounded-lg border border-border bg-background pl-9 pr-8 text-sm text-foreground placeholder:text-muted-foreground/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--amber)]/30 focus-visible:border-[var(--amber)]/40 transition-all duration-150"
|
|
55
59
|
/>
|
|
56
60
|
{value.length > 0 && (
|
|
57
61
|
<button
|
|
58
62
|
type="button"
|
|
59
63
|
onClick={() => onChange('')}
|
|
60
64
|
aria-label="Clear search"
|
|
61
|
-
className="absolute right-2 top-1/2 -translate-y-1/2 p-0.5 rounded-
|
|
65
|
+
className="absolute right-2.5 top-1/2 -translate-y-1/2 p-0.5 rounded-full text-muted-foreground hover:text-foreground hover:bg-muted cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring transition-colors duration-150"
|
|
62
66
|
>
|
|
63
|
-
<X size={
|
|
67
|
+
<X size={13} />
|
|
64
68
|
</button>
|
|
65
69
|
)}
|
|
66
70
|
</label>
|
|
@@ -112,20 +116,35 @@ export function BulkMessage({ message }: { message: string | null }) {
|
|
|
112
116
|
);
|
|
113
117
|
}
|
|
114
118
|
|
|
115
|
-
export function EmptyState({ message, className }: { message: string; className?: string }) {
|
|
119
|
+
export function EmptyState({ message, icon, className }: { message: string; icon?: React.ReactNode; className?: string }) {
|
|
116
120
|
return (
|
|
117
|
-
<div className={`rounded-
|
|
118
|
-
|
|
121
|
+
<div className={`rounded-xl border border-dashed border-border/60 bg-gradient-to-b from-card/80 to-card/40 p-10 text-center ${className ?? ''}`}>
|
|
122
|
+
{icon && (
|
|
123
|
+
<div className="w-10 h-10 rounded-full bg-muted/50 flex items-center justify-center mx-auto mb-3 text-muted-foreground/40">
|
|
124
|
+
{icon}
|
|
125
|
+
</div>
|
|
126
|
+
)}
|
|
127
|
+
<p className="text-sm text-muted-foreground/70 leading-relaxed max-w-xs mx-auto">{message}</p>
|
|
119
128
|
</div>
|
|
120
129
|
);
|
|
121
130
|
}
|
|
122
131
|
|
|
123
132
|
/* ────────── Agent Avatar ────────── */
|
|
124
133
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
'bg-
|
|
128
|
-
'bg-
|
|
134
|
+
/** Soft pastel palette: [bg, border, text] — watercolor aesthetic */
|
|
135
|
+
const AVATAR_PALETTES: [string, string, string][] = [
|
|
136
|
+
['bg-rose-100/70', 'border-rose-300/50', 'text-rose-600/80'],
|
|
137
|
+
['bg-violet-100/70', 'border-violet-300/50', 'text-violet-600/80'],
|
|
138
|
+
['bg-emerald-100/70', 'border-emerald-300/50', 'text-emerald-600/80'],
|
|
139
|
+
['bg-sky-100/70', 'border-sky-300/50', 'text-sky-600/80'],
|
|
140
|
+
['bg-amber-100/70', 'border-amber-300/50', 'text-amber-700/80'],
|
|
141
|
+
['bg-teal-100/70', 'border-teal-300/50', 'text-teal-600/80'],
|
|
142
|
+
['bg-pink-100/70', 'border-pink-300/50', 'text-pink-600/80'],
|
|
143
|
+
['bg-indigo-100/70', 'border-indigo-300/50', 'text-indigo-600/80'],
|
|
144
|
+
['bg-lime-100/70', 'border-lime-300/50', 'text-lime-700/80'],
|
|
145
|
+
['bg-fuchsia-100/70', 'border-fuchsia-300/50', 'text-fuchsia-600/80'],
|
|
146
|
+
['bg-cyan-100/70', 'border-cyan-300/50', 'text-cyan-600/80'],
|
|
147
|
+
['bg-orange-100/70', 'border-orange-300/50', 'text-orange-600/80'],
|
|
129
148
|
];
|
|
130
149
|
|
|
131
150
|
function hashName(str: string): number {
|
|
@@ -153,13 +172,13 @@ export function AgentAvatar({
|
|
|
153
172
|
onRemove?: () => void;
|
|
154
173
|
href?: string;
|
|
155
174
|
}) {
|
|
156
|
-
const
|
|
175
|
+
const [bg, border, text] = AVATAR_PALETTES[hashName(name) % AVATAR_PALETTES.length];
|
|
157
176
|
const sizeClasses = size === 'sm' ? 'w-7 h-7 text-[10px]' : 'w-9 h-9 text-xs';
|
|
158
177
|
const dotColor = status === 'connected' ? 'bg-[var(--success)]' : status === 'detected' ? 'bg-[var(--amber)]' : 'bg-muted-foreground';
|
|
159
178
|
|
|
160
179
|
return (
|
|
161
180
|
<div className="relative group/avatar" title={name}>
|
|
162
|
-
<div className={`${sizeClasses} ${
|
|
181
|
+
<div className={`${sizeClasses} ${bg} ${border} ${text} border rounded-full flex items-center justify-center font-semibold select-none`}>
|
|
163
182
|
{initials(name)}
|
|
164
183
|
</div>
|
|
165
184
|
{status && (
|
|
@@ -322,9 +341,11 @@ export function AgentPickerPopover({
|
|
|
322
341
|
onClick={() => onSelect(agent.key)}
|
|
323
342
|
className="w-full text-left px-3 py-2 text-xs text-foreground hover:bg-muted cursor-pointer flex items-center gap-2 transition-colors duration-100"
|
|
324
343
|
>
|
|
325
|
-
|
|
344
|
+
{(() => { const [bg, bdr, txt] = AVATAR_PALETTES[hashName(agent.name) % AVATAR_PALETTES.length]; return (
|
|
345
|
+
<div className={`w-6 h-6 rounded-full border ${bg} ${bdr} ${txt} flex items-center justify-center text-[9px] font-semibold shrink-0`}>
|
|
326
346
|
{initials(agent.name)}
|
|
327
347
|
</div>
|
|
348
|
+
); })()}
|
|
328
349
|
{agent.name}
|
|
329
350
|
</button>
|
|
330
351
|
))}
|
|
@@ -207,7 +207,12 @@ export default function AgentsSkillsSection({
|
|
|
207
207
|
<section className="space-y-4 overflow-hidden" aria-label={copy.title}>
|
|
208
208
|
{/* Header */}
|
|
209
209
|
<div className="flex items-center justify-between gap-3">
|
|
210
|
-
<h2 className="text-sm font-
|
|
210
|
+
<h2 className="text-sm font-semibold text-foreground flex items-center gap-2">
|
|
211
|
+
<div className="w-6 h-6 rounded-md bg-muted/50 flex items-center justify-center">
|
|
212
|
+
<Zap size={13} className="text-muted-foreground/70" aria-hidden="true" />
|
|
213
|
+
</div>
|
|
214
|
+
{copy.title}
|
|
215
|
+
</h2>
|
|
211
216
|
<div className="flex items-center gap-1 rounded-md border border-border p-0.5 bg-background" role="tablist" aria-label={copy.title}>
|
|
212
217
|
<PillButton active={view === 'bySkill'} label={copy.tabs.bySkill} onClick={() => setView('bySkill')} />
|
|
213
218
|
<PillButton active={view === 'byAgent'} label={copy.tabs.byAgent} onClick={() => setView('byAgent')} />
|
|
@@ -215,8 +220,8 @@ export default function AgentsSkillsSection({
|
|
|
215
220
|
</div>
|
|
216
221
|
|
|
217
222
|
{/* Compact status strip */}
|
|
218
|
-
<div className="rounded-
|
|
219
|
-
<div className="flex flex-wrap items-center gap-x-
|
|
223
|
+
<div className="rounded-xl border border-border/60 bg-gradient-to-r from-card to-card/80 p-3.5">
|
|
224
|
+
<div className="flex flex-wrap items-center gap-x-5 gap-y-2 text-xs">
|
|
220
225
|
<span className="inline-flex items-center gap-1.5 text-muted-foreground">
|
|
221
226
|
<span className="w-1.5 h-1.5 rounded-full bg-[var(--success)]" aria-hidden="true" />
|
|
222
227
|
{copy.summaryEnabled(enabledCount)}
|
|
@@ -446,8 +451,12 @@ function BySkillView({
|
|
|
446
451
|
<div className="space-y-3">
|
|
447
452
|
{sortedGrouped.map(([groupKey, sortedSkills]) => (
|
|
448
453
|
<div key={groupKey}>
|
|
449
|
-
<div className="
|
|
450
|
-
|
|
454
|
+
<div className="flex items-center gap-2 mb-2.5">
|
|
455
|
+
<span className="w-1 h-4 rounded-full bg-[var(--amber)]/40" aria-hidden="true" />
|
|
456
|
+
<span className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
|
457
|
+
{copy.groupLabels[groupKey as keyof typeof copy.groupLabels]}
|
|
458
|
+
</span>
|
|
459
|
+
<span className="text-2xs tabular-nums text-muted-foreground/50 font-medium">({sortedSkills.length})</span>
|
|
451
460
|
</div>
|
|
452
461
|
<div className="space-y-3">
|
|
453
462
|
{sortedSkills.map((skill) => {
|
|
@@ -457,7 +466,7 @@ function BySkillView({
|
|
|
457
466
|
const isUserSkill = skill.kind === 'mindos' && skill.source === 'user';
|
|
458
467
|
|
|
459
468
|
return (
|
|
460
|
-
<div key={skill.name} className="rounded-
|
|
469
|
+
<div key={skill.name} className="rounded-xl border border-border bg-card p-4 hover:shadow-[0_2px_8px_rgba(0,0,0,0.04)] transition-all duration-200">
|
|
461
470
|
{/* Skill header */}
|
|
462
471
|
<div className="flex items-center justify-between gap-2 mb-2">
|
|
463
472
|
<div className="flex items-center gap-2 min-w-0">
|
|
@@ -676,7 +685,7 @@ function AgentCard({
|
|
|
676
685
|
const visibleNative = nativeExpanded ? nativeSkills : nativeSkills.slice(0, NATIVE_COLLAPSE_THRESHOLD);
|
|
677
686
|
|
|
678
687
|
return (
|
|
679
|
-
<div className="rounded-
|
|
688
|
+
<div className="rounded-xl border border-border bg-card hover:shadow-[0_2px_8px_rgba(0,0,0,0.04)] transition-all duration-200 overflow-hidden">
|
|
680
689
|
{/* Card header with avatar */}
|
|
681
690
|
<div className="flex items-center gap-3 p-4 pb-0">
|
|
682
691
|
<AgentAvatar name={name} status={status} />
|
|
@@ -136,6 +136,8 @@ export default function SkillDetailPopover({
|
|
|
136
136
|
setLoadError(false);
|
|
137
137
|
setCopied(false);
|
|
138
138
|
setDeleteMsg(null);
|
|
139
|
+
setDeleting(false);
|
|
140
|
+
setToggleBusy(false);
|
|
139
141
|
fetchContent();
|
|
140
142
|
}
|
|
141
143
|
}, [open, skillName, fetchContent]);
|
|
@@ -215,14 +217,14 @@ export default function SkillDetailPopover({
|
|
|
215
217
|
className="fixed right-0 top-0 z-50 h-full w-full max-w-md border-l border-border bg-card shadow-2xl flex flex-col animate-in slide-in-from-right duration-200"
|
|
216
218
|
>
|
|
217
219
|
{/* ─── Header ─── */}
|
|
218
|
-
<div className="flex items-center gap-3 px-5 py-4 border-b border-border shrink-0">
|
|
219
|
-
<div className="w-9 h-9 rounded-
|
|
220
|
+
<div className="flex items-center gap-3 px-5 py-4 border-b border-border/60 bg-gradient-to-r from-card to-card/80 shrink-0">
|
|
221
|
+
<div className="w-9 h-9 rounded-xl bg-[var(--amber)]/[0.08] flex items-center justify-center text-[var(--amber)] shrink-0">
|
|
220
222
|
<CapIcon size={18} />
|
|
221
223
|
</div>
|
|
222
224
|
<div className="flex-1 min-w-0">
|
|
223
225
|
<h2 className="text-sm font-semibold text-foreground truncate">{skillName}</h2>
|
|
224
|
-
<div className="flex items-center gap-2 mt-
|
|
225
|
-
<span className={`text-2xs px-
|
|
226
|
+
<div className="flex items-center gap-2 mt-1">
|
|
227
|
+
<span className={`text-2xs px-2 py-0.5 rounded-full font-medium select-none ${
|
|
226
228
|
isNative
|
|
227
229
|
? 'bg-muted text-muted-foreground'
|
|
228
230
|
: skill?.source === 'builtin'
|
|
@@ -231,7 +233,7 @@ export default function SkillDetailPopover({
|
|
|
231
233
|
}`}>
|
|
232
234
|
{sourceLabel}
|
|
233
235
|
</span>
|
|
234
|
-
<span className="text-2xs text-muted-foreground capitalize">{capability}</span>
|
|
236
|
+
<span className="text-2xs text-muted-foreground/60 capitalize">{capability}</span>
|
|
235
237
|
</div>
|
|
236
238
|
</div>
|
|
237
239
|
<button
|
|
@@ -265,9 +267,9 @@ export default function SkillDetailPopover({
|
|
|
265
267
|
|
|
266
268
|
{/* Path */}
|
|
267
269
|
{skillPath && (
|
|
268
|
-
<div className="rounded-
|
|
269
|
-
<span className="text-2xs text-muted-foreground block mb-1">{copy.path}</span>
|
|
270
|
-
<code className="text-xs text-foreground font-mono break-all leading-relaxed">{skillPath}</code>
|
|
270
|
+
<div className="rounded-xl border border-border/50 bg-muted/[0.03] p-3.5">
|
|
271
|
+
<span className="text-2xs text-muted-foreground/60 block mb-1.5 uppercase tracking-wider">{copy.path}</span>
|
|
272
|
+
<code className="text-xs text-foreground/80 font-mono break-all leading-relaxed">{skillPath}</code>
|
|
271
273
|
</div>
|
|
272
274
|
)}
|
|
273
275
|
|
|
@@ -408,9 +410,9 @@ function MetaCard({
|
|
|
408
410
|
: tone === 'muted' ? 'text-muted-foreground'
|
|
409
411
|
: 'text-foreground';
|
|
410
412
|
return (
|
|
411
|
-
<div className="rounded-
|
|
412
|
-
<span className="text-2xs text-muted-foreground block mb-
|
|
413
|
-
<span className={`text-sm font-
|
|
413
|
+
<div className="rounded-xl border border-border/50 bg-muted/[0.03] px-3.5 py-3 hover:bg-muted/[0.06] transition-colors duration-100">
|
|
414
|
+
<span className="text-2xs text-muted-foreground/60 block mb-1 uppercase tracking-wider">{label}</span>
|
|
415
|
+
<span className={`text-sm font-semibold capitalize ${valueColor}`}>{value}</span>
|
|
414
416
|
</div>
|
|
415
417
|
);
|
|
416
418
|
}
|