@geminilight/mindos 0.5.51 → 0.5.54
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/README.md +7 -7
- package/README_zh.md +5 -5
- package/app/app/layout.tsx +2 -0
- package/app/components/ActivityBar.tsx +3 -2
- package/app/components/Panel.tsx +1 -0
- package/app/components/RightAgentDetailPanel.tsx +121 -0
- package/app/components/SidebarLayout.tsx +62 -5
- package/app/components/UpdateOverlay.tsx +124 -0
- package/app/components/help/HelpContent.tsx +10 -7
- package/app/components/panels/AgentsPanel.tsx +156 -178
- package/app/components/panels/AgentsPanelAgentDetail.tsx +193 -0
- package/app/components/panels/AgentsPanelAgentGroups.tsx +116 -0
- package/app/components/panels/AgentsPanelAgentListRow.tsx +101 -0
- package/app/components/panels/AgentsPanelHubNav.tsx +48 -0
- package/app/components/panels/DiscoverPanel.tsx +6 -46
- package/app/components/panels/EchoPanel.tsx +82 -0
- package/app/components/panels/PanelNavRow.tsx +51 -0
- package/app/components/panels/agents-panel-resolve-status.ts +13 -0
- package/app/components/settings/McpSkillsSection.tsx +88 -2
- package/app/components/settings/McpTab.tsx +26 -0
- package/app/components/settings/UpdateTab.tsx +65 -27
- package/app/lib/i18n-en.ts +31 -2
- package/app/lib/i18n-zh.ts +30 -2
- package/app/next-env.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { ChevronDown, ChevronRight } from 'lucide-react';
|
|
4
|
+
import type { AgentInfo } from '../settings/types';
|
|
5
|
+
import type { McpContextValue } from '@/hooks/useMcpData';
|
|
6
|
+
import AgentsPanelAgentListRow, { type AgentsPanelAgentListRowCopy } from './AgentsPanelAgentListRow';
|
|
7
|
+
|
|
8
|
+
type AgentsCopy = {
|
|
9
|
+
rosterLabel: string;
|
|
10
|
+
sectionConnected: string;
|
|
11
|
+
sectionDetected: string;
|
|
12
|
+
sectionNotDetected: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function AgentsPanelAgentGroups({
|
|
16
|
+
connected,
|
|
17
|
+
detected,
|
|
18
|
+
notFound,
|
|
19
|
+
onOpenDetail,
|
|
20
|
+
selectedAgentKey,
|
|
21
|
+
mcp,
|
|
22
|
+
listCopy,
|
|
23
|
+
showNotDetected,
|
|
24
|
+
setShowNotDetected,
|
|
25
|
+
p,
|
|
26
|
+
}: {
|
|
27
|
+
connected: AgentInfo[];
|
|
28
|
+
detected: AgentInfo[];
|
|
29
|
+
notFound: AgentInfo[];
|
|
30
|
+
onOpenDetail?: (key: string) => void;
|
|
31
|
+
selectedAgentKey?: string | null;
|
|
32
|
+
mcp: Pick<McpContextValue, 'installAgent'>;
|
|
33
|
+
listCopy: AgentsPanelAgentListRowCopy;
|
|
34
|
+
showNotDetected: boolean;
|
|
35
|
+
setShowNotDetected: (v: boolean | ((prev: boolean) => boolean)) => void;
|
|
36
|
+
p: AgentsCopy;
|
|
37
|
+
}) {
|
|
38
|
+
const open = onOpenDetail ?? (() => {});
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div>
|
|
42
|
+
<div className="px-0 py-1 mb-0.5">
|
|
43
|
+
<span className="text-2xs font-semibold text-muted-foreground uppercase tracking-wider">{p.rosterLabel}</span>
|
|
44
|
+
</div>
|
|
45
|
+
{connected.length > 0 && (
|
|
46
|
+
<section className="mb-3">
|
|
47
|
+
<h3 className="text-[11px] font-medium text-muted-foreground/90 uppercase tracking-wider mb-2 pl-0.5">
|
|
48
|
+
{p.sectionConnected} ({connected.length})
|
|
49
|
+
</h3>
|
|
50
|
+
<div className="space-y-1.5">
|
|
51
|
+
{connected.map(agent => (
|
|
52
|
+
<AgentsPanelAgentListRow
|
|
53
|
+
key={agent.key}
|
|
54
|
+
agent={agent}
|
|
55
|
+
agentStatus="connected"
|
|
56
|
+
selected={selectedAgentKey === agent.key}
|
|
57
|
+
onOpenDetail={() => open(agent.key)}
|
|
58
|
+
onInstallAgent={mcp.installAgent}
|
|
59
|
+
copy={listCopy}
|
|
60
|
+
/>
|
|
61
|
+
))}
|
|
62
|
+
</div>
|
|
63
|
+
</section>
|
|
64
|
+
)}
|
|
65
|
+
|
|
66
|
+
{detected.length > 0 && (
|
|
67
|
+
<section className="mb-3">
|
|
68
|
+
<h3 className="text-[11px] font-medium text-muted-foreground/90 uppercase tracking-wider mb-2 pl-0.5">
|
|
69
|
+
{p.sectionDetected} ({detected.length})
|
|
70
|
+
</h3>
|
|
71
|
+
<div className="space-y-1.5">
|
|
72
|
+
{detected.map(agent => (
|
|
73
|
+
<AgentsPanelAgentListRow
|
|
74
|
+
key={agent.key}
|
|
75
|
+
agent={agent}
|
|
76
|
+
agentStatus="detected"
|
|
77
|
+
selected={selectedAgentKey === agent.key}
|
|
78
|
+
onOpenDetail={() => open(agent.key)}
|
|
79
|
+
onInstallAgent={mcp.installAgent}
|
|
80
|
+
copy={listCopy}
|
|
81
|
+
/>
|
|
82
|
+
))}
|
|
83
|
+
</div>
|
|
84
|
+
</section>
|
|
85
|
+
)}
|
|
86
|
+
|
|
87
|
+
{notFound.length > 0 && (
|
|
88
|
+
<section>
|
|
89
|
+
<button
|
|
90
|
+
type="button"
|
|
91
|
+
onClick={() => setShowNotDetected(!showNotDetected)}
|
|
92
|
+
className="flex items-center gap-1 text-[11px] font-medium text-muted-foreground uppercase tracking-wider mb-2 hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm pl-0.5"
|
|
93
|
+
>
|
|
94
|
+
{showNotDetected ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
|
|
95
|
+
{p.sectionNotDetected} ({notFound.length})
|
|
96
|
+
</button>
|
|
97
|
+
{showNotDetected && (
|
|
98
|
+
<div className="space-y-1.5">
|
|
99
|
+
{notFound.map(agent => (
|
|
100
|
+
<AgentsPanelAgentListRow
|
|
101
|
+
key={agent.key}
|
|
102
|
+
agent={agent}
|
|
103
|
+
agentStatus="notFound"
|
|
104
|
+
selected={selectedAgentKey === agent.key}
|
|
105
|
+
onOpenDetail={() => open(agent.key)}
|
|
106
|
+
onInstallAgent={mcp.installAgent}
|
|
107
|
+
copy={listCopy}
|
|
108
|
+
/>
|
|
109
|
+
))}
|
|
110
|
+
</div>
|
|
111
|
+
)}
|
|
112
|
+
</section>
|
|
113
|
+
)}
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { ChevronRight, Loader2 } from 'lucide-react';
|
|
5
|
+
import type { AgentInfo } from '../settings/types';
|
|
6
|
+
|
|
7
|
+
export type AgentsPanelAgentListStatus = 'connected' | 'detected' | 'notFound';
|
|
8
|
+
|
|
9
|
+
export interface AgentsPanelAgentListRowCopy {
|
|
10
|
+
installing: string;
|
|
11
|
+
install: (name: string) => string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function AgentsPanelAgentListRow({
|
|
15
|
+
agent,
|
|
16
|
+
agentStatus,
|
|
17
|
+
selected = false,
|
|
18
|
+
onOpenDetail,
|
|
19
|
+
onInstallAgent,
|
|
20
|
+
copy,
|
|
21
|
+
}: {
|
|
22
|
+
agent: AgentInfo;
|
|
23
|
+
agentStatus: AgentsPanelAgentListStatus;
|
|
24
|
+
selected?: boolean;
|
|
25
|
+
onOpenDetail: () => void;
|
|
26
|
+
onInstallAgent: (key: string) => Promise<boolean>;
|
|
27
|
+
copy: AgentsPanelAgentListRowCopy;
|
|
28
|
+
}) {
|
|
29
|
+
const dot =
|
|
30
|
+
agentStatus === 'connected' ? 'bg-emerald-500' : agentStatus === 'detected' ? 'bg-amber-500' : 'bg-zinc-400';
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
className={`
|
|
35
|
+
group flex items-center gap-0 rounded-xl border transition-all duration-150
|
|
36
|
+
${selected
|
|
37
|
+
? 'border-border ring-2 ring-ring/50 bg-[var(--amber-dim)]/45'
|
|
38
|
+
: 'border-border/70 bg-card/50 hover:border-border hover:bg-muted/25'}
|
|
39
|
+
`}
|
|
40
|
+
>
|
|
41
|
+
<button
|
|
42
|
+
type="button"
|
|
43
|
+
onClick={onOpenDetail}
|
|
44
|
+
className="flex flex-1 min-w-0 items-center gap-2.5 text-left rounded-xl pl-3 pr-2 py-2.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
45
|
+
>
|
|
46
|
+
<span className={`w-2 h-2 rounded-full shrink-0 ring-2 ring-background ${dot}`} />
|
|
47
|
+
<span className="text-sm font-medium text-foreground truncate leading-tight">{agent.name}</span>
|
|
48
|
+
{agentStatus === 'connected' && agent.transport && (
|
|
49
|
+
<span className="text-2xs font-mono tabular-nums px-1.5 py-0.5 rounded-md bg-muted/90 text-muted-foreground shrink-0 border border-border/50">
|
|
50
|
+
{agent.transport}
|
|
51
|
+
</span>
|
|
52
|
+
)}
|
|
53
|
+
<span className="flex-1 min-w-[4px]" />
|
|
54
|
+
<ChevronRight
|
|
55
|
+
size={15}
|
|
56
|
+
className={`shrink-0 transition-opacity duration-150 ${selected ? 'text-[var(--amber)] opacity-90' : 'text-muted-foreground/45 group-hover:text-muted-foreground/80'}`}
|
|
57
|
+
aria-hidden
|
|
58
|
+
/>
|
|
59
|
+
</button>
|
|
60
|
+
|
|
61
|
+
{agentStatus === 'detected' && (
|
|
62
|
+
<div className="pr-2 py-2 shrink-0">
|
|
63
|
+
<AgentInstallButton agentKey={agent.key} agentName={agent.name} onInstallAgent={onInstallAgent} copy={copy} />
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function AgentInstallButton({
|
|
71
|
+
agentKey,
|
|
72
|
+
agentName,
|
|
73
|
+
onInstallAgent,
|
|
74
|
+
copy,
|
|
75
|
+
}: {
|
|
76
|
+
agentKey: string;
|
|
77
|
+
agentName: string;
|
|
78
|
+
onInstallAgent: (key: string) => Promise<boolean>;
|
|
79
|
+
copy: AgentsPanelAgentListRowCopy;
|
|
80
|
+
}) {
|
|
81
|
+
const [installing, setInstalling] = useState(false);
|
|
82
|
+
|
|
83
|
+
const handleInstall = async (e: React.MouseEvent) => {
|
|
84
|
+
e.stopPropagation();
|
|
85
|
+
setInstalling(true);
|
|
86
|
+
await onInstallAgent(agentKey);
|
|
87
|
+
setInstalling(false);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<button
|
|
92
|
+
type="button"
|
|
93
|
+
onClick={handleInstall}
|
|
94
|
+
disabled={installing}
|
|
95
|
+
className="flex items-center gap-1 px-2 py-1.5 text-2xs rounded-lg font-medium text-[var(--amber-foreground)] disabled:opacity-50 transition-colors shrink-0 bg-[var(--amber)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
96
|
+
>
|
|
97
|
+
{installing ? <Loader2 size={10} className="animate-spin" /> : null}
|
|
98
|
+
{installing ? copy.installing : copy.install(agentName)}
|
|
99
|
+
</button>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { type RefObject } from 'react';
|
|
4
|
+
import { LayoutDashboard, Server, Zap } from 'lucide-react';
|
|
5
|
+
import { PanelNavRow } from './PanelNavRow';
|
|
6
|
+
|
|
7
|
+
type HubCopy = {
|
|
8
|
+
navOverview: string;
|
|
9
|
+
navMcp: string;
|
|
10
|
+
navSkills: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function AgentsPanelHubNav({
|
|
14
|
+
copy,
|
|
15
|
+
connectedCount,
|
|
16
|
+
overviewRef,
|
|
17
|
+
skillsRef,
|
|
18
|
+
scrollTo,
|
|
19
|
+
openAdvancedConfig,
|
|
20
|
+
}: {
|
|
21
|
+
copy: HubCopy;
|
|
22
|
+
connectedCount: number;
|
|
23
|
+
overviewRef: RefObject<HTMLDivElement | null>;
|
|
24
|
+
skillsRef: RefObject<HTMLDivElement | null>;
|
|
25
|
+
scrollTo: (el: HTMLElement | null) => void;
|
|
26
|
+
openAdvancedConfig: () => void;
|
|
27
|
+
}) {
|
|
28
|
+
return (
|
|
29
|
+
<div className="py-2">
|
|
30
|
+
<PanelNavRow
|
|
31
|
+
icon={<LayoutDashboard size={14} className="text-[var(--amber)]" />}
|
|
32
|
+
title={copy.navOverview}
|
|
33
|
+
badge={<span className="text-2xs tabular-nums text-muted-foreground">{connectedCount}</span>}
|
|
34
|
+
onClick={() => scrollTo(overviewRef.current)}
|
|
35
|
+
/>
|
|
36
|
+
<PanelNavRow
|
|
37
|
+
icon={<Server size={14} className="text-muted-foreground" />}
|
|
38
|
+
title={copy.navMcp}
|
|
39
|
+
onClick={openAdvancedConfig}
|
|
40
|
+
/>
|
|
41
|
+
<PanelNavRow
|
|
42
|
+
icon={<Zap size={14} className="text-muted-foreground" />}
|
|
43
|
+
title={copy.navSkills}
|
|
44
|
+
onClick={() => scrollTo(skillsRef.current)}
|
|
45
|
+
/>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import { Lightbulb, Blocks, Zap, LayoutTemplate, ChevronRight, User, Download, RefreshCw, Repeat, Rocket, Search, Handshake, ShieldCheck } from 'lucide-react';
|
|
3
|
+
import { Lightbulb, Blocks, Zap, LayoutTemplate, User, Download, RefreshCw, Repeat, Rocket, Search, Handshake, ShieldCheck } from 'lucide-react';
|
|
5
4
|
import PanelHeader from './PanelHeader';
|
|
5
|
+
import { PanelNavRow, ComingSoonBadge } from './PanelNavRow';
|
|
6
6
|
import { useLocale } from '@/lib/LocaleContext';
|
|
7
7
|
import { useCases } from '@/components/explore/use-cases';
|
|
8
8
|
import { openAskModal } from '@/hooks/useAskModal';
|
|
@@ -13,46 +13,6 @@ interface DiscoverPanelProps {
|
|
|
13
13
|
onMaximize?: () => void;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
/** Navigation entry — clickable row linking to a page or showing coming soon */
|
|
17
|
-
function NavEntry({
|
|
18
|
-
icon,
|
|
19
|
-
title,
|
|
20
|
-
badge,
|
|
21
|
-
href,
|
|
22
|
-
onClick,
|
|
23
|
-
}: {
|
|
24
|
-
icon: React.ReactNode;
|
|
25
|
-
title: string;
|
|
26
|
-
badge?: React.ReactNode;
|
|
27
|
-
href?: string;
|
|
28
|
-
onClick?: () => void;
|
|
29
|
-
}) {
|
|
30
|
-
const content = (
|
|
31
|
-
<>
|
|
32
|
-
<span className="flex items-center justify-center w-7 h-7 rounded-md bg-muted shrink-0">{icon}</span>
|
|
33
|
-
<span className="text-sm font-medium text-foreground flex-1">{title}</span>
|
|
34
|
-
{badge}
|
|
35
|
-
<ChevronRight size={14} className="text-muted-foreground shrink-0" />
|
|
36
|
-
</>
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
const className = "flex items-center gap-3 px-4 py-2.5 hover:bg-muted/50 transition-colors cursor-pointer";
|
|
40
|
-
|
|
41
|
-
if (href) {
|
|
42
|
-
return <Link href={href} className={className}>{content}</Link>;
|
|
43
|
-
}
|
|
44
|
-
return <button onClick={onClick} className={`${className} w-full`}>{content}</button>;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** Coming soon badge */
|
|
48
|
-
function ComingSoonBadge({ label }: { label: string }) {
|
|
49
|
-
return (
|
|
50
|
-
<span className="text-2xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground shrink-0">
|
|
51
|
-
{label}
|
|
52
|
-
</span>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
16
|
/** Compact use case row */
|
|
57
17
|
function UseCaseRow({
|
|
58
18
|
icon,
|
|
@@ -112,23 +72,23 @@ export default function DiscoverPanel({ active, maximized, onMaximize }: Discove
|
|
|
112
72
|
<div className="flex-1 overflow-y-auto min-h-0">
|
|
113
73
|
{/* Navigation entries */}
|
|
114
74
|
<div className="py-2">
|
|
115
|
-
<
|
|
75
|
+
<PanelNavRow
|
|
116
76
|
icon={<Lightbulb size={14} className="text-[var(--amber)]" />}
|
|
117
77
|
title={d.useCases}
|
|
118
78
|
badge={<span className="text-2xs tabular-nums text-muted-foreground">{useCases.length}</span>}
|
|
119
79
|
href="/explore"
|
|
120
80
|
/>
|
|
121
|
-
<
|
|
81
|
+
<PanelNavRow
|
|
122
82
|
icon={<Blocks size={14} className="text-muted-foreground" />}
|
|
123
83
|
title={d.pluginMarket}
|
|
124
84
|
badge={<ComingSoonBadge label={d.comingSoon} />}
|
|
125
85
|
/>
|
|
126
|
-
<
|
|
86
|
+
<PanelNavRow
|
|
127
87
|
icon={<Zap size={14} className="text-muted-foreground" />}
|
|
128
88
|
title={d.skillMarket}
|
|
129
89
|
badge={<ComingSoonBadge label={d.comingSoon} />}
|
|
130
90
|
/>
|
|
131
|
-
<
|
|
91
|
+
<PanelNavRow
|
|
132
92
|
icon={<LayoutTemplate size={14} className="text-muted-foreground" />}
|
|
133
93
|
title={d.spaceTemplates}
|
|
134
94
|
badge={<ComingSoonBadge label={d.comingSoon} />}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
4
|
+
import { UserRound, Bookmark, Sun, History, Brain } from 'lucide-react';
|
|
5
|
+
import PanelHeader from './PanelHeader';
|
|
6
|
+
import { ComingSoonBadge } from './PanelNavRow';
|
|
7
|
+
import { useLocale } from '@/lib/LocaleContext';
|
|
8
|
+
|
|
9
|
+
interface EchoPanelProps {
|
|
10
|
+
active: boolean;
|
|
11
|
+
maximized?: boolean;
|
|
12
|
+
onMaximize?: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function EchoPlaceholdSection({
|
|
16
|
+
icon,
|
|
17
|
+
title,
|
|
18
|
+
hint,
|
|
19
|
+
comingSoonLabel,
|
|
20
|
+
}: {
|
|
21
|
+
icon: ReactNode;
|
|
22
|
+
title: string;
|
|
23
|
+
hint: string;
|
|
24
|
+
comingSoonLabel: string;
|
|
25
|
+
}) {
|
|
26
|
+
return (
|
|
27
|
+
<div className="px-4 py-2.5 border-b border-border/60 last:border-b-0">
|
|
28
|
+
<div className="flex items-center gap-2.5">
|
|
29
|
+
<span className="flex items-center justify-center w-7 h-7 rounded-md bg-muted shrink-0 text-muted-foreground">{icon}</span>
|
|
30
|
+
<span className="text-sm font-medium text-foreground flex-1 text-left">{title}</span>
|
|
31
|
+
<ComingSoonBadge label={comingSoonLabel} />
|
|
32
|
+
</div>
|
|
33
|
+
<p className="text-2xs text-muted-foreground leading-relaxed mt-1.5 pl-9">{hint}</p>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default function EchoPanel({ active, maximized, onMaximize }: EchoPanelProps) {
|
|
39
|
+
const { t } = useLocale();
|
|
40
|
+
const e = t.panels.echo;
|
|
41
|
+
const soon = e.comingSoon;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className={`flex flex-col h-full ${active ? '' : 'hidden'}`}>
|
|
45
|
+
<PanelHeader title={e.title} maximized={maximized} onMaximize={onMaximize} />
|
|
46
|
+
<div className="flex-1 overflow-y-auto min-h-0">
|
|
47
|
+
<div className="py-1">
|
|
48
|
+
<EchoPlaceholdSection
|
|
49
|
+
icon={<UserRound size={14} />}
|
|
50
|
+
title={e.aboutYouTitle}
|
|
51
|
+
hint={e.aboutYouHint}
|
|
52
|
+
comingSoonLabel={soon}
|
|
53
|
+
/>
|
|
54
|
+
<EchoPlaceholdSection
|
|
55
|
+
icon={<Bookmark size={14} />}
|
|
56
|
+
title={e.continuedTitle}
|
|
57
|
+
hint={e.continuedHint}
|
|
58
|
+
comingSoonLabel={soon}
|
|
59
|
+
/>
|
|
60
|
+
<EchoPlaceholdSection
|
|
61
|
+
icon={<Sun size={14} />}
|
|
62
|
+
title={e.dailyEchoTitle}
|
|
63
|
+
hint={e.dailyEchoHint}
|
|
64
|
+
comingSoonLabel={soon}
|
|
65
|
+
/>
|
|
66
|
+
<EchoPlaceholdSection
|
|
67
|
+
icon={<History size={14} />}
|
|
68
|
+
title={e.pastYouTitle}
|
|
69
|
+
hint={e.pastYouHint}
|
|
70
|
+
comingSoonLabel={soon}
|
|
71
|
+
/>
|
|
72
|
+
<EchoPlaceholdSection
|
|
73
|
+
icon={<Brain size={14} />}
|
|
74
|
+
title={e.intentGrowthTitle}
|
|
75
|
+
hint={e.intentGrowthHint}
|
|
76
|
+
comingSoonLabel={soon}
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
4
|
+
import Link from 'next/link';
|
|
5
|
+
import { ChevronRight } from 'lucide-react';
|
|
6
|
+
|
|
7
|
+
/** Row matching Discover panel nav: icon tile, title, optional badge, chevron. */
|
|
8
|
+
export function PanelNavRow({
|
|
9
|
+
icon,
|
|
10
|
+
title,
|
|
11
|
+
badge,
|
|
12
|
+
href,
|
|
13
|
+
onClick,
|
|
14
|
+
}: {
|
|
15
|
+
icon: ReactNode;
|
|
16
|
+
title: string;
|
|
17
|
+
badge?: React.ReactNode;
|
|
18
|
+
href?: string;
|
|
19
|
+
onClick?: () => void;
|
|
20
|
+
}) {
|
|
21
|
+
const content = (
|
|
22
|
+
<>
|
|
23
|
+
<span className="flex items-center justify-center w-7 h-7 rounded-md bg-muted shrink-0">{icon}</span>
|
|
24
|
+
<span className="text-sm font-medium text-foreground flex-1 text-left">{title}</span>
|
|
25
|
+
{badge}
|
|
26
|
+
<ChevronRight size={14} className="text-muted-foreground shrink-0" />
|
|
27
|
+
</>
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const className =
|
|
31
|
+
'flex items-center gap-3 px-4 py-2.5 hover:bg-muted/50 transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm';
|
|
32
|
+
|
|
33
|
+
if (href) {
|
|
34
|
+
return (
|
|
35
|
+
<Link href={href} className={className}>
|
|
36
|
+
{content}
|
|
37
|
+
</Link>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
return (
|
|
41
|
+
<button type="button" onClick={onClick} className={`${className} w-full`}>
|
|
42
|
+
{content}
|
|
43
|
+
</button>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function ComingSoonBadge({ label }: { label: string }) {
|
|
48
|
+
return (
|
|
49
|
+
<span className="text-2xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground shrink-0">{label}</span>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type AgentsPanelAgentDetailStatus = 'connected' | 'detected' | 'notFound';
|
|
2
|
+
|
|
3
|
+
export function resolveAgentDetailStatus(
|
|
4
|
+
key: string,
|
|
5
|
+
connected: { key: string }[],
|
|
6
|
+
detected: { key: string }[],
|
|
7
|
+
notFound: { key: string }[],
|
|
8
|
+
): AgentsPanelAgentDetailStatus | null {
|
|
9
|
+
if (connected.some(a => a.key === key)) return 'connected';
|
|
10
|
+
if (detected.some(a => a.key === key)) return 'detected';
|
|
11
|
+
if (notFound.some(a => a.key === key)) return 'notFound';
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
import { useState, useEffect, useCallback, useMemo } from 'react';
|
|
4
4
|
import {
|
|
5
5
|
Loader2, ChevronDown, ChevronRight,
|
|
6
|
-
Plus, X, Search,
|
|
6
|
+
Plus, X, Search, Copy, Check,
|
|
7
7
|
} from 'lucide-react';
|
|
8
8
|
import { apiFetch } from '@/lib/api';
|
|
9
9
|
import { useMcpDataOptional } from '@/hooks/useMcpData';
|
|
10
|
+
import { copyToClipboard } from '@/lib/clipboard';
|
|
10
11
|
import type { SkillInfo, McpSkillsSectionProps } from './types';
|
|
11
12
|
import SkillRow from './McpSkillRow';
|
|
12
13
|
import SkillCreateForm from './McpSkillCreateForm';
|
|
@@ -24,7 +25,7 @@ export default function SkillsSection({ t }: McpSkillsSectionProps) {
|
|
|
24
25
|
const [createError, setCreateError] = useState('');
|
|
25
26
|
|
|
26
27
|
const [search, setSearch] = useState('');
|
|
27
|
-
const [builtinCollapsed, setBuiltinCollapsed] = useState(
|
|
28
|
+
const [builtinCollapsed, setBuiltinCollapsed] = useState(false);
|
|
28
29
|
const [editing, setEditing] = useState<string | null>(null);
|
|
29
30
|
const [editContent, setEditContent] = useState('');
|
|
30
31
|
const [editError, setEditError] = useState('');
|
|
@@ -354,6 +355,91 @@ export default function SkillsSection({ t }: McpSkillsSectionProps) {
|
|
|
354
355
|
{m?.addSkill ?? '+ Add Skill'}
|
|
355
356
|
</button>
|
|
356
357
|
)}
|
|
358
|
+
|
|
359
|
+
{/* CLI install hint with agent selector */}
|
|
360
|
+
<SkillCliHint
|
|
361
|
+
agents={mcp?.agents ?? []}
|
|
362
|
+
skillName={(() => {
|
|
363
|
+
const mindosEnabled = skills.find(s => s.name === 'mindos')?.enabled ?? true;
|
|
364
|
+
return mindosEnabled ? 'mindos' : 'mindos-zh';
|
|
365
|
+
})()}
|
|
366
|
+
m={m}
|
|
367
|
+
/>
|
|
368
|
+
</div>
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/* ── Skill CLI Install Hint ── */
|
|
373
|
+
|
|
374
|
+
function SkillCliHint({ agents, skillName, m }: {
|
|
375
|
+
agents: { key: string; name: string; present?: boolean; installed?: boolean }[];
|
|
376
|
+
skillName: string;
|
|
377
|
+
m: Record<string, any> | undefined;
|
|
378
|
+
}) {
|
|
379
|
+
const [selectedAgent, setSelectedAgent] = useState('claude-code');
|
|
380
|
+
const [copied, setCopied] = useState(false);
|
|
381
|
+
|
|
382
|
+
const cmd = `npx skills add GeminiLight/MindOS --skill ${skillName} -a ${selectedAgent} -g -y`;
|
|
383
|
+
const skillPath = `~/.agents/skills/${skillName}/SKILL.md`;
|
|
384
|
+
|
|
385
|
+
const handleCopy = async () => {
|
|
386
|
+
const ok = await copyToClipboard(cmd);
|
|
387
|
+
if (ok) { setCopied(true); setTimeout(() => setCopied(false), 2000); }
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
// Group agents: connected first, then detected, then not found
|
|
391
|
+
const connected = agents.filter(a => a.present && a.installed);
|
|
392
|
+
const detected = agents.filter(a => a.present && !a.installed);
|
|
393
|
+
const notFound = agents.filter(a => !a.present);
|
|
394
|
+
|
|
395
|
+
return (
|
|
396
|
+
<div className="border-t border-border pt-3 mt-3 space-y-2.5">
|
|
397
|
+
<p className="text-2xs font-medium text-muted-foreground">
|
|
398
|
+
{m?.cliInstallHint ?? 'Install via CLI:'}
|
|
399
|
+
</p>
|
|
400
|
+
|
|
401
|
+
{/* Agent selector */}
|
|
402
|
+
<div className="relative">
|
|
403
|
+
<select
|
|
404
|
+
value={selectedAgent}
|
|
405
|
+
onChange={(e) => setSelectedAgent(e.target.value)}
|
|
406
|
+
className="w-full appearance-none px-2.5 py-1.5 pr-7 text-2xs rounded-md border border-border bg-background text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
407
|
+
>
|
|
408
|
+
{connected.length > 0 && (
|
|
409
|
+
<optgroup label={m?.connectedGroup ?? 'Connected'}>
|
|
410
|
+
{connected.map(a => <option key={a.key} value={a.key}>✓ {a.name}</option>)}
|
|
411
|
+
</optgroup>
|
|
412
|
+
)}
|
|
413
|
+
{detected.length > 0 && (
|
|
414
|
+
<optgroup label={m?.detectedGroup ?? 'Detected'}>
|
|
415
|
+
{detected.map(a => <option key={a.key} value={a.key}>○ {a.name}</option>)}
|
|
416
|
+
</optgroup>
|
|
417
|
+
)}
|
|
418
|
+
{notFound.length > 0 && (
|
|
419
|
+
<optgroup label={m?.notFoundGroup ?? 'Not Installed'}>
|
|
420
|
+
{notFound.map(a => <option key={a.key} value={a.key}>· {a.name}</option>)}
|
|
421
|
+
</optgroup>
|
|
422
|
+
)}
|
|
423
|
+
</select>
|
|
424
|
+
<ChevronDown size={12} className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground pointer-events-none" />
|
|
425
|
+
</div>
|
|
426
|
+
|
|
427
|
+
{/* Command */}
|
|
428
|
+
<div className="flex items-center gap-1.5">
|
|
429
|
+
<code className="flex-1 text-[10px] font-mono bg-muted/50 border border-border rounded-lg px-2.5 py-2 text-muted-foreground select-all overflow-x-auto whitespace-nowrap">
|
|
430
|
+
{cmd}
|
|
431
|
+
</code>
|
|
432
|
+
<button onClick={handleCopy}
|
|
433
|
+
className="p-1.5 rounded-md border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors shrink-0">
|
|
434
|
+
{copied ? <Check size={11} /> : <Copy size={11} />}
|
|
435
|
+
</button>
|
|
436
|
+
</div>
|
|
437
|
+
|
|
438
|
+
{/* Path hint */}
|
|
439
|
+
<p className="text-2xs text-muted-foreground">
|
|
440
|
+
{m?.skillPathHint ?? 'Skill files installed at:'}{' '}
|
|
441
|
+
<code className="font-mono text-[10px] bg-muted px-1 py-0.5 rounded">{skillPath}</code>
|
|
442
|
+
</p>
|
|
357
443
|
</div>
|
|
358
444
|
);
|
|
359
445
|
}
|
|
@@ -219,6 +219,32 @@ function AgentConfigViewer({ connectedAgents, detectedAgents, notFoundAgents, cu
|
|
|
219
219
|
|
|
220
220
|
{currentAgent && (
|
|
221
221
|
<>
|
|
222
|
+
{/* Agent status badge */}
|
|
223
|
+
<div className="flex items-center gap-2">
|
|
224
|
+
{currentAgent.present && currentAgent.installed ? (
|
|
225
|
+
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-2xs font-medium bg-emerald-500/10 text-emerald-600 dark:text-emerald-400">
|
|
226
|
+
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500 inline-block" />
|
|
227
|
+
{m?.tagConnected ?? 'Connected'}
|
|
228
|
+
</span>
|
|
229
|
+
) : currentAgent.present && !currentAgent.installed ? (
|
|
230
|
+
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-2xs font-medium" style={{ background: 'var(--amber-subtle, rgba(200,135,58,0.1))', color: 'var(--amber)' }}>
|
|
231
|
+
<span className="w-1.5 h-1.5 rounded-full inline-block" style={{ background: 'var(--amber)' }} />
|
|
232
|
+
{m?.tagDetected ?? 'Detected — not configured'}
|
|
233
|
+
</span>
|
|
234
|
+
) : (
|
|
235
|
+
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-2xs font-medium bg-muted text-muted-foreground">
|
|
236
|
+
<span className="w-1.5 h-1.5 rounded-full bg-zinc-400 inline-block" />
|
|
237
|
+
{m?.tagNotInstalled ?? 'Not installed'}
|
|
238
|
+
</span>
|
|
239
|
+
)}
|
|
240
|
+
{currentAgent.transport && (
|
|
241
|
+
<span className="px-1.5 py-0.5 rounded text-2xs bg-muted text-muted-foreground">{currentAgent.transport}</span>
|
|
242
|
+
)}
|
|
243
|
+
{currentAgent.scope && (
|
|
244
|
+
<span className="px-1.5 py-0.5 rounded text-2xs bg-muted text-muted-foreground">{currentAgent.scope}</span>
|
|
245
|
+
)}
|
|
246
|
+
</div>
|
|
247
|
+
|
|
222
248
|
{/* Transport toggle */}
|
|
223
249
|
<div className="flex items-center rounded-lg border border-border overflow-hidden w-fit">
|
|
224
250
|
<button
|