@geminilight/mindos 0.5.69 → 0.6.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/app/api/ask/route.ts +122 -92
- package/app/app/api/file/import/route.ts +197 -0
- package/app/app/api/mcp/agents/route.ts +53 -2
- package/app/app/api/mcp/status/route.ts +1 -1
- package/app/app/api/skills/route.ts +10 -114
- package/app/components/ActivityBar.tsx +5 -7
- package/app/components/CreateSpaceModal.tsx +31 -6
- package/app/components/FileTree.tsx +68 -11
- package/app/components/GuideCard.tsx +197 -131
- package/app/components/HomeContent.tsx +85 -18
- 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 +96 -8
- package/app/components/SpaceInitToast.tsx +173 -0
- package/app/components/TableOfContents.tsx +1 -0
- package/app/components/agents/AgentDetailContent.tsx +69 -45
- package/app/components/agents/AgentsContentPage.tsx +2 -1
- package/app/components/agents/AgentsMcpSection.tsx +16 -12
- package/app/components/agents/AgentsOverviewSection.tsx +37 -36
- package/app/components/agents/AgentsPrimitives.tsx +41 -20
- package/app/components/agents/AgentsSkillsSection.tsx +16 -7
- package/app/components/agents/SkillDetailPopover.tsx +11 -11
- package/app/components/agents/agents-content-model.ts +16 -8
- package/app/components/ask/AskContent.tsx +148 -50
- package/app/components/ask/MentionPopover.tsx +16 -8
- package/app/components/ask/SlashCommandPopover.tsx +62 -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/components/settings/KnowledgeTab.tsx +61 -0
- package/app/components/walkthrough/steps.ts +11 -6
- package/app/hooks/useFileImport.ts +191 -0
- package/app/hooks/useFileUpload.ts +11 -0
- package/app/hooks/useMention.ts +14 -6
- package/app/hooks/useSlashCommand.ts +114 -0
- package/app/lib/actions.ts +79 -2
- package/app/lib/agent/index.ts +1 -1
- package/app/lib/agent/prompt.ts +2 -0
- package/app/lib/agent/tools.ts +252 -0
- package/app/lib/core/create-space.ts +11 -4
- package/app/lib/core/file-convert.ts +97 -0
- package/app/lib/core/index.ts +1 -1
- package/app/lib/core/organize.ts +105 -0
- package/app/lib/i18n-en.ts +102 -46
- package/app/lib/i18n-zh.ts +101 -45
- package/app/lib/mcp-agents.ts +8 -0
- package/app/lib/pdf-extract.ts +33 -0
- package/app/lib/pi-integration/extensions.ts +45 -0
- package/app/lib/pi-integration/mcporter.ts +219 -0
- package/app/lib/pi-integration/session-store.ts +62 -0
- package/app/lib/pi-integration/skills.ts +116 -0
- package/app/lib/settings.ts +1 -1
- package/app/next-env.d.ts +1 -1
- package/app/next.config.ts +1 -1
- package/app/package.json +2 -0
- package/mcp/src/index.ts +29 -0
- package/package.json +1 -1
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useCallback, useTransition } from 'react';
|
|
4
|
+
import { Sparkles, Check, Undo2, X } from 'lucide-react';
|
|
5
|
+
import { useRouter } from 'next/navigation';
|
|
6
|
+
import { useLocale } from '@/lib/LocaleContext';
|
|
7
|
+
import { encodePath } from '@/lib/utils';
|
|
8
|
+
import { revertSpaceInitAction } from '@/lib/actions';
|
|
9
|
+
|
|
10
|
+
type InitState = 'working' | 'done' | 'reverted' | 'error';
|
|
11
|
+
|
|
12
|
+
interface InitInfo {
|
|
13
|
+
spaceName: string;
|
|
14
|
+
spacePath: string;
|
|
15
|
+
description: string;
|
|
16
|
+
state: InitState;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Global toast that shows AI initialization progress when a new space is created.
|
|
21
|
+
* Mounted in SidebarLayout so it persists across route transitions.
|
|
22
|
+
*
|
|
23
|
+
* States:
|
|
24
|
+
* - working: ✨ progress animation
|
|
25
|
+
* - done: ✓ ready + [Review] [Discard] [×] — persists until user acts
|
|
26
|
+
* - reverted: ↩ "reverted to template" — auto-dismiss 2s
|
|
27
|
+
* - error: silent dismiss
|
|
28
|
+
*/
|
|
29
|
+
export default function SpaceInitToast() {
|
|
30
|
+
const [info, setInfo] = useState<InitInfo | null>(null);
|
|
31
|
+
const [visible, setVisible] = useState(false);
|
|
32
|
+
const [reverting, startRevert] = useTransition();
|
|
33
|
+
const router = useRouter();
|
|
34
|
+
const { t } = useLocale();
|
|
35
|
+
|
|
36
|
+
const dismiss = useCallback(() => {
|
|
37
|
+
setVisible(false);
|
|
38
|
+
setTimeout(() => setInfo(null), 200);
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
const handleEvent = useCallback((e: Event) => {
|
|
42
|
+
const detail = (e as CustomEvent).detail as Partial<InitInfo>;
|
|
43
|
+
if (detail.state === 'working') {
|
|
44
|
+
setInfo(detail as InitInfo);
|
|
45
|
+
requestAnimationFrame(() => setVisible(true));
|
|
46
|
+
} else if (detail.state === 'done') {
|
|
47
|
+
setInfo((prev) => prev ? { ...prev, state: 'done' } : null);
|
|
48
|
+
router.refresh();
|
|
49
|
+
} else {
|
|
50
|
+
dismiss();
|
|
51
|
+
}
|
|
52
|
+
}, [router, dismiss]);
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
window.addEventListener('mindos:ai-init', handleEvent);
|
|
56
|
+
return () => window.removeEventListener('mindos:ai-init', handleEvent);
|
|
57
|
+
}, [handleEvent]);
|
|
58
|
+
|
|
59
|
+
const handleReview = useCallback(() => {
|
|
60
|
+
if (!info) return;
|
|
61
|
+
dismiss();
|
|
62
|
+
router.push(`/view/${encodePath(info.spacePath + '/')}`);
|
|
63
|
+
}, [info, dismiss, router]);
|
|
64
|
+
|
|
65
|
+
const handleDiscard = useCallback(() => {
|
|
66
|
+
if (!info) return;
|
|
67
|
+
startRevert(async () => {
|
|
68
|
+
await revertSpaceInitAction(info.spacePath, info.spaceName, info.description);
|
|
69
|
+
setInfo((prev) => prev ? { ...prev, state: 'reverted' } : null);
|
|
70
|
+
router.refresh();
|
|
71
|
+
window.dispatchEvent(new Event('mindos:files-changed'));
|
|
72
|
+
setTimeout(dismiss, 2000);
|
|
73
|
+
});
|
|
74
|
+
}, [info, router, dismiss]);
|
|
75
|
+
|
|
76
|
+
const handleAccept = useCallback(() => {
|
|
77
|
+
dismiss();
|
|
78
|
+
}, [dismiss]);
|
|
79
|
+
|
|
80
|
+
if (!info) return null;
|
|
81
|
+
|
|
82
|
+
const h = t.home;
|
|
83
|
+
const { state } = info;
|
|
84
|
+
const isDone = state === 'done';
|
|
85
|
+
const isReverted = state === 'reverted';
|
|
86
|
+
const isWorking = state === 'working';
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div
|
|
90
|
+
className={`fixed bottom-6 left-1/2 z-50 -translate-x-1/2 transition-all duration-200 ease-out ${
|
|
91
|
+
visible
|
|
92
|
+
? 'opacity-100 translate-y-0 scale-100'
|
|
93
|
+
: 'opacity-0 translate-y-3 scale-[0.96] pointer-events-none'
|
|
94
|
+
}`}
|
|
95
|
+
>
|
|
96
|
+
<div
|
|
97
|
+
className="flex items-center gap-2.5 rounded-full border bg-card/95 backdrop-blur py-2 shadow-lg"
|
|
98
|
+
style={{
|
|
99
|
+
borderColor: 'color-mix(in srgb, var(--amber) 40%, var(--border))',
|
|
100
|
+
boxShadow: '0 8px 24px color-mix(in srgb, var(--amber) 12%, rgba(0,0,0,.2))',
|
|
101
|
+
paddingLeft: '0.75rem',
|
|
102
|
+
paddingRight: isDone ? '0.5rem' : '1rem',
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
{/* Icon */}
|
|
106
|
+
<span
|
|
107
|
+
className={`inline-flex h-6 w-6 items-center justify-center rounded-full shrink-0 ${
|
|
108
|
+
isReverted
|
|
109
|
+
? 'text-muted-foreground bg-muted'
|
|
110
|
+
: 'text-[var(--amber)] bg-[var(--amber-subtle)]'
|
|
111
|
+
} ${isWorking ? 'animate-pulse' : ''}`}
|
|
112
|
+
>
|
|
113
|
+
{isReverted ? <Undo2 size={12} /> : isDone ? <Check size={13} strokeWidth={2.5} /> : <Sparkles size={13} />}
|
|
114
|
+
</span>
|
|
115
|
+
|
|
116
|
+
{/* Text */}
|
|
117
|
+
<span className="text-xs text-foreground whitespace-nowrap select-none font-medium">
|
|
118
|
+
{isReverted
|
|
119
|
+
? h.aiInitReverted(info.spaceName)
|
|
120
|
+
: isDone
|
|
121
|
+
? h.aiInitReady(info.spaceName)
|
|
122
|
+
: <>{h.aiInitGenerating(info.spaceName)}<DotPulse /></>
|
|
123
|
+
}
|
|
124
|
+
</span>
|
|
125
|
+
|
|
126
|
+
{/* Action buttons — only in done state */}
|
|
127
|
+
{isDone && (
|
|
128
|
+
<div className="flex items-center gap-1 ml-1">
|
|
129
|
+
<button
|
|
130
|
+
type="button"
|
|
131
|
+
onClick={handleReview}
|
|
132
|
+
className="px-2.5 py-1 rounded-full text-2xs font-medium bg-[var(--amber)] text-white hover:opacity-90 transition-opacity focus-visible:ring-2 focus-visible:ring-ring"
|
|
133
|
+
>
|
|
134
|
+
{h.aiInitReview}
|
|
135
|
+
</button>
|
|
136
|
+
<button
|
|
137
|
+
type="button"
|
|
138
|
+
onClick={handleDiscard}
|
|
139
|
+
disabled={reverting}
|
|
140
|
+
className="px-2.5 py-1 rounded-full text-2xs font-medium bg-muted text-muted-foreground hover:text-foreground transition-colors focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50"
|
|
141
|
+
>
|
|
142
|
+
{h.aiInitDiscard}
|
|
143
|
+
</button>
|
|
144
|
+
<button
|
|
145
|
+
type="button"
|
|
146
|
+
onClick={handleAccept}
|
|
147
|
+
aria-label="Accept"
|
|
148
|
+
className="ml-0.5 inline-flex h-5 w-5 items-center justify-center rounded-full text-muted-foreground hover:text-foreground hover:bg-muted/60 transition-colors focus-visible:ring-2 focus-visible:ring-ring"
|
|
149
|
+
>
|
|
150
|
+
<X size={12} />
|
|
151
|
+
</button>
|
|
152
|
+
</div>
|
|
153
|
+
)}
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function DotPulse() {
|
|
160
|
+
return (
|
|
161
|
+
<span className="inline-flex gap-[2px] items-end h-[1em] ml-px">
|
|
162
|
+
<span className="w-[3px] h-[3px] rounded-full bg-muted-foreground animate-[dotBounce_1.2s_0ms_infinite]" />
|
|
163
|
+
<span className="w-[3px] h-[3px] rounded-full bg-muted-foreground animate-[dotBounce_1.2s_200ms_infinite]" />
|
|
164
|
+
<span className="w-[3px] h-[3px] rounded-full bg-muted-foreground animate-[dotBounce_1.2s_400ms_infinite]" />
|
|
165
|
+
<style>{`
|
|
166
|
+
@keyframes dotBounce {
|
|
167
|
+
0%, 60%, 100% { opacity: 0.25; transform: translateY(0); }
|
|
168
|
+
30% { opacity: 1; transform: translateY(-2px); }
|
|
169
|
+
}
|
|
170
|
+
`}</style>
|
|
171
|
+
</span>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
@@ -128,6 +128,7 @@ export default function TableOfContents({ content }: TableOfContentsProps) {
|
|
|
128
128
|
href={`#${heading.id}`}
|
|
129
129
|
onClick={(e) => handleClick(e, heading.id)}
|
|
130
130
|
className="block text-xs py-1 rounded transition-colors duration-100 leading-snug"
|
|
131
|
+
suppressHydrationWarning
|
|
131
132
|
style={{
|
|
132
133
|
paddingLeft: `${8 + indent}px`,
|
|
133
134
|
paddingRight: '8px',
|
|
@@ -73,6 +73,7 @@ export default function AgentDetailContent({ agentKey }: { agentKey: string }) {
|
|
|
73
73
|
return map;
|
|
74
74
|
}, [mcp.agents]);
|
|
75
75
|
|
|
76
|
+
const isMindOS = agentKey === 'mindos';
|
|
76
77
|
const status = agent ? resolveAgentStatus(agent) : 'notFound';
|
|
77
78
|
const currentScope = agent?.scope === 'project' ? 'project' : 'global';
|
|
78
79
|
const currentTransport: 'stdio' | 'http' = agent?.transport === 'http' ? 'http' : 'stdio';
|
|
@@ -214,49 +215,61 @@ export default function AgentDetailContent({ agentKey }: { agentKey: string }) {
|
|
|
214
215
|
</Link>
|
|
215
216
|
|
|
216
217
|
{/* ═══════════ AGENT PROFILE (consolidated header) ═══════════ */}
|
|
217
|
-
<section className="rounded-
|
|
218
|
-
<div className="flex items-center gap-
|
|
218
|
+
<section className="rounded-xl border border-border bg-gradient-to-b from-card to-card/80 overflow-hidden">
|
|
219
|
+
<div className="flex items-center gap-4 p-5">
|
|
219
220
|
<AgentAvatar name={agent.name} status={status} size="md" />
|
|
220
|
-
<div className="min-w-0">
|
|
221
|
+
<div className="min-w-0 flex-1">
|
|
221
222
|
<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-
|
|
223
|
+
<div className="flex flex-wrap items-center gap-x-2 gap-y-0.5 mt-1">
|
|
224
|
+
<span className={`text-2xs font-medium px-2 py-0.5 rounded-full ${
|
|
224
225
|
status === 'connected' ? 'bg-success/10 text-success'
|
|
225
226
|
: status === 'detected' ? 'bg-[var(--amber-subtle)] text-[var(--amber)]'
|
|
226
227
|
: 'bg-muted text-muted-foreground'
|
|
227
228
|
}`}>{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>
|
|
229
|
+
<span className="text-2xs text-muted-foreground/60 font-mono">{agent.transport ?? agent.preferredTransport}</span>
|
|
230
|
+
<span className="text-2xs text-muted-foreground/30" aria-hidden="true">·</span>
|
|
231
|
+
<span className="text-2xs text-muted-foreground/60">{agent.skillMode ?? a.na}</span>
|
|
231
232
|
</div>
|
|
232
233
|
</div>
|
|
233
234
|
</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>
|
|
235
|
+
<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]">
|
|
236
|
+
<span>{a.detail.format}: <span className="text-foreground/80 font-medium">{agent.format}</span></span>
|
|
237
|
+
<span>{a.detail.lastActivityAt}: <span className="text-foreground/80 tabular-nums font-medium">{agent.runtimeLastActivityAt ?? a.na}</span></span>
|
|
238
|
+
<span className="font-medium text-foreground/80 tabular-nums">{configuredMcpServers.length} MCP · {nativeInstalledSkills.length} skills</span>
|
|
238
239
|
</div>
|
|
239
240
|
</section>
|
|
240
241
|
|
|
241
242
|
{/* ═══════════ MCP MANAGEMENT ═══════════ */}
|
|
242
|
-
<section className="rounded-
|
|
243
|
-
<h2 className="text-sm font-
|
|
244
|
-
<
|
|
243
|
+
<section className="rounded-xl border border-border bg-card p-5 space-y-4">
|
|
244
|
+
<h2 className="text-sm font-semibold text-foreground flex items-center gap-2">
|
|
245
|
+
<div className="w-6 h-6 rounded-md bg-muted/50 flex items-center justify-center">
|
|
246
|
+
<Server size={13} className="text-muted-foreground/70" />
|
|
247
|
+
</div>
|
|
245
248
|
{a.detail.mcpManagement}
|
|
246
249
|
</h2>
|
|
247
250
|
|
|
248
251
|
{/* MCP status row */}
|
|
249
252
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 text-sm">
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
+
{isMindOS ? (
|
|
254
|
+
<>
|
|
255
|
+
<DetailLine label="Status" value={mcp.status?.running ? '● Running' : '○ Stopped'} />
|
|
256
|
+
<DetailLine label="Endpoint" value={mcp.status?.endpoint ?? a.na} />
|
|
257
|
+
<DetailLine label="Tools" value={mcp.status?.toolCount != null ? String(mcp.status.toolCount) : a.na} />
|
|
258
|
+
</>
|
|
259
|
+
) : (
|
|
260
|
+
<>
|
|
261
|
+
<DetailLine label={a.detail.mcpInstalled} value={agent.installed ? a.detail.yes : a.detail.no} />
|
|
262
|
+
<DetailLine label={a.detail.mcpScope} value={agent.scope ?? a.na} />
|
|
263
|
+
<DetailLine label={a.detail.mcpConfigPath} value={agent.configPath ?? a.na} />
|
|
264
|
+
</>
|
|
265
|
+
)}
|
|
253
266
|
</div>
|
|
254
267
|
|
|
255
268
|
{/* Configured MCP servers with management */}
|
|
256
|
-
<div className="rounded-
|
|
269
|
+
<div className="rounded-xl border border-border/60 bg-background/50 p-4 space-y-2.5">
|
|
257
270
|
<div className="flex items-center justify-between">
|
|
258
271
|
<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>
|
|
272
|
+
<span className="text-2xs text-muted-foreground/60 tabular-nums">{a.detail.configuredMcpServersCount(configuredMcpServers.length)}</span>
|
|
260
273
|
</div>
|
|
261
274
|
|
|
262
275
|
{mcpHint && (
|
|
@@ -272,8 +285,10 @@ export default function AgentDetailContent({ agentKey }: { agentKey: string }) {
|
|
|
272
285
|
{configuredMcpServers.map((name) => {
|
|
273
286
|
const sharedWith = (crossAgentMcpMap.get(name) ?? []).filter((n) => n !== agent.name);
|
|
274
287
|
return (
|
|
275
|
-
<div key={name} className="flex items-center gap-2 rounded-
|
|
276
|
-
<
|
|
288
|
+
<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">
|
|
289
|
+
<div className="w-5 h-5 rounded-md bg-[var(--amber)]/[0.08] flex items-center justify-center shrink-0">
|
|
290
|
+
<Server size={10} className="text-[var(--amber)]" />
|
|
291
|
+
</div>
|
|
277
292
|
<span className="text-xs font-medium text-foreground flex-1 min-w-0 truncate">{name}</span>
|
|
278
293
|
{sharedWith.length > 0 && (
|
|
279
294
|
<div className="flex items-center gap-1">
|
|
@@ -300,38 +315,47 @@ export default function AgentDetailContent({ agentKey }: { agentKey: string }) {
|
|
|
300
315
|
|
|
301
316
|
{/* MCP actions */}
|
|
302
317
|
<div className="flex flex-wrap items-center gap-2">
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
318
|
+
{!isMindOS && (
|
|
319
|
+
<ActionButton
|
|
320
|
+
onClick={() => void handleCopySnippet()}
|
|
321
|
+
disabled={false}
|
|
322
|
+
busy={false}
|
|
323
|
+
label={snippetCopied ? a.detail.mcpCopied : a.detail.mcpCopySnippet}
|
|
324
|
+
/>
|
|
325
|
+
)}
|
|
309
326
|
<ActionButton
|
|
310
327
|
onClick={() => void mcp.refresh()}
|
|
311
328
|
disabled={false}
|
|
312
329
|
busy={false}
|
|
313
330
|
label={a.detail.mcpRefresh}
|
|
314
331
|
/>
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
332
|
+
{!isMindOS && (
|
|
333
|
+
<ActionButton
|
|
334
|
+
onClick={() => void handleApplyMcpConfig(currentScope, currentTransport)}
|
|
335
|
+
disabled={mcpBusy}
|
|
336
|
+
busy={mcpBusy}
|
|
337
|
+
label={a.detail.mcpReconnect}
|
|
338
|
+
variant="primary"
|
|
339
|
+
/>
|
|
340
|
+
)}
|
|
322
341
|
</div>
|
|
323
|
-
<p className="text-2xs text-muted-foreground truncate">{snippet.path}</p>
|
|
342
|
+
{!isMindOS && <p className="text-2xs text-muted-foreground truncate">{snippet.path}</p>}
|
|
324
343
|
{mcpMessage && <p className="text-xs text-muted-foreground animate-in fade-in duration-200">{mcpMessage}</p>}
|
|
325
344
|
</section>
|
|
326
345
|
|
|
327
346
|
{/* ═══════════ SKILL ASSIGNMENTS ═══════════ */}
|
|
328
|
-
<section className="rounded-
|
|
347
|
+
<section className="rounded-xl border border-border bg-card p-5 space-y-4">
|
|
329
348
|
<div className="flex items-center justify-between">
|
|
330
|
-
<h2 className="text-sm font-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
349
|
+
<h2 className="text-sm font-semibold text-foreground flex items-center gap-2">
|
|
350
|
+
<div className="w-6 h-6 rounded-md bg-muted/50 flex items-center justify-center">
|
|
351
|
+
<Zap size={13} className="text-muted-foreground/70" />
|
|
352
|
+
</div>
|
|
353
|
+
{a.detail.skillAssignments}
|
|
354
|
+
</h2>
|
|
355
|
+
<div className="flex items-center gap-2 text-2xs text-muted-foreground/60 tabular-nums">
|
|
356
|
+
<span className="px-1.5 py-0.5 rounded bg-muted/40">MindOS {skillSummary.total}</span>
|
|
357
|
+
<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>
|
|
358
|
+
<span className="px-1.5 py-0.5 rounded bg-muted/40">{a.detail.nativeInstalledSkills} {nativeInstalledSkills.length}</span>
|
|
335
359
|
</div>
|
|
336
360
|
</div>
|
|
337
361
|
|
|
@@ -504,9 +528,9 @@ export default function AgentDetailContent({ agentKey }: { agentKey: string }) {
|
|
|
504
528
|
|
|
505
529
|
function DetailLine({ label, value }: { label: string; value: string }) {
|
|
506
530
|
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>
|
|
531
|
+
<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">
|
|
532
|
+
<p className="text-2xs text-muted-foreground/60 mb-1 uppercase tracking-wider">{label}</p>
|
|
533
|
+
<p className="text-sm text-foreground font-medium truncate">{value}</p>
|
|
510
534
|
</div>
|
|
511
535
|
);
|
|
512
536
|
}
|
|
@@ -46,8 +46,9 @@ export default function AgentsContentPage({ tab }: { tab: AgentsDashboardTab })
|
|
|
46
46
|
detectedCount: buckets.detected.length,
|
|
47
47
|
notFoundCount: buckets.notFound.length,
|
|
48
48
|
allSkillsDisabled: mcp.skills.length > 0 && mcp.skills.every((s) => !s.enabled),
|
|
49
|
+
copy: a.overview,
|
|
49
50
|
}),
|
|
50
|
-
[mcp.skills, mcp.status?.running, buckets.detected.length, buckets.notFound.length],
|
|
51
|
+
[mcp.skills, mcp.status?.running, buckets.detected.length, buckets.notFound.length, a.overview],
|
|
51
52
|
);
|
|
52
53
|
const enabledSkillCount = useMemo(
|
|
53
54
|
() => mcp.skills.filter((skill) => skill.enabled).length,
|
|
@@ -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>
|
|
@@ -375,11 +381,11 @@ function AgentCard({
|
|
|
375
381
|
return (
|
|
376
382
|
<Link
|
|
377
383
|
href={`/agents/${encodeURIComponent(agent.key)}`}
|
|
378
|
-
className=
|
|
379
|
-
hover:border-[var(--amber)]/30 hover:shadow-
|
|
384
|
+
className={`group rounded-xl border border-border bg-card p-3.5
|
|
385
|
+
hover:border-[var(--amber)]/30 hover:shadow-[0_2px_8px_rgba(0,0,0,0.04)]
|
|
380
386
|
active:scale-[0.98]
|
|
381
387
|
transition-all duration-150 animate-in
|
|
382
|
-
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring
|
|
388
|
+
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring`}
|
|
383
389
|
style={{ animationDelay: `${Math.min(index * 30, 300)}ms` }}
|
|
384
390
|
>
|
|
385
391
|
{/* Top row: avatar + name + status */}
|
|
@@ -390,27 +396,18 @@ function AgentCard({
|
|
|
390
396
|
{agent.name}
|
|
391
397
|
</span>
|
|
392
398
|
{agent.transport && status === 'connected' && (
|
|
393
|
-
<span className="text-2xs text-muted-foreground font-mono">{agent.transport}</span>
|
|
399
|
+
<span className="text-2xs text-muted-foreground/60 font-mono">{agent.transport}</span>
|
|
394
400
|
)}
|
|
395
401
|
</div>
|
|
396
|
-
<span className={`text-2xs px-
|
|
402
|
+
<span className={`text-2xs px-2 py-0.5 rounded-full font-medium shrink-0 select-none ${statusColor}`}>
|
|
397
403
|
{statusLabel}
|
|
398
404
|
</span>
|
|
399
405
|
</div>
|
|
400
406
|
|
|
401
407
|
{/* Metrics row */}
|
|
402
|
-
<div className="flex items-center gap-
|
|
408
|
+
<div className="flex items-center gap-1 pt-2.5 border-t border-border/40">
|
|
403
409
|
<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
410
|
<MetricChip icon={<Zap size={11} aria-hidden="true" />} value={skillCount} label={copy.colSkills as string} />
|
|
406
|
-
{agent.skillMode && (
|
|
407
|
-
<>
|
|
408
|
-
<span className="text-border mx-2 select-none" aria-hidden="true">·</span>
|
|
409
|
-
<span className="text-2xs px-1.5 py-0.5 rounded bg-muted text-muted-foreground truncate select-none">
|
|
410
|
-
{agent.skillMode}
|
|
411
|
-
</span>
|
|
412
|
-
</>
|
|
413
|
-
)}
|
|
414
411
|
<span className="flex-1 min-w-[4px]" />
|
|
415
412
|
{hasRuntime && (
|
|
416
413
|
<span
|
|
@@ -438,9 +435,13 @@ function MetricChip({
|
|
|
438
435
|
label: string;
|
|
439
436
|
}) {
|
|
440
437
|
return (
|
|
441
|
-
<span
|
|
442
|
-
|
|
443
|
-
|
|
438
|
+
<span
|
|
439
|
+
className={`inline-flex items-center gap-1 px-1.5 py-0.5 rounded-md ${value > 0 ? 'bg-muted/40' : ''}`}
|
|
440
|
+
title={label}
|
|
441
|
+
aria-label={`${label}: ${value}`}
|
|
442
|
+
>
|
|
443
|
+
<span className={value > 0 ? 'text-muted-foreground' : 'text-muted-foreground/30'}>{icon}</span>
|
|
444
|
+
<span className={`tabular-nums text-xs ${value > 0 ? 'text-foreground font-medium' : 'text-muted-foreground/40'}`}>
|
|
444
445
|
{value}
|
|
445
446
|
</span>
|
|
446
447
|
</span>
|