@geminilight/mindos 0.6.27 → 0.6.28
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/components/agents/AgentDetailContent.tsx +48 -1
- package/app/components/agents/AgentsContentPage.tsx +3 -0
- package/app/components/agents/AgentsOverviewSection.tsx +11 -0
- package/app/components/ask/AskContent.tsx +8 -0
- package/app/components/help/HelpContent.tsx +65 -9
- package/app/components/panels/AgentsPanelAgentListRow.tsx +10 -1
- package/package.json +1 -1
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import Link from 'next/link';
|
|
4
4
|
import { useCallback, useMemo, useState } from 'react';
|
|
5
|
-
import { ArrowLeft, Server, Search, Trash2, Zap } from 'lucide-react';
|
|
5
|
+
import { ArrowLeft, ChevronDown, Globe, Server, Search, Trash2, Zap } from 'lucide-react';
|
|
6
|
+
import { cn } from '@/lib/utils';
|
|
6
7
|
import { useLocale } from '@/lib/LocaleContext';
|
|
7
8
|
import { useMcpData } from '@/hooks/useMcpData';
|
|
9
|
+
import { useA2aRegistry } from '@/hooks/useA2aRegistry';
|
|
8
10
|
import { apiFetch } from '@/lib/api';
|
|
9
11
|
import { copyToClipboard } from '@/lib/clipboard';
|
|
10
12
|
import { generateSnippet } from '@/lib/mcp-snippets';
|
|
@@ -21,7 +23,9 @@ import SkillDetailPopover from './SkillDetailPopover';
|
|
|
21
23
|
export default function AgentDetailContent({ agentKey }: { agentKey: string }) {
|
|
22
24
|
const { t } = useLocale();
|
|
23
25
|
const a = t.agentsContent;
|
|
26
|
+
const p = t.panels.agents;
|
|
24
27
|
const mcp = useMcpData();
|
|
28
|
+
const a2a = useA2aRegistry();
|
|
25
29
|
|
|
26
30
|
const agent = useMemo(() => mcp.agents.find((item) => item.key === agentKey), [mcp.agents, agentKey]);
|
|
27
31
|
const [skillQuery, setSkillQuery] = useState('');
|
|
@@ -39,6 +43,7 @@ export default function AgentDetailContent({ agentKey }: { agentKey: string }) {
|
|
|
39
43
|
const [confirmMcpRemove, setConfirmMcpRemove] = useState<string | null>(null);
|
|
40
44
|
const [mcpHint, setMcpHint] = useState<string | null>(null);
|
|
41
45
|
const [detailSkillName, setDetailSkillName] = useState<string | null>(null);
|
|
46
|
+
const [a2aOpen, setA2aOpen] = useState(false);
|
|
42
47
|
|
|
43
48
|
const filteredSkills = useMemo(
|
|
44
49
|
() =>
|
|
@@ -358,6 +363,48 @@ export default function AgentDetailContent({ agentKey }: { agentKey: string }) {
|
|
|
358
363
|
</div>
|
|
359
364
|
</section>
|
|
360
365
|
|
|
366
|
+
{/* ═══════════ A2A CAPABILITIES ═══════════ */}
|
|
367
|
+
<section className="rounded-xl border border-border bg-card overflow-hidden">
|
|
368
|
+
<button
|
|
369
|
+
type="button"
|
|
370
|
+
onClick={() => setA2aOpen(!a2aOpen)}
|
|
371
|
+
className="w-full flex items-center justify-between gap-2 px-4 py-3 text-left hover:bg-muted/20 transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
372
|
+
aria-expanded={a2aOpen}
|
|
373
|
+
>
|
|
374
|
+
<h2 className="text-xs font-semibold text-foreground flex items-center gap-2 shrink-0">
|
|
375
|
+
<Globe size={12} className="text-muted-foreground/50" />
|
|
376
|
+
{p.a2aCapabilities}
|
|
377
|
+
</h2>
|
|
378
|
+
<ChevronDown
|
|
379
|
+
size={13}
|
|
380
|
+
className={cn('shrink-0 text-muted-foreground/50 transition-transform duration-200', a2aOpen && 'rotate-180')}
|
|
381
|
+
aria-hidden="true"
|
|
382
|
+
/>
|
|
383
|
+
</button>
|
|
384
|
+
<div
|
|
385
|
+
className={cn(
|
|
386
|
+
'grid transition-[grid-template-rows] duration-250 ease-out',
|
|
387
|
+
a2aOpen ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]',
|
|
388
|
+
)}
|
|
389
|
+
>
|
|
390
|
+
<div className="overflow-hidden" {...(!a2aOpen && { inert: true } as React.HTMLAttributes<HTMLDivElement>)}>
|
|
391
|
+
<div className="px-4 pb-3 pt-1 space-y-2 border-t border-border/40">
|
|
392
|
+
<div className="flex items-baseline gap-2 px-0.5 min-w-0">
|
|
393
|
+
<span className="text-2xs text-muted-foreground/50 uppercase tracking-wider shrink-0 min-w-[60px]">{p.a2aStatus}</span>
|
|
394
|
+
<span className={`text-xs font-medium ${
|
|
395
|
+
status === 'connected' ? 'text-[var(--success)]' : 'text-muted-foreground'
|
|
396
|
+
}`}>
|
|
397
|
+
{status === 'connected' ? p.a2aConnected : p.a2aUnavailable}
|
|
398
|
+
</span>
|
|
399
|
+
</div>
|
|
400
|
+
{a2a.agents.length === 0 && (
|
|
401
|
+
<p className="text-2xs text-muted-foreground/50">{p.a2aNoRemoteHint}</p>
|
|
402
|
+
)}
|
|
403
|
+
</div>
|
|
404
|
+
</div>
|
|
405
|
+
</div>
|
|
406
|
+
</section>
|
|
407
|
+
|
|
361
408
|
{/* ═══════════ SKILL ASSIGNMENTS ═══════════ */}
|
|
362
409
|
<section className="rounded-xl border border-border bg-card p-4 space-y-3">
|
|
363
410
|
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useMemo, useState } from 'react';
|
|
4
4
|
import { useLocale } from '@/lib/LocaleContext';
|
|
5
5
|
import { useMcpData } from '@/hooks/useMcpData';
|
|
6
|
+
import { useA2aRegistry } from '@/hooks/useA2aRegistry';
|
|
6
7
|
import { copyToClipboard } from '@/lib/clipboard';
|
|
7
8
|
import { generateSnippet } from '@/lib/mcp-snippets';
|
|
8
9
|
import {
|
|
@@ -18,6 +19,7 @@ export default function AgentsContentPage({ tab }: { tab: AgentsDashboardTab })
|
|
|
18
19
|
const { t } = useLocale();
|
|
19
20
|
const a = t.agentsContent;
|
|
20
21
|
const mcp = useMcpData();
|
|
22
|
+
const a2a = useA2aRegistry();
|
|
21
23
|
const [copyState, setCopyState] = useState<string | null>(null);
|
|
22
24
|
const pageHeader = useMemo(() => {
|
|
23
25
|
if (tab === 'skills') {
|
|
@@ -86,6 +88,7 @@ export default function AgentsContentPage({ tab }: { tab: AgentsDashboardTab })
|
|
|
86
88
|
enabledSkillCount={enabledSkillCount}
|
|
87
89
|
allAgents={mcp.agents}
|
|
88
90
|
pulseCopy={a.workspacePulse}
|
|
91
|
+
a2aCount={a2a.agents.length}
|
|
89
92
|
/>
|
|
90
93
|
)}
|
|
91
94
|
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
ArrowRight,
|
|
8
8
|
Cable,
|
|
9
9
|
ChevronDown,
|
|
10
|
+
Globe,
|
|
10
11
|
Server,
|
|
11
12
|
Zap,
|
|
12
13
|
} from 'lucide-react';
|
|
@@ -63,6 +64,7 @@ export default function AgentsOverviewSection({
|
|
|
63
64
|
enabledSkillCount,
|
|
64
65
|
allAgents,
|
|
65
66
|
pulseCopy,
|
|
67
|
+
a2aCount,
|
|
66
68
|
}: {
|
|
67
69
|
copy: OverviewCopy;
|
|
68
70
|
buckets: AgentBuckets;
|
|
@@ -73,6 +75,7 @@ export default function AgentsOverviewSection({
|
|
|
73
75
|
enabledSkillCount: number;
|
|
74
76
|
allAgents: AgentInfo[];
|
|
75
77
|
pulseCopy: PulseCopy;
|
|
78
|
+
a2aCount?: number;
|
|
76
79
|
}) {
|
|
77
80
|
const allHealthy = riskQueue.length === 0 && mcpRunning;
|
|
78
81
|
const totalAgents = allAgents.length;
|
|
@@ -128,6 +131,14 @@ export default function AgentsOverviewSection({
|
|
|
128
131
|
value={mcpRunning ? `:${mcpPort}` : '—'}
|
|
129
132
|
tone={mcpRunning ? 'ok' : 'warn'}
|
|
130
133
|
/>
|
|
134
|
+
{a2aCount != null && (
|
|
135
|
+
<StatCell
|
|
136
|
+
icon={<Globe size={14} aria-hidden="true" />}
|
|
137
|
+
label={copy.a2aLabel as string ?? 'A2A'}
|
|
138
|
+
value={a2aCount}
|
|
139
|
+
tone={a2aCount > 0 ? 'ok' : 'muted'}
|
|
140
|
+
/>
|
|
141
|
+
)}
|
|
131
142
|
</div>
|
|
132
143
|
</section>
|
|
133
144
|
|
|
@@ -200,11 +200,15 @@ export default function AskContent({ visible, currentFile, initialMessage, onFir
|
|
|
200
200
|
|
|
201
201
|
// Focus and init session when becoming visible (edge-triggered for panel, level-triggered for modal)
|
|
202
202
|
const prevVisibleRef = useRef(false);
|
|
203
|
+
const prevFileRef = useRef(currentFile);
|
|
203
204
|
useEffect(() => {
|
|
204
205
|
const justOpened = variant === 'panel'
|
|
205
206
|
? (visible && !prevVisibleRef.current) // panel: edge detection
|
|
206
207
|
: visible; // modal: level detection (reset every open)
|
|
207
208
|
|
|
209
|
+
// Detect file change while panel is already open
|
|
210
|
+
const fileChanged = visible && prevVisibleRef.current && currentFile !== prevFileRef.current;
|
|
211
|
+
|
|
208
212
|
if (justOpened) {
|
|
209
213
|
setTimeout(() => inputRef.current?.focus(), 50);
|
|
210
214
|
void session.initSessions();
|
|
@@ -216,11 +220,15 @@ export default function AskContent({ visible, currentFile, initialMessage, onFir
|
|
|
216
220
|
slash.resetSlash();
|
|
217
221
|
setSelectedSkill(null);
|
|
218
222
|
setShowHistory(false);
|
|
223
|
+
} else if (fileChanged) {
|
|
224
|
+
// Update attached file context to match new file (don't reset session/messages)
|
|
225
|
+
setAttachedFiles(currentFile ? [currentFile] : []);
|
|
219
226
|
} else if (!visible && variant === 'modal') {
|
|
220
227
|
// Modal: abort streaming on close
|
|
221
228
|
abortRef.current?.abort();
|
|
222
229
|
}
|
|
223
230
|
prevVisibleRef.current = visible;
|
|
231
|
+
prevFileRef.current = currentFile;
|
|
224
232
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
225
233
|
}, [visible, currentFile]);
|
|
226
234
|
|
|
@@ -5,7 +5,8 @@ import { BookOpen, Rocket, Brain, Keyboard, HelpCircle, Bot, ChevronDown, Copy,
|
|
|
5
5
|
import { useLocale } from '@/lib/LocaleContext';
|
|
6
6
|
|
|
7
7
|
/* ── Collapsible Section ── */
|
|
8
|
-
function Section({ icon, title, defaultOpen = false, children }: {
|
|
8
|
+
function Section({ id, icon, title, defaultOpen = false, children }: {
|
|
9
|
+
id?: string;
|
|
9
10
|
icon: React.ReactNode;
|
|
10
11
|
title: string;
|
|
11
12
|
defaultOpen?: boolean;
|
|
@@ -14,7 +15,7 @@ function Section({ icon, title, defaultOpen = false, children }: {
|
|
|
14
15
|
const [open, setOpen] = useState(defaultOpen);
|
|
15
16
|
|
|
16
17
|
return (
|
|
17
|
-
<div className="bg-card border border-border rounded-lg overflow-hidden">
|
|
18
|
+
<div id={id} className="bg-card border border-border rounded-lg overflow-hidden scroll-mt-4">
|
|
18
19
|
<button
|
|
19
20
|
onClick={() => setOpen(v => !v)}
|
|
20
21
|
className="w-full flex items-center gap-3 px-5 py-4 text-left hover:bg-muted/50 transition-colors focus-visible:ring-2 focus-visible:ring-ring"
|
|
@@ -144,8 +145,63 @@ export default function HelpContent() {
|
|
|
144
145
|
{ keys: '@', label: h.shortcuts.attachFile },
|
|
145
146
|
], [mod, h.shortcuts]);
|
|
146
147
|
|
|
148
|
+
const tocItems = useMemo(() => [
|
|
149
|
+
{ id: 'what-is', label: h.whatIs.title },
|
|
150
|
+
{ id: 'concepts', label: h.concepts.title },
|
|
151
|
+
{ id: 'quick-start', label: h.quickStart.title },
|
|
152
|
+
{ id: 'agent-usage', label: h.agentUsage.title },
|
|
153
|
+
{ id: 'shortcuts', label: h.shortcutsTitle },
|
|
154
|
+
{ id: 'faq', label: h.faq.title },
|
|
155
|
+
], [h]);
|
|
156
|
+
|
|
157
|
+
const [activeSection, setActiveSection] = useState('');
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
const ids = tocItems.map(i => i.id);
|
|
160
|
+
const observer = new IntersectionObserver(
|
|
161
|
+
(entries) => {
|
|
162
|
+
for (const entry of entries) {
|
|
163
|
+
if (entry.isIntersecting) {
|
|
164
|
+
setActiveSection(entry.target.id);
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
{ rootMargin: '-10% 0px -80% 0px', threshold: 0 },
|
|
170
|
+
);
|
|
171
|
+
const timer = setTimeout(() => {
|
|
172
|
+
for (const id of ids) {
|
|
173
|
+
const el = document.getElementById(id);
|
|
174
|
+
if (el) observer.observe(el);
|
|
175
|
+
}
|
|
176
|
+
}, 100);
|
|
177
|
+
return () => { clearTimeout(timer); observer.disconnect(); };
|
|
178
|
+
}, [tocItems]);
|
|
179
|
+
|
|
147
180
|
return (
|
|
148
|
-
<div className="content-width px-4 md:px-6 py-8 md:py-12">
|
|
181
|
+
<div className="content-width px-4 md:px-6 py-8 md:py-12 relative">
|
|
182
|
+
{/* ── Floating TOC (wide screens only) ── */}
|
|
183
|
+
<nav className="hidden xl:block fixed top-24 w-44" style={{ left: 'calc(50% + 340px)' }} aria-label="Table of contents">
|
|
184
|
+
<ul className="space-y-1 border-l border-border pl-3">
|
|
185
|
+
{tocItems.map(item => (
|
|
186
|
+
<li key={item.id}>
|
|
187
|
+
<a
|
|
188
|
+
href={`#${item.id}`}
|
|
189
|
+
onClick={(e) => {
|
|
190
|
+
e.preventDefault();
|
|
191
|
+
document.getElementById(item.id)?.scrollIntoView({ behavior: 'smooth' });
|
|
192
|
+
}}
|
|
193
|
+
className={`block text-xs py-1 transition-colors ${
|
|
194
|
+
activeSection === item.id
|
|
195
|
+
? 'text-[var(--amber)] font-medium border-l-2 border-[var(--amber)] -ml-[13px] pl-[11px]'
|
|
196
|
+
: 'text-muted-foreground hover:text-foreground'
|
|
197
|
+
}`}
|
|
198
|
+
>
|
|
199
|
+
{item.label}
|
|
200
|
+
</a>
|
|
201
|
+
</li>
|
|
202
|
+
))}
|
|
203
|
+
</ul>
|
|
204
|
+
</nav>
|
|
149
205
|
{/* ── Header ── */}
|
|
150
206
|
<div className="mb-8">
|
|
151
207
|
<div className="flex items-center gap-2 mb-1">
|
|
@@ -158,12 +214,12 @@ export default function HelpContent() {
|
|
|
158
214
|
{/* ── Sections ── */}
|
|
159
215
|
<div className="space-y-3">
|
|
160
216
|
{/* 1. What is MindOS */}
|
|
161
|
-
<Section icon={<BookOpen size={18} />} title={h.whatIs.title} defaultOpen>
|
|
217
|
+
<Section id="what-is" icon={<BookOpen size={18} />} title={h.whatIs.title} defaultOpen>
|
|
162
218
|
<p className="text-sm text-muted-foreground leading-relaxed">{h.whatIs.body}</p>
|
|
163
219
|
</Section>
|
|
164
220
|
|
|
165
221
|
{/* 2. Core Concepts */}
|
|
166
|
-
<Section icon={<Brain size={18} />} title={h.concepts.title} defaultOpen>
|
|
222
|
+
<Section id="concepts" icon={<Brain size={18} />} title={h.concepts.title} defaultOpen>
|
|
167
223
|
<div className="space-y-4">
|
|
168
224
|
<div>
|
|
169
225
|
<p className="text-sm font-medium text-foreground">{h.concepts.spaceTitle}</p>
|
|
@@ -181,7 +237,7 @@ export default function HelpContent() {
|
|
|
181
237
|
</Section>
|
|
182
238
|
|
|
183
239
|
{/* 3. Quick Start */}
|
|
184
|
-
<Section icon={<Rocket size={18} />} title={h.quickStart.title} defaultOpen>
|
|
240
|
+
<Section id="quick-start" icon={<Rocket size={18} />} title={h.quickStart.title} defaultOpen>
|
|
185
241
|
<div className="space-y-4">
|
|
186
242
|
<StepCard step={1} title={h.quickStart.step1Title} desc={h.quickStart.step1Desc} />
|
|
187
243
|
<StepCard step={2} title={h.quickStart.step2Title} desc={h.quickStart.step2Desc} />
|
|
@@ -190,7 +246,7 @@ export default function HelpContent() {
|
|
|
190
246
|
</Section>
|
|
191
247
|
|
|
192
248
|
{/* 4. Using MindOS with AI Agents */}
|
|
193
|
-
<Section icon={<Bot size={18} />} title={h.agentUsage.title} defaultOpen>
|
|
249
|
+
<Section id="agent-usage" icon={<Bot size={18} />} title={h.agentUsage.title} defaultOpen>
|
|
194
250
|
<p className="text-sm text-muted-foreground leading-relaxed mb-4">{h.agentUsage.intro}</p>
|
|
195
251
|
|
|
196
252
|
<div className="space-y-3">
|
|
@@ -215,7 +271,7 @@ export default function HelpContent() {
|
|
|
215
271
|
</Section>
|
|
216
272
|
|
|
217
273
|
{/* 5. Keyboard Shortcuts */}
|
|
218
|
-
<Section icon={<Keyboard size={18} />} title={h.shortcutsTitle}>
|
|
274
|
+
<Section id="shortcuts" icon={<Keyboard size={18} />} title={h.shortcutsTitle}>
|
|
219
275
|
<div className="space-y-0">
|
|
220
276
|
{shortcuts.map((s) => (
|
|
221
277
|
<ShortcutRow key={s.keys} keys={s.keys} label={s.label} />
|
|
@@ -224,7 +280,7 @@ export default function HelpContent() {
|
|
|
224
280
|
</Section>
|
|
225
281
|
|
|
226
282
|
{/* 6. FAQ */}
|
|
227
|
-
<Section icon={<HelpCircle size={18} />} title={h.faq.title}>
|
|
283
|
+
<Section id="faq" icon={<HelpCircle size={18} />} title={h.faq.title}>
|
|
228
284
|
<div>
|
|
229
285
|
{h.faq.items.map((item, i) => (
|
|
230
286
|
<FaqItem key={i} q={item.q} a={item.a} />
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import Link from 'next/link';
|
|
5
|
-
import { Check, ChevronRight, Loader2, RotateCw } from 'lucide-react';
|
|
5
|
+
import { Check, ChevronRight, Globe, Loader2, RotateCw } from 'lucide-react';
|
|
6
6
|
import type { AgentInfo } from '../settings/types';
|
|
7
7
|
import { AgentAvatar } from '../agents/AgentsPrimitives';
|
|
8
8
|
|
|
@@ -23,6 +23,8 @@ export default function AgentsPanelAgentListRow({
|
|
|
23
23
|
detailHref,
|
|
24
24
|
onInstallAgent,
|
|
25
25
|
copy,
|
|
26
|
+
isA2aReady = false,
|
|
27
|
+
a2aTooltip,
|
|
26
28
|
}: {
|
|
27
29
|
agent: AgentInfo;
|
|
28
30
|
agentStatus: AgentsPanelAgentListStatus;
|
|
@@ -30,6 +32,8 @@ export default function AgentsPanelAgentListRow({
|
|
|
30
32
|
detailHref: string;
|
|
31
33
|
onInstallAgent: (key: string) => Promise<boolean>;
|
|
32
34
|
copy: AgentsPanelAgentListRowCopy;
|
|
35
|
+
isA2aReady?: boolean;
|
|
36
|
+
a2aTooltip?: string;
|
|
33
37
|
}) {
|
|
34
38
|
return (
|
|
35
39
|
<div
|
|
@@ -51,6 +55,11 @@ export default function AgentsPanelAgentListRow({
|
|
|
51
55
|
{agent.transport}
|
|
52
56
|
</span>
|
|
53
57
|
)}
|
|
58
|
+
{isA2aReady && (
|
|
59
|
+
<span title={a2aTooltip} className="shrink-0 text-muted-foreground/70">
|
|
60
|
+
<Globe size={12} />
|
|
61
|
+
</span>
|
|
62
|
+
)}
|
|
54
63
|
<span className="flex-1 min-w-[4px]" />
|
|
55
64
|
<ChevronRight
|
|
56
65
|
size={15}
|
package/package.json
CHANGED