@geminilight/mindos 0.5.70 → 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/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 +3 -4
- package/app/components/CreateSpaceModal.tsx +31 -6
- package/app/components/FileTree.tsx +33 -2
- package/app/components/GuideCard.tsx +197 -131
- package/app/components/HomeContent.tsx +85 -18
- package/app/components/SidebarLayout.tsx +13 -0
- package/app/components/SpaceInitToast.tsx +173 -0
- package/app/components/agents/AgentDetailContent.tsx +32 -17
- package/app/components/agents/AgentsContentPage.tsx +2 -1
- package/app/components/agents/AgentsOverviewSection.tsx +1 -14
- package/app/components/agents/agents-content-model.ts +16 -8
- package/app/components/ask/AskContent.tsx +137 -50
- package/app/components/ask/MentionPopover.tsx +16 -8
- package/app/components/ask/SlashCommandPopover.tsx +62 -0
- package/app/components/settings/KnowledgeTab.tsx +61 -0
- package/app/components/walkthrough/steps.ts +11 -6
- 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 +106 -0
- package/app/lib/core/create-space.ts +11 -4
- package/app/lib/core/index.ts +1 -1
- package/app/lib/i18n-en.ts +51 -46
- package/app/lib/i18n-zh.ts +50 -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
|
+
}
|
|
@@ -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';
|
|
@@ -249,9 +250,19 @@ export default function AgentDetailContent({ agentKey }: { agentKey: string }) {
|
|
|
249
250
|
|
|
250
251
|
{/* MCP status row */}
|
|
251
252
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 text-sm">
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
+
)}
|
|
255
266
|
</div>
|
|
256
267
|
|
|
257
268
|
{/* Configured MCP servers with management */}
|
|
@@ -304,27 +315,31 @@ export default function AgentDetailContent({ agentKey }: { agentKey: string }) {
|
|
|
304
315
|
|
|
305
316
|
{/* MCP actions */}
|
|
306
317
|
<div className="flex flex-wrap items-center gap-2">
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
+
)}
|
|
313
326
|
<ActionButton
|
|
314
327
|
onClick={() => void mcp.refresh()}
|
|
315
328
|
disabled={false}
|
|
316
329
|
busy={false}
|
|
317
330
|
label={a.detail.mcpRefresh}
|
|
318
331
|
/>
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
+
)}
|
|
326
341
|
</div>
|
|
327
|
-
<p className="text-2xs text-muted-foreground truncate">{snippet.path}</p>
|
|
342
|
+
{!isMindOS && <p className="text-2xs text-muted-foreground truncate">{snippet.path}</p>}
|
|
328
343
|
{mcpMessage && <p className="text-xs text-muted-foreground animate-in fade-in duration-200">{mcpMessage}</p>}
|
|
329
344
|
</section>
|
|
330
345
|
|
|
@@ -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,
|
|
@@ -378,18 +378,10 @@ function AgentCard({
|
|
|
378
378
|
? 'bg-amber-500/10 text-amber-600 dark:text-amber-400'
|
|
379
379
|
: 'bg-zinc-500/10 text-zinc-500';
|
|
380
380
|
|
|
381
|
-
const accentBorder =
|
|
382
|
-
status === 'connected'
|
|
383
|
-
? 'border-l-[var(--success)]'
|
|
384
|
-
: status === 'detected'
|
|
385
|
-
? 'border-l-[var(--amber)]'
|
|
386
|
-
: '';
|
|
387
|
-
|
|
388
381
|
return (
|
|
389
382
|
<Link
|
|
390
383
|
href={`/agents/${encodeURIComponent(agent.key)}`}
|
|
391
|
-
className={`group rounded-xl border bg-card p-3.5
|
|
392
|
-
${accentBorder ? `border-l-2 ${accentBorder} border-border` : 'border-border'}
|
|
384
|
+
className={`group rounded-xl border border-border bg-card p-3.5
|
|
393
385
|
hover:border-[var(--amber)]/30 hover:shadow-[0_2px_8px_rgba(0,0,0,0.04)]
|
|
394
386
|
active:scale-[0.98]
|
|
395
387
|
transition-all duration-150 animate-in
|
|
@@ -416,11 +408,6 @@ function AgentCard({
|
|
|
416
408
|
<div className="flex items-center gap-1 pt-2.5 border-t border-border/40">
|
|
417
409
|
<MetricChip icon={<Server size={11} aria-hidden="true" />} value={mcpCount} label={copy.colMcp as string} />
|
|
418
410
|
<MetricChip icon={<Zap size={11} aria-hidden="true" />} value={skillCount} label={copy.colSkills as string} />
|
|
419
|
-
{agent.skillMode && (
|
|
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>
|
|
423
|
-
)}
|
|
424
411
|
<span className="flex-1 min-w-[4px]" />
|
|
425
412
|
{hasRuntime && (
|
|
426
413
|
<span
|
|
@@ -56,11 +56,10 @@ export function groupSkillsByCapability(skills: SkillInfo[]): Record<SkillCapabi
|
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return items;
|
|
59
|
+
export interface RiskCopy {
|
|
60
|
+
riskMcpStopped: string;
|
|
61
|
+
riskDetected: (n: number) => string;
|
|
62
|
+
riskSkillsDisabled: string;
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
export function buildRiskQueue(args: {
|
|
@@ -68,9 +67,12 @@ export function buildRiskQueue(args: {
|
|
|
68
67
|
detectedCount: number;
|
|
69
68
|
notFoundCount: number;
|
|
70
69
|
allSkillsDisabled: boolean;
|
|
70
|
+
copy: RiskCopy;
|
|
71
71
|
}): RiskItem[] {
|
|
72
|
-
const items =
|
|
73
|
-
if (args.
|
|
72
|
+
const items: RiskItem[] = [];
|
|
73
|
+
if (!args.mcpRunning) items.push({ id: 'mcp-stopped', severity: 'error', title: args.copy.riskMcpStopped });
|
|
74
|
+
if (args.detectedCount > 0) items.push({ id: 'detected-unconfigured', severity: 'warn', title: args.copy.riskDetected(args.detectedCount) });
|
|
75
|
+
if (args.allSkillsDisabled) items.push({ id: 'skills-disabled', severity: 'warn', title: args.copy.riskSkillsDisabled });
|
|
74
76
|
return items;
|
|
75
77
|
}
|
|
76
78
|
|
|
@@ -178,12 +180,18 @@ export function filterAgentsForMcpWorkspace(
|
|
|
178
180
|
});
|
|
179
181
|
}
|
|
180
182
|
|
|
183
|
+
const defaultRiskCopy: RiskCopy = {
|
|
184
|
+
riskMcpStopped: 'MCP server is not running.',
|
|
185
|
+
riskDetected: (n: number) => `${n} detected agent(s) need configuration.`,
|
|
186
|
+
riskSkillsDisabled: 'All skills are disabled.',
|
|
187
|
+
};
|
|
188
|
+
|
|
181
189
|
export function buildMcpRiskQueue(args: {
|
|
182
190
|
mcpRunning: boolean;
|
|
183
191
|
detectedCount: number;
|
|
184
192
|
notFoundCount: number;
|
|
185
193
|
}): RiskItem[] {
|
|
186
|
-
return
|
|
194
|
+
return buildRiskQueue({ ...args, allSkillsDisabled: false, copy: defaultRiskCopy });
|
|
187
195
|
}
|
|
188
196
|
|
|
189
197
|
export interface McpBulkReconnectResult {
|