@geminilight/mindos 0.6.40 → 0.6.41
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/_standalone/.mindos-build-version +1 -1
- package/_standalone/.next/BUILD_ID +1 -1
- package/_standalone/.next/app-path-routes-manifest.json +18 -18
- package/_standalone/.next/build-manifest.json +3 -3
- package/_standalone/.next/cache/.previewinfo +1 -1
- package/_standalone/.next/cache/.rscinfo +1 -1
- package/_standalone/.next/cache/config.json +3 -3
- package/_standalone/.next/prerender-manifest.json +3 -3
- package/_standalone/.next/react-loadable-manifest.json +5 -1
- package/_standalone/.next/server/app/.well-known/agent-card.json/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/_global-error.html +2 -2
- package/_standalone/.next/server/app/_global-error.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/_standalone/.next/server/app/_not-found/page.js +1 -1
- package/_standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/agents/[agentKey]/page.js +1 -1
- package/_standalone/.next/server/app/agents/[agentKey]/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/agents/[agentKey]/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/agents/page.js +2 -2
- package/_standalone/.next/server/app/agents/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/agents/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/agents/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/delegations/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/discover/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/config/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/detect/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/install/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/registry/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/session/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agent-activity/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/ask/route.js +1 -1
- package/_standalone/.next/server/app/api/ask/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/ask/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/ask-sessions/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/auth/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/backlinks/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/backlinks/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/bootstrap/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/bootstrap/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/changes/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/changes/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/export/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/export/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/extract-pdf/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/file/import/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/file/import/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/file/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/file/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/graph/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/graph/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/inbox/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/inbox/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/init/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/init/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/agents/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/mcp/agents/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/install/route.js +1 -1
- package/_standalone/.next/server/app/api/mcp/install/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/install-skill/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/mcp/install-skill/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/restart/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/status/route.js +1 -1
- package/_standalone/.next/server/app/api/mcp/status/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/monitoring/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/monitoring/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/recent-files/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/recent-files/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/restart/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/search/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/list-models/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/reset-token/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/test-key/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/check-path/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/check-port/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/generate-token/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/ls/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/tree-version/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/tree-version/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/uninstall/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/update-check/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/update-status/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/workflows/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/workflows/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/changes/page.js +1 -1
- package/_standalone/.next/server/app/changes/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/changes/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/echo/[segment]/page.js +1 -1
- package/_standalone/.next/server/app/echo/[segment]/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/echo/[segment]/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/echo/page.js +1 -1
- package/_standalone/.next/server/app/echo/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/echo/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/explore/page.js +1 -1
- package/_standalone/.next/server/app/explore/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/explore/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/help/page.js +2 -2
- package/_standalone/.next/server/app/help/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/help/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/login/page.js +1 -1
- package/_standalone/.next/server/app/login/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/page.js +2 -2
- package/_standalone/.next/server/app/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/setup/page.js +2 -2
- package/_standalone/.next/server/app/setup/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/trash/page.js +3 -3
- package/_standalone/.next/server/app/trash/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/trash/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/view/[...path]/page.js +3 -3
- package/_standalone/.next/server/app/view/[...path]/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/view/[...path]/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app-paths-manifest.json +18 -18
- package/_standalone/.next/server/chunks/1550.js +1 -1
- package/_standalone/.next/server/chunks/2190.js +11 -0
- package/_standalone/.next/server/chunks/{6365.js → 2536.js} +2 -2
- package/_standalone/.next/server/chunks/5648.js +2 -0
- package/_standalone/.next/server/chunks/8388.js +1 -1
- package/_standalone/.next/server/chunks/953.js +1 -1
- package/_standalone/.next/server/chunks/9539.js +219 -0
- package/_standalone/.next/server/middleware-build-manifest.js +1 -1
- package/_standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/_standalone/.next/server/next-font-manifest.js +1 -1
- package/_standalone/.next/server/next-font-manifest.json +1 -1
- package/_standalone/.next/server/pages/500.html +2 -2
- package/_standalone/.next/server/server-reference-manifest.js +1 -1
- package/_standalone/.next/server/server-reference-manifest.json +1 -1
- package/_standalone/.next/static/chunks/1053-b70535785cc5aaee.js +29 -0
- package/_standalone/.next/static/chunks/{8663-de911d2d395622be.js → 1880-c2a9e76201841c86.js} +1 -1
- package/_standalone/.next/static/chunks/3637.0541ac2d0ea7de1f.js +1 -0
- package/_standalone/.next/static/chunks/4563-b2a2ce80aff845af.js +6 -0
- package/_standalone/.next/static/chunks/6981-3d7dcac2d12a5670.js +1 -0
- package/_standalone/.next/static/chunks/7144-5febf62f1a79fe64.js +1 -0
- package/_standalone/.next/static/chunks/app/agents/[agentKey]/page-773071a99c4daac2.js +1 -0
- package/_standalone/.next/static/chunks/app/agents/page-6102a884b2cb3cfe.js +5 -0
- package/_standalone/.next/static/chunks/app/help/page-2325d25b6846ca07.js +1 -0
- package/_standalone/.next/static/chunks/app/{layout-9378c1c8d3e5761b.js → layout-42cdbce19f404567.js} +34 -34
- package/_standalone/.next/static/chunks/app/{page-9bae420fbbdc5fff.js → page-8c9643b649e01735.js} +1 -1
- package/_standalone/.next/static/chunks/app/setup/page-d158b8cb533feb1e.js +1 -0
- package/_standalone/.next/static/chunks/app/trash/{page-b61ef2d5cd4f8d73.js → page-e9ab74ffeb96af41.js} +1 -1
- package/_standalone/.next/static/chunks/app/view/[...path]/page-764a69a1c8bd4eef.js +12 -0
- package/_standalone/.next/static/chunks/{webpack-c28c55d0a6021a6b.js → webpack-7b276daaa930d480.js} +1 -1
- package/_standalone/.next/static/css/bc9179074eaf65ae.css +1 -0
- package/_standalone/.next/trace +63 -63
- package/_standalone/__tests__/api/mcp-install.test.ts +23 -0
- package/_standalone/__tests__/cli/agent-routing.test.ts +232 -0
- package/_standalone/__tests__/cli/file-subcommands.test.ts +379 -0
- package/_standalone/__tests__/core/tools.test.ts +3 -6
- package/_standalone/components/FileTree.tsx +3 -2
- package/_standalone/components/MarkdownView.tsx +30 -15
- package/_standalone/components/RightAskPanel.tsx +36 -6
- package/_standalone/components/Sidebar.tsx +3 -3
- package/_standalone/components/agents/AgentsMcpSection.tsx +3 -0
- package/_standalone/components/settings/McpAgentInstall.tsx +94 -27
- package/_standalone/components/settings/McpSkillsSection.tsx +1 -1
- package/_standalone/components/settings/McpTab.tsx +484 -340
- package/_standalone/components/settings/SettingsContent.tsx +12 -6
- package/_standalone/components/settings/types.ts +3 -0
- package/_standalone/components/setup/StepAgents.tsx +113 -47
- package/_standalone/components/setup/StepReview.tsx +14 -27
- package/_standalone/components/setup/types.ts +6 -0
- package/_standalone/data/skills/mindos/SKILL.md +92 -92
- package/_standalone/data/skills/mindos/references/write-supplement.md +119 -0
- package/_standalone/data/skills/mindos-zh/SKILL.md +100 -104
- package/_standalone/data/skills/mindos-zh/references/write-supplement.md +119 -0
- package/_standalone/lib/i18n/modules/features.ts +4 -4
- package/_standalone/lib/i18n/modules/knowledge.ts +4 -0
- package/_standalone/lib/i18n/modules/onboarding.ts +40 -30
- package/_standalone/lib/i18n/modules/settings.ts +78 -6
- package/_standalone/lib/mcp-snippets.ts +5 -1
- package/_standalone/tsconfig.tsbuildinfo +1 -1
- package/app/app/api/ask/route.ts +3 -2
- package/app/app/api/mcp/install/route.ts +2 -1
- package/app/app/api/mcp/status/route.ts +14 -6
- package/app/app/view/[...path]/ViewPageClient.tsx +12 -27
- package/app/components/FileTree.tsx +3 -2
- package/app/components/MarkdownView.tsx +30 -15
- package/app/components/RightAskPanel.tsx +36 -6
- package/app/components/Sidebar.tsx +3 -3
- package/app/components/agents/AgentsMcpSection.tsx +3 -0
- package/app/components/help/HelpContent.tsx +1 -0
- package/app/components/settings/McpAgentInstall.tsx +94 -27
- package/app/components/settings/McpSkillsSection.tsx +1 -1
- package/app/components/settings/McpTab.tsx +484 -340
- package/app/components/settings/SettingsContent.tsx +12 -6
- package/app/components/settings/types.ts +3 -0
- package/app/components/setup/StepAgents.tsx +113 -47
- package/app/components/setup/StepReview.tsx +14 -27
- package/app/components/setup/index.tsx +12 -11
- package/app/components/setup/types.ts +6 -0
- package/app/data/skills/mindos/SKILL.md +92 -92
- package/app/data/skills/mindos/references/write-supplement.md +119 -0
- package/app/data/skills/mindos-zh/SKILL.md +100 -104
- package/app/data/skills/mindos-zh/references/write-supplement.md +119 -0
- package/app/lib/fs.ts +0 -6
- package/app/lib/i18n/modules/features.ts +4 -4
- package/app/lib/i18n/modules/knowledge.ts +4 -0
- package/app/lib/i18n/modules/onboarding.ts +40 -30
- package/app/lib/i18n/modules/settings.ts +78 -6
- package/app/lib/mcp-agents.ts +1 -2
- package/app/lib/mcp-snippets.ts +5 -1
- package/app/lib/renderers/index.ts +2 -1
- package/bin/cli.js +168 -1404
- package/bin/commands/agent.js +156 -20
- package/bin/commands/api.js +14 -11
- package/bin/commands/ask.js +79 -68
- package/bin/commands/build.js +26 -0
- package/bin/commands/config.js +170 -0
- package/bin/commands/dev.js +58 -0
- package/bin/commands/doctor.js +205 -0
- package/bin/commands/file.js +551 -36
- package/bin/commands/gateway.js +42 -0
- package/bin/commands/init-skills.js +56 -0
- package/bin/commands/logs.js +32 -0
- package/bin/commands/mcp-cmd.js +57 -0
- package/bin/commands/onboard.js +25 -0
- package/bin/commands/open.js +41 -0
- package/bin/commands/restart.js +48 -0
- package/bin/commands/search.js +16 -14
- package/bin/commands/space.js +96 -25
- package/bin/commands/start.js +262 -0
- package/bin/commands/status.js +2 -2
- package/bin/commands/stop.js +14 -0
- package/bin/commands/sync-cmd.js +134 -0
- package/bin/commands/token.js +98 -0
- package/bin/commands/uninstall.js +154 -0
- package/bin/commands/update.js +286 -0
- package/bin/lib/build.js +1 -1
- package/bin/lib/colors.js +8 -7
- package/bin/lib/command.js +37 -96
- package/bin/lib/config.js +5 -0
- package/bin/lib/csv.js +19 -0
- package/bin/lib/jsonc.js +12 -0
- package/bin/lib/markdown.js +69 -0
- package/bin/lib/mcp-agents.js +1 -6
- package/bin/lib/mcp-build.js +1 -1
- package/bin/lib/mcp-install.js +2 -1
- package/bin/lib/one-shot.js +88 -0
- package/bin/lib/path-expand.js +9 -0
- package/bin/lib/remote.js +65 -0
- package/bin/lib/repl.js +167 -0
- package/bin/lib/{utils.js → shell.js} +10 -26
- package/bin/lib/skill-check.js +1 -1
- package/bin/lib/sse-stream.js +167 -0
- package/package.json +2 -2
- package/scripts/setup.js +182 -120
- package/skills/mindos/SKILL.md +92 -92
- package/skills/mindos-zh/SKILL.md +100 -104
- package/_standalone/.next/server/chunks/1955.js +0 -11
- package/_standalone/.next/server/chunks/3680.js +0 -1
- package/_standalone/.next/server/chunks/4497.js +0 -219
- package/_standalone/.next/server/chunks/5560.js +0 -2
- package/_standalone/.next/static/chunks/1053-0adaccc98a752a58.js +0 -29
- package/_standalone/.next/static/chunks/3637.f9a42cca59fd5bb5.js +0 -1
- package/_standalone/.next/static/chunks/4563-c2afaeacb241d1d0.js +0 -6
- package/_standalone/.next/static/chunks/6090-c98268ca726a68d3.js +0 -1
- package/_standalone/.next/static/chunks/9371-575600301da5d6bb.js +0 -1
- package/_standalone/.next/static/chunks/app/agents/[agentKey]/page-3e08abb495ecd5fd.js +0 -1
- package/_standalone/.next/static/chunks/app/agents/page-e7e0f87ad3d765ac.js +0 -5
- package/_standalone/.next/static/chunks/app/help/page-3d0e1ceaa4abc243.js +0 -1
- package/_standalone/.next/static/chunks/app/setup/page-99ed3d1bb6b8f4ef.js +0 -1
- package/_standalone/.next/static/chunks/app/view/[...path]/page-44fa78cbea613a78.js +0 -12
- package/_standalone/.next/static/css/d300701f384db50d.css +0 -1
- package/_standalone/components/renderers/agent-inspector/manifest.ts +0 -16
- /package/_standalone/.next/static/{rZLs1krFuduixvcVNe6q3 → Ij3PFh-a0zi5K_ANoSAW0}/_buildManifest.js +0 -0
- /package/_standalone/.next/static/{rZLs1krFuduixvcVNe6q3 → Ij3PFh-a0zi5K_ANoSAW0}/_ssgManifest.js +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useMemo, useRef, useEffect, useCallback } from 'react';
|
|
2
|
-
import { Loader2, Copy, Check, Monitor, Globe, AlertCircle, RotateCcw, RefreshCw, Eye, EyeOff, ChevronDown, ChevronRight, Link2, Shield } from 'lucide-react';
|
|
2
|
+
import { Loader2, Copy, Check, Monitor, Globe, AlertCircle, RotateCcw, RefreshCw, Eye, EyeOff, ChevronDown, ChevronRight, Link2, Shield, Terminal, Plug, CheckCircle2, Sparkles, Users } from 'lucide-react';
|
|
3
3
|
import { toast } from '@/lib/toast';
|
|
4
4
|
import { useMcpDataOptional } from '@/lib/stores/mcp-store';
|
|
5
5
|
import { generateSnippet } from '@/lib/mcp-snippets';
|
|
@@ -11,18 +11,17 @@ import type { McpTabProps, McpStatus, AgentInfo } from './types';
|
|
|
11
11
|
import AgentInstall from './McpAgentInstall';
|
|
12
12
|
import SkillsSection from './McpSkillsSection';
|
|
13
13
|
|
|
14
|
-
/* ── Main
|
|
14
|
+
/* ── Main Connections Tab ────────────────────────────────────────── */
|
|
15
15
|
|
|
16
16
|
export function McpTab({ t }: McpTabProps) {
|
|
17
17
|
const mcp = useMcpDataOptional();
|
|
18
18
|
const m = t.settings?.mcp;
|
|
19
19
|
|
|
20
|
+
const [mode, setMode] = useState<'cli' | 'mcp'>('cli');
|
|
20
21
|
const [restarting, setRestarting] = useState(false);
|
|
21
22
|
const [selectedAgent, setSelectedAgent] = useState('');
|
|
22
|
-
const [transport, setTransport] = useState<'stdio' | 'http'>('stdio');
|
|
23
23
|
const restartPollRef = useRef<ReturnType<typeof setInterval>>(undefined);
|
|
24
24
|
|
|
25
|
-
// Cleanup restart poll on unmount
|
|
26
25
|
useEffect(() => () => clearInterval(restartPollRef.current), []);
|
|
27
26
|
|
|
28
27
|
if (!mcp || mcp.loading) {
|
|
@@ -36,408 +35,553 @@ export function McpTab({ t }: McpTabProps) {
|
|
|
36
35
|
const connectedAgents = mcp.agents.filter(a => a.present && a.installed);
|
|
37
36
|
const detectedAgents = mcp.agents.filter(a => a.present && !a.installed);
|
|
38
37
|
const notFoundAgents = mcp.agents.filter(a => !a.present);
|
|
39
|
-
|
|
40
|
-
// Auto-select first agent if none selected
|
|
41
38
|
const effectiveSelected = selectedAgent || (mcp.agents[0]?.key ?? '');
|
|
42
39
|
const currentAgent = mcp.agents.find(a => a.key === effectiveSelected);
|
|
43
40
|
|
|
41
|
+
// Determine active skill name based on which mindos skill is enabled
|
|
42
|
+
const mindosEnabled = mcp.skills?.find(s => s.name === 'mindos')?.enabled ?? true;
|
|
43
|
+
const activeSkillName = mindosEnabled ? 'mindos' : 'mindos-zh';
|
|
44
|
+
|
|
45
|
+
const handleRestart = async () => {
|
|
46
|
+
setRestarting(true);
|
|
47
|
+
try {
|
|
48
|
+
await apiFetch('/api/mcp/restart', { method: 'POST' });
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.error('[McpTab] Restart request failed:', err);
|
|
51
|
+
setRestarting(false);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const deadline = Date.now() + 60_000;
|
|
55
|
+
clearInterval(restartPollRef.current);
|
|
56
|
+
restartPollRef.current = setInterval(async () => {
|
|
57
|
+
if (Date.now() > deadline) {
|
|
58
|
+
clearInterval(restartPollRef.current);
|
|
59
|
+
setRestarting(false);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const s = await apiFetch<McpStatus>('/api/mcp/status', { timeout: 3000 });
|
|
64
|
+
if (s.running) {
|
|
65
|
+
clearInterval(restartPollRef.current);
|
|
66
|
+
setRestarting(false);
|
|
67
|
+
mcp.refresh();
|
|
68
|
+
}
|
|
69
|
+
} catch { /* continue polling */ }
|
|
70
|
+
}, 3000);
|
|
71
|
+
};
|
|
72
|
+
|
|
44
73
|
return (
|
|
45
74
|
<div className="space-y-6">
|
|
46
|
-
{/*
|
|
47
|
-
<
|
|
75
|
+
{/* 1. Auth Token */}
|
|
76
|
+
<AuthTokenCard status={mcp.status} m={m} />
|
|
77
|
+
|
|
78
|
+
{/* 2. Connect Agents (CLI/MCP guides + detected agents) */}
|
|
79
|
+
<ConnectCard
|
|
80
|
+
mode={mode}
|
|
81
|
+
onModeChange={setMode}
|
|
48
82
|
status={mcp.status}
|
|
83
|
+
agents={mcp.agents}
|
|
84
|
+
connectedAgents={connectedAgents}
|
|
85
|
+
detectedAgents={detectedAgents}
|
|
86
|
+
notFoundAgents={notFoundAgents}
|
|
87
|
+
currentAgent={currentAgent ?? null}
|
|
88
|
+
selectedAgent={effectiveSelected}
|
|
89
|
+
onSelectAgent={setSelectedAgent}
|
|
49
90
|
restarting={restarting}
|
|
50
|
-
onRestart={
|
|
51
|
-
setRestarting(true);
|
|
52
|
-
try {
|
|
53
|
-
await apiFetch('/api/mcp/restart', { method: 'POST' });
|
|
54
|
-
} catch (err) {
|
|
55
|
-
console.error('[McpTab] Restart request failed:', err);
|
|
56
|
-
setRestarting(false);
|
|
57
|
-
return; // Exit early, don't start polling if restart request fails
|
|
58
|
-
}
|
|
59
|
-
const deadline = Date.now() + 60_000;
|
|
60
|
-
clearInterval(restartPollRef.current);
|
|
61
|
-
restartPollRef.current = setInterval(async () => {
|
|
62
|
-
if (Date.now() > deadline) {
|
|
63
|
-
clearInterval(restartPollRef.current);
|
|
64
|
-
setRestarting(false);
|
|
65
|
-
console.warn('[McpTab] MCP restart timed out after 60s');
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
try {
|
|
69
|
-
const s = await apiFetch<McpStatus>('/api/mcp/status', { timeout: 3000 });
|
|
70
|
-
if (s.running) {
|
|
71
|
-
clearInterval(restartPollRef.current);
|
|
72
|
-
setRestarting(false);
|
|
73
|
-
mcp.refresh();
|
|
74
|
-
}
|
|
75
|
-
} catch (err) {
|
|
76
|
-
console.warn('[McpTab] Status poll attempt failed:', err);
|
|
77
|
-
// Continue polling on individual failures
|
|
78
|
-
}
|
|
79
|
-
}, 3000);
|
|
80
|
-
}}
|
|
91
|
+
onRestart={handleRestart}
|
|
81
92
|
onRefresh={mcp.refresh}
|
|
93
|
+
activeSkillName={activeSkillName}
|
|
82
94
|
m={m}
|
|
95
|
+
t={t}
|
|
83
96
|
/>
|
|
84
97
|
|
|
85
|
-
{/*
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
selectedAgent={effectiveSelected}
|
|
99
|
-
onSelectAgent={(key) => setSelectedAgent(key)}
|
|
100
|
-
transport={transport}
|
|
101
|
-
onTransportChange={setTransport}
|
|
102
|
-
onCopy={async (snippet) => {
|
|
103
|
-
const ok = await copyToClipboard(snippet);
|
|
104
|
-
if (ok) toast.copy();
|
|
105
|
-
}}
|
|
106
|
-
m={m}
|
|
107
|
-
/>
|
|
98
|
+
{/* 3. Skills */}
|
|
99
|
+
<div className="rounded-xl border border-border bg-card overflow-hidden">
|
|
100
|
+
<div className="flex items-center gap-2.5 px-4 pt-4 pb-3">
|
|
101
|
+
<div className="w-7 h-7 rounded-lg bg-muted flex items-center justify-center shrink-0">
|
|
102
|
+
<Sparkles size={14} className="text-muted-foreground" />
|
|
103
|
+
</div>
|
|
104
|
+
<div className="flex-1 min-w-0">
|
|
105
|
+
<h3 className="text-sm font-semibold text-foreground">{m?.skillsTitle ?? 'Skills'}</h3>
|
|
106
|
+
<p className="text-2xs text-muted-foreground">{m?.skillsDesc ?? 'Teach agents how to operate your knowledge base.'}</p>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
<div className="px-4 pb-4">
|
|
110
|
+
<SkillsSection t={t} />
|
|
108
111
|
</div>
|
|
109
|
-
)}
|
|
110
|
-
|
|
111
|
-
{/* Skills */}
|
|
112
|
-
<div>
|
|
113
|
-
<p className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-3">{m?.skillsTitle ?? 'Skills'}</p>
|
|
114
|
-
<SkillsSection t={t} />
|
|
115
|
-
</div>
|
|
116
|
-
|
|
117
|
-
{/* Batch Agent Install */}
|
|
118
|
-
<div>
|
|
119
|
-
<p className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-3">{m?.agentsTitle ?? 'Agent Configuration'}</p>
|
|
120
|
-
<AgentInstall agents={mcp.agents} t={t} onRefresh={mcp.refresh} />
|
|
121
112
|
</div>
|
|
122
113
|
</div>
|
|
123
114
|
);
|
|
124
115
|
}
|
|
125
116
|
|
|
126
|
-
/* ──
|
|
117
|
+
/* ── Auth Token Card ── */
|
|
127
118
|
|
|
128
|
-
function
|
|
119
|
+
function AuthTokenCard({ status, m }: {
|
|
129
120
|
status: McpStatus | null;
|
|
130
|
-
restarting: boolean;
|
|
131
|
-
onRestart: () => void;
|
|
132
|
-
onRefresh: () => void;
|
|
133
121
|
m: Record<string, any> | undefined;
|
|
134
122
|
}) {
|
|
123
|
+
const [copiedField, setCopiedField] = useState<string | null>(null);
|
|
124
|
+
const [revealed, setRevealed] = useState(false);
|
|
125
|
+
|
|
126
|
+
useEffect(() => () => setRevealed(false), []);
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (!copiedField) return;
|
|
129
|
+
const t = setTimeout(() => setCopiedField(null), 2000);
|
|
130
|
+
return () => clearTimeout(t);
|
|
131
|
+
}, [copiedField]);
|
|
132
|
+
|
|
133
|
+
const handleCopy = useCallback(async (text: string, field: string) => {
|
|
134
|
+
if (!text) return;
|
|
135
|
+
const ok = await copyToClipboard(text);
|
|
136
|
+
if (ok) { setCopiedField(field); toast.copy(); }
|
|
137
|
+
}, []);
|
|
138
|
+
|
|
135
139
|
if (!status) return null;
|
|
140
|
+
|
|
141
|
+
const hasToken = status.authConfigured && !!status.authToken;
|
|
142
|
+
const displayToken = revealed ? (status.authToken ?? '') : (status.maskedToken ?? '');
|
|
143
|
+
|
|
136
144
|
return (
|
|
137
|
-
<div className="rounded-xl border border-border bg-card
|
|
138
|
-
<div className="flex items-center gap-2.5
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
<span className={`inline-block w-1.5 h-1.5 rounded-full shrink-0 ${status.running ? 'bg-success' : 'bg-muted-foreground'}`} />
|
|
147
|
-
<span className="text-foreground font-medium">
|
|
148
|
-
{status.running ? (m?.running ?? 'Running') : (m?.stopped ?? 'Stopped')}
|
|
149
|
-
</span>
|
|
150
|
-
{status.running && (
|
|
151
|
-
<>
|
|
152
|
-
<span className="text-muted-foreground">·</span>
|
|
153
|
-
<span className="font-mono text-muted-foreground">{status.endpoint}</span>
|
|
154
|
-
<span className="text-muted-foreground">·</span>
|
|
155
|
-
<span className="text-muted-foreground">{status.toolCount} {m?.tools ?? 'tools'}</span>
|
|
156
|
-
</>
|
|
157
|
-
)}
|
|
158
|
-
</>
|
|
159
|
-
)}
|
|
145
|
+
<div className="rounded-xl border border-border bg-card overflow-hidden">
|
|
146
|
+
<div className="flex items-center gap-2.5 px-4 pt-4 pb-3">
|
|
147
|
+
<div className="w-7 h-7 rounded-lg bg-muted flex items-center justify-center shrink-0">
|
|
148
|
+
<Shield size={14} className="text-muted-foreground" />
|
|
149
|
+
</div>
|
|
150
|
+
<div className="flex-1 min-w-0">
|
|
151
|
+
<h3 className="text-sm font-semibold text-foreground">{m?.tokenCardTitle ?? 'Auth Token'}</h3>
|
|
152
|
+
<p className="text-2xs text-muted-foreground">{m?.tokenCardDesc ?? 'Used by CLI remote mode and MCP connections.'}</p>
|
|
153
|
+
</div>
|
|
160
154
|
</div>
|
|
161
|
-
<div className="
|
|
162
|
-
{
|
|
163
|
-
<
|
|
164
|
-
className="flex items-center gap-
|
|
165
|
-
|
|
166
|
-
|
|
155
|
+
<div className="px-4 pb-4">
|
|
156
|
+
{hasToken ? (
|
|
157
|
+
<div className="flex items-center gap-2">
|
|
158
|
+
<div className="flex-1 flex items-center gap-2 px-2.5 py-1.5 bg-muted/50 border border-border rounded-lg min-h-[34px]">
|
|
159
|
+
<code className="flex-1 text-xs font-mono text-foreground break-all select-all leading-relaxed">{displayToken}</code>
|
|
160
|
+
</div>
|
|
161
|
+
<button type="button" onClick={() => setRevealed(v => !v)}
|
|
162
|
+
className="shrink-0 p-1.5 rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors focus-visible:ring-2 focus-visible:ring-ring"
|
|
163
|
+
title={revealed ? (m?.tokenHide ?? 'Hide') : (m?.tokenShow ?? 'Show')}>
|
|
164
|
+
{revealed ? <EyeOff size={13} /> : <Eye size={13} />}
|
|
165
|
+
</button>
|
|
166
|
+
<CopyButton onCopy={() => handleCopy(status.authToken ?? '', 'token-card')} copied={copiedField === 'token-card'} title={m?.tokenCopy ?? 'Copy'} size="sm" />
|
|
167
|
+
</div>
|
|
168
|
+
) : (
|
|
169
|
+
<div className="px-2.5 py-2 bg-[var(--amber-subtle)] border border-[var(--amber)]/20 rounded-lg">
|
|
170
|
+
<p className="text-xs text-[var(--amber-text)]">{m?.tokenNone ?? 'No token set.'}</p>
|
|
171
|
+
<p className="text-2xs text-muted-foreground mt-0.5">{m?.tokenNoneAction ?? 'Generate one in Settings → General → Security.'}</p>
|
|
172
|
+
</div>
|
|
167
173
|
)}
|
|
168
|
-
<button onClick={onRefresh}
|
|
169
|
-
className="p-1.5 rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors">
|
|
170
|
-
<RefreshCw size={14} />
|
|
171
|
-
</button>
|
|
172
174
|
</div>
|
|
173
175
|
</div>
|
|
174
176
|
);
|
|
175
177
|
}
|
|
176
178
|
|
|
177
|
-
/* ──
|
|
179
|
+
/* ── Shared copy-state hook for guide sub-components ── */
|
|
180
|
+
|
|
181
|
+
function useCopyField() {
|
|
182
|
+
const [copiedField, setCopiedField] = useState<string | null>(null);
|
|
183
|
+
useEffect(() => {
|
|
184
|
+
if (!copiedField) return;
|
|
185
|
+
const timer = setTimeout(() => setCopiedField(null), 2000);
|
|
186
|
+
return () => clearTimeout(timer);
|
|
187
|
+
}, [copiedField]);
|
|
188
|
+
const handleCopy = useCallback(async (text: string, field: string) => {
|
|
189
|
+
if (!text) return;
|
|
190
|
+
const ok = await copyToClipboard(text);
|
|
191
|
+
if (ok) { setCopiedField(field); toast.copy(); }
|
|
192
|
+
}, []);
|
|
193
|
+
return { copiedField, handleCopy };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* ── Connect Card — header + CLI/MCP tabs + detected agents ── */
|
|
178
197
|
|
|
179
|
-
function
|
|
198
|
+
function ConnectCard({ mode, onModeChange, status, agents, connectedAgents, detectedAgents, notFoundAgents, currentAgent, selectedAgent, onSelectAgent, restarting, onRestart, onRefresh, activeSkillName, m, t }: {
|
|
199
|
+
mode: 'cli' | 'mcp';
|
|
200
|
+
onModeChange: (m: 'cli' | 'mcp') => void;
|
|
201
|
+
status: McpStatus | null;
|
|
202
|
+
agents: AgentInfo[];
|
|
180
203
|
connectedAgents: AgentInfo[];
|
|
181
204
|
detectedAgents: AgentInfo[];
|
|
182
205
|
notFoundAgents: AgentInfo[];
|
|
183
206
|
currentAgent: AgentInfo | null;
|
|
184
|
-
mcpStatus: McpStatus | null;
|
|
185
207
|
selectedAgent: string;
|
|
186
208
|
onSelectAgent: (key: string) => void;
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
209
|
+
restarting: boolean;
|
|
210
|
+
onRestart: () => void;
|
|
211
|
+
onRefresh: () => void;
|
|
212
|
+
activeSkillName: string;
|
|
190
213
|
m: Record<string, any> | undefined;
|
|
214
|
+
t: McpTabProps['t'];
|
|
191
215
|
}) {
|
|
192
|
-
|
|
193
|
-
() => currentAgent ? generateSnippet(currentAgent, mcpStatus, transport) : null,
|
|
194
|
-
[currentAgent, mcpStatus, transport]
|
|
195
|
-
);
|
|
216
|
+
if (!status) return null;
|
|
196
217
|
|
|
197
218
|
return (
|
|
198
|
-
<div className="rounded-xl border border-border bg-card
|
|
199
|
-
{/*
|
|
200
|
-
<
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
options: connectedAgents.map(a => ({
|
|
207
|
-
value: a.key,
|
|
208
|
-
label: `${a.name} — ${a.transport ?? 'stdio'} · ${a.scope ?? 'global'}`,
|
|
209
|
-
})),
|
|
210
|
-
}] : []),
|
|
211
|
-
...(detectedAgents.length > 0 ? [{
|
|
212
|
-
label: m?.detectedGroup ?? 'Detected (not configured)',
|
|
213
|
-
options: detectedAgents.map(a => ({
|
|
214
|
-
value: a.key,
|
|
215
|
-
label: `${a.name} — ${m?.notConfigured ?? 'not configured'}`,
|
|
216
|
-
})),
|
|
217
|
-
}] : []),
|
|
218
|
-
...(notFoundAgents.length > 0 ? [{
|
|
219
|
-
label: m?.notFoundGroup ?? 'Not Installed',
|
|
220
|
-
options: notFoundAgents.map(a => ({
|
|
221
|
-
value: a.key,
|
|
222
|
-
label: a.name,
|
|
223
|
-
})),
|
|
224
|
-
}] : []),
|
|
225
|
-
] as SelectItem[]}
|
|
226
|
-
/>
|
|
227
|
-
|
|
228
|
-
{currentAgent && (
|
|
229
|
-
<>
|
|
230
|
-
{/* Agent status badge */}
|
|
231
|
-
<div className="flex items-center gap-2">
|
|
232
|
-
{currentAgent.present && currentAgent.installed ? (
|
|
233
|
-
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-2xs font-medium bg-success/10 text-success">
|
|
234
|
-
<span className="w-1.5 h-1.5 rounded-full bg-success inline-block" />
|
|
235
|
-
{m?.tagConnected ?? 'Connected'}
|
|
236
|
-
</span>
|
|
237
|
-
) : currentAgent.present && !currentAgent.installed ? (
|
|
238
|
-
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-2xs font-medium bg-[var(--amber-subtle)] text-[var(--amber-text)]">
|
|
239
|
-
<span className="w-1.5 h-1.5 rounded-full bg-[var(--amber)] inline-block" />
|
|
240
|
-
{m?.tagDetected ?? 'Detected — not configured'}
|
|
241
|
-
</span>
|
|
242
|
-
) : (
|
|
243
|
-
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-2xs font-medium bg-muted text-muted-foreground">
|
|
244
|
-
<span className="w-1.5 h-1.5 rounded-full bg-muted-foreground inline-block" />
|
|
245
|
-
{m?.tagNotInstalled ?? 'Not installed'}
|
|
246
|
-
</span>
|
|
247
|
-
)}
|
|
248
|
-
{currentAgent.transport && (
|
|
249
|
-
<span className="px-1.5 py-0.5 rounded text-2xs bg-muted text-muted-foreground">{currentAgent.transport}</span>
|
|
250
|
-
)}
|
|
251
|
-
{currentAgent.scope && (
|
|
252
|
-
<span className="px-1.5 py-0.5 rounded text-2xs bg-muted text-muted-foreground">{currentAgent.scope}</span>
|
|
253
|
-
)}
|
|
254
|
-
</div>
|
|
219
|
+
<div className="rounded-xl border border-border bg-card overflow-hidden">
|
|
220
|
+
{/* Header */}
|
|
221
|
+
<div className="flex items-center gap-2.5 px-4 pt-4 pb-3">
|
|
222
|
+
<div className="w-7 h-7 rounded-lg bg-[var(--amber-subtle)] flex items-center justify-center shrink-0">
|
|
223
|
+
<Link2 size={14} className="text-[var(--amber)]" />
|
|
224
|
+
</div>
|
|
225
|
+
<h3 className="text-sm font-semibold text-foreground">{m?.connectionTitle ?? 'Connect Agents'}</h3>
|
|
226
|
+
</div>
|
|
255
227
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
<
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
228
|
+
{/* Tab switcher */}
|
|
229
|
+
<div className="grid grid-cols-2 mx-4 mb-3 rounded-lg border border-border overflow-hidden">
|
|
230
|
+
<button
|
|
231
|
+
onClick={() => onModeChange('cli')}
|
|
232
|
+
className={`flex flex-col items-start px-3 py-2.5 text-left transition-colors ${
|
|
233
|
+
mode === 'cli' ? 'bg-muted' : 'hover:bg-muted/50'
|
|
234
|
+
}`}
|
|
235
|
+
>
|
|
236
|
+
<span className="flex items-center gap-1.5">
|
|
237
|
+
<Terminal size={12} className={mode === 'cli' ? 'text-[var(--amber)]' : 'text-muted-foreground'} />
|
|
238
|
+
<span className={`text-xs font-semibold ${mode === 'cli' ? 'text-foreground' : 'text-muted-foreground'}`}>CLI</span>
|
|
239
|
+
<span className="text-2xs px-1 py-0.5 rounded bg-[var(--amber-subtle)] text-[var(--amber-text)] font-medium leading-none">{m?.recommended ?? 'Recommended'}</span>
|
|
240
|
+
</span>
|
|
241
|
+
<span className="text-2xs text-muted-foreground mt-0.5">{m?.cliAgents ?? 'Claude Code · Gemini CLI · Codex'}</span>
|
|
242
|
+
</button>
|
|
243
|
+
<button
|
|
244
|
+
onClick={() => onModeChange('mcp')}
|
|
245
|
+
className={`flex flex-col items-start px-3 py-2.5 text-left transition-colors border-l border-border ${
|
|
246
|
+
mode === 'mcp' ? 'bg-muted' : 'hover:bg-muted/50'
|
|
247
|
+
}`}
|
|
248
|
+
>
|
|
249
|
+
<span className="flex items-center gap-1.5">
|
|
250
|
+
<Plug size={12} className={mode === 'mcp' ? 'text-foreground' : 'text-muted-foreground'} />
|
|
251
|
+
<span className={`text-xs font-semibold ${mode === 'mcp' ? 'text-foreground' : 'text-muted-foreground'}`}>MCP</span>
|
|
252
|
+
</span>
|
|
253
|
+
<span className="text-2xs text-muted-foreground mt-0.5">{m?.mcpAgents ?? 'Claude Desktop · Cursor'}</span>
|
|
254
|
+
</button>
|
|
255
|
+
</div>
|
|
275
256
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
</pre>
|
|
290
|
-
<div className="flex items-center gap-3 text-sm">
|
|
291
|
-
<button onClick={() => onCopy(snippet.snippet)}
|
|
292
|
-
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors shrink-0">
|
|
293
|
-
<Copy size={14} />
|
|
294
|
-
{m?.copyConfig ?? 'Copy config'}
|
|
295
|
-
</button>
|
|
296
|
-
<span className="text-muted-foreground">→</span>
|
|
297
|
-
<span className="font-mono text-muted-foreground truncate text-2xs">{snippet.path}</span>
|
|
298
|
-
</div>
|
|
299
|
-
</>
|
|
300
|
-
)}
|
|
301
|
-
</>
|
|
302
|
-
)}
|
|
257
|
+
{/* Tab content */}
|
|
258
|
+
<div className="px-4 pb-4 space-y-4">
|
|
259
|
+
{mode === 'cli' ? (
|
|
260
|
+
<CliGuide status={status} activeSkillName={activeSkillName} agents={agents} connectedAgents={connectedAgents} detectedAgents={detectedAgents} notFoundAgents={notFoundAgents} onRefresh={onRefresh} m={m} t={t} />
|
|
261
|
+
) : (
|
|
262
|
+
<McpGuide
|
|
263
|
+
status={status} agents={agents} activeSkillName={activeSkillName}
|
|
264
|
+
connectedAgents={connectedAgents} detectedAgents={detectedAgents} notFoundAgents={notFoundAgents}
|
|
265
|
+
currentAgent={currentAgent} selectedAgent={selectedAgent} onSelectAgent={onSelectAgent}
|
|
266
|
+
restarting={restarting} onRestart={onRestart} onRefresh={onRefresh} m={m} t={t}
|
|
267
|
+
/>
|
|
268
|
+
)}
|
|
269
|
+
</div>
|
|
303
270
|
</div>
|
|
304
271
|
);
|
|
305
272
|
}
|
|
306
273
|
|
|
307
|
-
/* ──
|
|
274
|
+
/* ── CLI Guide (local + remote setup) ── */
|
|
308
275
|
|
|
309
|
-
function
|
|
310
|
-
status: McpStatus
|
|
311
|
-
m: Record<string, any> | undefined;
|
|
276
|
+
function CliGuide({ status, activeSkillName, agents, connectedAgents, detectedAgents, notFoundAgents, onRefresh, m, t }: {
|
|
277
|
+
status: McpStatus; activeSkillName: string; agents: AgentInfo[]; connectedAgents: AgentInfo[]; detectedAgents: AgentInfo[]; notFoundAgents: AgentInfo[];
|
|
278
|
+
onRefresh: () => void; m: Record<string, any> | undefined; t: McpTabProps['t'];
|
|
312
279
|
}) {
|
|
313
|
-
const
|
|
314
|
-
const [howToOpen, setHowToOpen] = useState(false);
|
|
315
|
-
const [copiedField, setCopiedField] = useState<'token' | 'url' | null>(null);
|
|
280
|
+
const { copiedField, handleCopy } = useCopyField();
|
|
316
281
|
|
|
317
|
-
|
|
318
|
-
|
|
282
|
+
const hasToken = status.authConfigured && !!status.authToken;
|
|
283
|
+
const remoteHost = status.localIP || 'localhost';
|
|
284
|
+
const webPort = typeof window !== 'undefined' ? window.location.port || '3456' : '3456';
|
|
285
|
+
const remoteUrl = `http://${remoteHost}:${webPort}`;
|
|
286
|
+
const maskedAuthToken = status.maskedToken ?? '';
|
|
319
287
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
288
|
+
return (
|
|
289
|
+
<>
|
|
290
|
+
{/* Local */}
|
|
291
|
+
<div className="space-y-2.5">
|
|
292
|
+
<div className="flex items-center gap-2">
|
|
293
|
+
<Monitor size={12} className="text-success" />
|
|
294
|
+
<span className="text-xs font-semibold text-foreground">{m?.localTitle ?? 'Local'}</span>
|
|
295
|
+
<span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-2xs font-medium bg-success/10 text-success">
|
|
296
|
+
<CheckCircle2 size={10} />
|
|
297
|
+
{m?.localReady ?? 'Ready to use'}
|
|
298
|
+
</span>
|
|
299
|
+
</div>
|
|
300
|
+
<p className="text-xs text-muted-foreground">
|
|
301
|
+
{m?.cliLocalDesc ?? 'MindOS Skill is built-in. Install the CLI and you\'re good to go.'}
|
|
302
|
+
</p>
|
|
303
|
+
<CodeBlock code="mindos file list" label={m?.cliSkillVerify ?? 'Verify'} onCopy={handleCopy} copiedField={copiedField} fieldId="cli-verify" />
|
|
304
|
+
|
|
305
|
+
{/* Detected Agents — install Skill locally */}
|
|
306
|
+
{agents.length > 0 && (
|
|
307
|
+
<InlineCollapsible
|
|
308
|
+
icon={<Users size={12} className="text-muted-foreground" />}
|
|
309
|
+
title={m?.detectedAgentsTitle ?? 'Detected Agents'}
|
|
310
|
+
badge={`${connectedAgents.length + detectedAgents.length}/${agents.length}`}
|
|
311
|
+
>
|
|
312
|
+
<AgentInstall agents={agents} t={t} onRefresh={onRefresh} mode="cli" activeSkillName={activeSkillName} />
|
|
313
|
+
</InlineCollapsible>
|
|
314
|
+
)}
|
|
315
|
+
</div>
|
|
326
316
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
317
|
+
{/* Remote */}
|
|
318
|
+
<div className="border-t border-border pt-3 space-y-3">
|
|
319
|
+
<div className="flex items-center gap-2">
|
|
320
|
+
<Globe size={12} className="text-muted-foreground" />
|
|
321
|
+
<span className="text-xs font-semibold text-foreground">{m?.remoteTitle ?? 'Remote Access'}</span>
|
|
322
|
+
</div>
|
|
323
|
+
<p className="text-xs text-muted-foreground">
|
|
324
|
+
{m?.cliRemoteDesc ?? 'Install the CLI on another machine and connect to this MindOS server.'}
|
|
325
|
+
</p>
|
|
326
|
+
<StepBlock step="1" label={m?.cliSkillInstall ?? 'Install'}>
|
|
327
|
+
<CodeBlock code="npm install -g @geminilight/mindos" onCopy={handleCopy} copiedField={copiedField} fieldId="cli-install" compact />
|
|
328
|
+
</StepBlock>
|
|
329
|
+
<StepBlock step="2" label={m?.cliRemoteConfigure ?? 'Configure'}>
|
|
330
|
+
<div className="space-y-1">
|
|
331
|
+
<CodeBlock code={`mindos config set url ${remoteUrl}`} onCopy={handleCopy} copiedField={copiedField} fieldId="cli-url" compact />
|
|
332
|
+
<CodeBlock
|
|
333
|
+
code={`mindos config set authToken ${hasToken ? maskedAuthToken : '<token>'}`}
|
|
334
|
+
onCopy={(_, field) => {
|
|
335
|
+
handleCopy(`mindos config set authToken ${status.authToken ?? '<token>'}`, field);
|
|
336
|
+
}}
|
|
337
|
+
copiedField={copiedField} fieldId="cli-token" compact
|
|
338
|
+
hint={hasToken ? (m?.tokenCopyFullHint ?? 'Copies full token') : undefined}
|
|
339
|
+
/>
|
|
340
|
+
</div>
|
|
341
|
+
</StepBlock>
|
|
342
|
+
<StepBlock step="3" label={m?.cliInstallSkill ?? 'Install Skill'}>
|
|
343
|
+
<CodeBlock code={`npx skills add GeminiLight/MindOS --skill ${activeSkillName} -g -y`} onCopy={handleCopy} copiedField={copiedField} fieldId="cli-skill" compact />
|
|
344
|
+
</StepBlock>
|
|
345
|
+
<StepBlock step="4" label={m?.cliSkillVerify ?? 'Verify'}>
|
|
346
|
+
<CodeBlock code="mindos file list" onCopy={handleCopy} copiedField={copiedField} fieldId="cli-remote-verify" compact />
|
|
347
|
+
</StepBlock>
|
|
348
|
+
</div>
|
|
349
|
+
</>
|
|
350
|
+
);
|
|
351
|
+
}
|
|
335
352
|
|
|
336
|
-
|
|
353
|
+
/* ── MCP Guide (status + snippets + remote) ── */
|
|
354
|
+
|
|
355
|
+
function McpGuide({ status, agents, activeSkillName, connectedAgents, detectedAgents, notFoundAgents, currentAgent, selectedAgent, onSelectAgent, restarting, onRestart, onRefresh, m, t }: {
|
|
356
|
+
status: McpStatus;
|
|
357
|
+
agents: AgentInfo[];
|
|
358
|
+
activeSkillName: string;
|
|
359
|
+
connectedAgents: AgentInfo[];
|
|
360
|
+
detectedAgents: AgentInfo[];
|
|
361
|
+
notFoundAgents: AgentInfo[];
|
|
362
|
+
currentAgent: AgentInfo | null;
|
|
363
|
+
selectedAgent: string;
|
|
364
|
+
onSelectAgent: (key: string) => void;
|
|
365
|
+
restarting: boolean;
|
|
366
|
+
onRestart: () => void;
|
|
367
|
+
onRefresh: () => void;
|
|
368
|
+
m: Record<string, any> | undefined;
|
|
369
|
+
t: McpTabProps['t'];
|
|
370
|
+
}) {
|
|
371
|
+
const { copiedField, handleCopy } = useCopyField();
|
|
337
372
|
|
|
338
373
|
const hasToken = status.authConfigured && !!status.authToken;
|
|
339
|
-
const
|
|
340
|
-
const
|
|
374
|
+
const remoteHost = status.localIP || 'localhost';
|
|
375
|
+
const mcpUrl = `http://${remoteHost}:${status.port}/mcp`;
|
|
376
|
+
|
|
377
|
+
const localSnippet = useMemo(
|
|
378
|
+
() => currentAgent ? generateSnippet(currentAgent, status, 'stdio') : null,
|
|
379
|
+
[currentAgent, status]
|
|
380
|
+
);
|
|
381
|
+
const remoteSnippet = useMemo(
|
|
382
|
+
() => currentAgent ? generateSnippet(currentAgent, status, 'http') : null,
|
|
383
|
+
[currentAgent, status]
|
|
384
|
+
);
|
|
341
385
|
|
|
342
386
|
return (
|
|
343
|
-
|
|
344
|
-
{/*
|
|
345
|
-
<
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
<div className="flex-
|
|
350
|
-
<
|
|
351
|
-
<
|
|
387
|
+
<>
|
|
388
|
+
{/* MCP Status */}
|
|
389
|
+
<McpStatusInline status={status} restarting={restarting} onRestart={onRestart} onRefresh={onRefresh} m={m} />
|
|
390
|
+
|
|
391
|
+
{/* Local (stdio) */}
|
|
392
|
+
<div className="space-y-2.5">
|
|
393
|
+
<div className="flex items-center gap-2">
|
|
394
|
+
<Monitor size={12} className={status.running ? 'text-success' : 'text-muted-foreground'} />
|
|
395
|
+
<span className="text-xs font-semibold text-foreground">{m?.localTitle ?? 'Local'}</span>
|
|
352
396
|
</div>
|
|
397
|
+
<p className="text-xs text-muted-foreground">
|
|
398
|
+
{m?.mcpLocalDesc ?? 'Copy the config snippet and paste into your agent\'s MCP settings.'}
|
|
399
|
+
</p>
|
|
400
|
+
{agents.length > 0 && (
|
|
401
|
+
<div className="space-y-3">
|
|
402
|
+
<CustomSelect
|
|
403
|
+
value={selectedAgent}
|
|
404
|
+
onChange={onSelectAgent}
|
|
405
|
+
options={[
|
|
406
|
+
...(connectedAgents.length > 0 ? [{ label: m?.connectedGroup ?? 'Connected', options: connectedAgents.map(a => ({ value: a.key, label: a.name })) }] : []),
|
|
407
|
+
...(detectedAgents.length > 0 ? [{ label: m?.detectedGroup ?? 'Detected', options: detectedAgents.map(a => ({ value: a.key, label: a.name })) }] : []),
|
|
408
|
+
...(notFoundAgents.length > 0 ? [{ label: m?.notFoundGroup ?? 'Not Installed', options: notFoundAgents.map(a => ({ value: a.key, label: a.name })) }] : []),
|
|
409
|
+
] as SelectItem[]}
|
|
410
|
+
/>
|
|
411
|
+
{currentAgent && localSnippet && (
|
|
412
|
+
<>
|
|
413
|
+
{currentAgent.present && currentAgent.installed && (
|
|
414
|
+
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-2xs font-medium bg-success/10 text-success">
|
|
415
|
+
<CheckCircle2 size={10} /> {m?.tagConnected ?? 'Connected'}
|
|
416
|
+
</span>
|
|
417
|
+
)}
|
|
418
|
+
<pre className="text-[11px] font-mono bg-muted/50 border border-border rounded-lg p-3 overflow-x-auto whitespace-pre select-all max-h-[200px] overflow-y-auto">
|
|
419
|
+
{localSnippet.displaySnippet}
|
|
420
|
+
</pre>
|
|
421
|
+
<div className="flex items-center gap-3 text-sm">
|
|
422
|
+
<button onClick={async () => { const ok = await copyToClipboard(localSnippet.snippet); if (ok) toast.copy(); }}
|
|
423
|
+
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors shrink-0">
|
|
424
|
+
<Copy size={14} /> {m?.copyConfig ?? 'Copy'}
|
|
425
|
+
</button>
|
|
426
|
+
<span className="text-muted-foreground">→</span>
|
|
427
|
+
<span className="font-mono text-muted-foreground truncate text-2xs">{localSnippet.path}</span>
|
|
428
|
+
</div>
|
|
429
|
+
</>
|
|
430
|
+
)}
|
|
431
|
+
</div>
|
|
432
|
+
)}
|
|
353
433
|
</div>
|
|
354
434
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
<
|
|
358
|
-
<
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
{
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
<code className="flex-1 text-xs font-mono text-foreground break-all select-all leading-relaxed">
|
|
366
|
-
{displayToken}
|
|
367
|
-
</code>
|
|
368
|
-
</div>
|
|
369
|
-
<button
|
|
370
|
-
type="button"
|
|
371
|
-
onClick={() => setRevealed(v => !v)}
|
|
372
|
-
className="shrink-0 p-2 rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1"
|
|
373
|
-
title={revealed ? (m?.tokenHide ?? 'Hide') : (m?.tokenShow ?? 'Show')}
|
|
374
|
-
>
|
|
375
|
-
{revealed ? <EyeOff size={14} /> : <Eye size={14} />}
|
|
376
|
-
</button>
|
|
377
|
-
<button
|
|
378
|
-
type="button"
|
|
379
|
-
onClick={() => handleCopy(status.authToken ?? '', 'token')}
|
|
380
|
-
className={`shrink-0 p-2 rounded-lg border transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 ${
|
|
381
|
-
copiedField === 'token'
|
|
382
|
-
? 'border-success/50 bg-success/10 text-success'
|
|
383
|
-
: 'border-border text-muted-foreground hover:text-foreground hover:bg-muted'
|
|
384
|
-
}`}
|
|
385
|
-
title={m?.tokenCopy ?? 'Copy'}
|
|
386
|
-
>
|
|
387
|
-
{copiedField === 'token' ? <Check size={14} /> : <Copy size={14} />}
|
|
388
|
-
</button>
|
|
389
|
-
</div>
|
|
390
|
-
) : (
|
|
391
|
-
<div className="px-3 py-2.5 bg-[var(--amber-subtle)] border border-[var(--amber)]/20 rounded-lg">
|
|
392
|
-
<p className="text-xs text-[var(--amber-text)]">{m?.tokenNone ?? 'No token set.'}</p>
|
|
393
|
-
<p className="text-xs text-muted-foreground mt-1">{m?.tokenNoneAction ?? 'Generate one in Settings \u2192 General \u2192 Security.'}</p>
|
|
394
|
-
</div>
|
|
395
|
-
)}
|
|
396
|
-
</div>
|
|
435
|
+
{/* Detected Agents — install MCP config + Skill locally */}
|
|
436
|
+
{agents.length > 0 && (
|
|
437
|
+
<InlineCollapsible
|
|
438
|
+
icon={<Users size={12} className="text-muted-foreground" />}
|
|
439
|
+
title={m?.detectedAgentsTitle ?? 'Detected Agents'}
|
|
440
|
+
badge={`${connectedAgents.length + detectedAgents.length}/${agents.length}`}
|
|
441
|
+
>
|
|
442
|
+
<AgentInstall agents={agents} t={t} onRefresh={onRefresh} mode="mcp" activeSkillName={activeSkillName} />
|
|
443
|
+
</InlineCollapsible>
|
|
444
|
+
)}
|
|
397
445
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
: 'border-border text-muted-foreground hover:text-foreground hover:bg-muted'
|
|
415
|
-
}`}
|
|
416
|
-
title={m?.serverUrlCopy ?? 'Copy URL'}
|
|
417
|
-
>
|
|
418
|
-
{copiedField === 'url' ? <Check size={14} /> : <Copy size={14} />}
|
|
419
|
-
</button>
|
|
446
|
+
{/* Remote */}
|
|
447
|
+
<div className="border-t border-border pt-3 space-y-3">
|
|
448
|
+
<div className="flex items-center gap-2">
|
|
449
|
+
<Globe size={12} className="text-muted-foreground" />
|
|
450
|
+
<span className="text-xs font-semibold text-foreground">{m?.remoteTitle ?? 'Remote Access'}</span>
|
|
451
|
+
</div>
|
|
452
|
+
{!hasToken && (
|
|
453
|
+
<p className="flex items-center gap-1.5 text-xs text-[var(--amber-text)]">
|
|
454
|
+
<AlertCircle size={12} /> {m?.noAuthWarning ?? 'Set an Auth Token in Settings → General before enabling remote access.'}
|
|
455
|
+
</p>
|
|
456
|
+
)}
|
|
457
|
+
<div className="space-y-1">
|
|
458
|
+
<span className="text-2xs font-medium text-muted-foreground uppercase tracking-wider">{m?.serverUrl ?? 'MCP Server URL'}</span>
|
|
459
|
+
<div className="flex items-center gap-2 px-2.5 py-1.5 bg-muted/50 border border-border rounded-lg">
|
|
460
|
+
<code className="flex-1 text-xs font-mono text-foreground select-all truncate">{mcpUrl}</code>
|
|
461
|
+
<CopyButton onCopy={() => handleCopy(mcpUrl, 'mcp-url')} copied={copiedField === 'mcp-url'} size="sm" />
|
|
420
462
|
</div>
|
|
421
463
|
</div>
|
|
464
|
+
{agents.length > 0 && currentAgent && remoteSnippet && (
|
|
465
|
+
<>
|
|
466
|
+
<pre className="text-[11px] font-mono bg-muted/50 border border-border rounded-lg p-3 overflow-x-auto whitespace-pre select-all max-h-[200px] overflow-y-auto">
|
|
467
|
+
{remoteSnippet.displaySnippet}
|
|
468
|
+
</pre>
|
|
469
|
+
<div className="flex items-center gap-3 text-sm">
|
|
470
|
+
<button onClick={async () => { const ok = await copyToClipboard(remoteSnippet.snippet); if (ok) toast.copy(); }}
|
|
471
|
+
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors shrink-0">
|
|
472
|
+
<Copy size={14} /> {m?.copyConfig ?? 'Copy'}
|
|
473
|
+
</button>
|
|
474
|
+
<span className="text-muted-foreground">→</span>
|
|
475
|
+
<span className="font-mono text-muted-foreground truncate text-2xs">{remoteSnippet.path}</span>
|
|
476
|
+
</div>
|
|
477
|
+
</>
|
|
478
|
+
)}
|
|
479
|
+
</div>
|
|
480
|
+
</>
|
|
481
|
+
);
|
|
482
|
+
}
|
|
422
483
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
484
|
+
/* ── Inline Collapsible (inside a card) ── */
|
|
485
|
+
|
|
486
|
+
function InlineCollapsible({ icon, title, badge, defaultOpen = false, children }: {
|
|
487
|
+
icon?: React.ReactNode;
|
|
488
|
+
title: string;
|
|
489
|
+
badge?: string;
|
|
490
|
+
defaultOpen?: boolean;
|
|
491
|
+
children: React.ReactNode;
|
|
492
|
+
}) {
|
|
493
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
494
|
+
return (
|
|
495
|
+
<div className="rounded-lg border border-border overflow-hidden">
|
|
496
|
+
<button
|
|
497
|
+
type="button"
|
|
498
|
+
onClick={() => setOpen(v => !v)}
|
|
499
|
+
className="flex items-center gap-2 w-full px-3 py-2 text-left bg-muted/30 hover:bg-muted/60 transition-colors"
|
|
500
|
+
>
|
|
501
|
+
{open ? <ChevronDown size={11} className="text-muted-foreground" /> : <ChevronRight size={11} className="text-muted-foreground" />}
|
|
502
|
+
{icon}
|
|
503
|
+
<span className="text-xs font-medium text-muted-foreground">{title}</span>
|
|
504
|
+
{badge && <span className="text-2xs text-muted-foreground/70 ml-auto">{badge}</span>}
|
|
505
|
+
</button>
|
|
506
|
+
{open && <div className="px-3 py-2.5 border-t border-border">{children}</div>}
|
|
507
|
+
</div>
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/* ── Step Block ── */
|
|
512
|
+
|
|
513
|
+
function StepBlock({ step, label, children }: { step: string; label: string; children: React.ReactNode }) {
|
|
514
|
+
return (
|
|
515
|
+
<div className="space-y-1.5">
|
|
516
|
+
<div className="flex items-center gap-2">
|
|
517
|
+
<span className="w-4 h-4 rounded-full bg-muted flex items-center justify-center text-2xs font-semibold text-muted-foreground shrink-0">{step}</span>
|
|
518
|
+
<span className="text-2xs font-medium text-muted-foreground uppercase tracking-wider">{label}</span>
|
|
519
|
+
</div>
|
|
520
|
+
<div className="pl-6">{children}</div>
|
|
521
|
+
</div>
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/* ── MCP Status (compact inline) ── */
|
|
526
|
+
|
|
527
|
+
function McpStatusInline({ status, restarting, onRestart, onRefresh, m }: {
|
|
528
|
+
status: McpStatus; restarting: boolean; onRestart: () => void; onRefresh: () => void; m: Record<string, any> | undefined;
|
|
529
|
+
}) {
|
|
530
|
+
return (
|
|
531
|
+
<div className="flex items-center justify-between px-3 py-2 rounded-lg border border-border bg-muted/30">
|
|
532
|
+
<div className="flex items-center gap-2 text-xs">
|
|
533
|
+
{restarting ? (
|
|
534
|
+
<><Loader2 size={11} className="animate-spin text-[var(--amber)]" /><span className="text-[var(--amber)]">{m?.restarting ?? 'Restarting...'}</span></>
|
|
535
|
+
) : (
|
|
536
|
+
<>
|
|
537
|
+
<span className={`inline-block w-1.5 h-1.5 rounded-full shrink-0 ${status.running ? 'bg-success' : 'bg-muted-foreground'}`} />
|
|
538
|
+
<span className="font-medium text-foreground">MCP {status.running ? (m?.running ?? 'Running') : (m?.stopped ?? 'Stopped')}</span>
|
|
539
|
+
{status.running && <><span className="text-muted-foreground">·</span><span className="text-muted-foreground">:{status.port}</span><span className="text-muted-foreground">·</span><span className="text-muted-foreground">{status.toolCount} {m?.tools ?? 'tools'}</span></>}
|
|
540
|
+
</>
|
|
439
541
|
)}
|
|
440
542
|
</div>
|
|
543
|
+
<div className="flex items-center gap-1.5">
|
|
544
|
+
{!status.running && !restarting && (
|
|
545
|
+
<button onClick={onRestart} className="flex items-center gap-1 px-2 py-1 text-2xs rounded-md font-medium text-[var(--amber-foreground)] bg-[var(--amber)] transition-colors">
|
|
546
|
+
<RotateCcw size={11} /> {m?.restart ?? 'Restart'}
|
|
547
|
+
</button>
|
|
548
|
+
)}
|
|
549
|
+
<button onClick={onRefresh} className="p-1 rounded-md border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors">
|
|
550
|
+
<RefreshCw size={11} />
|
|
551
|
+
</button>
|
|
552
|
+
</div>
|
|
441
553
|
</div>
|
|
442
554
|
);
|
|
443
555
|
}
|
|
556
|
+
|
|
557
|
+
/* ── Code Block ── */
|
|
558
|
+
|
|
559
|
+
function CodeBlock({ label, code, onCopy, copiedField, fieldId, compact, hint }: {
|
|
560
|
+
label?: string; code: string; onCopy: (text: string, field: string) => void; copiedField: string | null; fieldId: string; compact?: boolean; hint?: string;
|
|
561
|
+
}) {
|
|
562
|
+
return (
|
|
563
|
+
<div className={compact ? '' : 'space-y-1'}>
|
|
564
|
+
{label && <span className="text-2xs font-medium text-muted-foreground uppercase tracking-wider">{label}</span>}
|
|
565
|
+
<div className={`flex items-center gap-2 ${compact ? 'px-2.5 py-1.5' : 'px-3 py-2'} bg-muted/50 border border-border rounded-lg`}>
|
|
566
|
+
<code className="flex-1 text-xs font-mono text-foreground select-all truncate">{code}</code>
|
|
567
|
+
{hint && <span className="text-2xs text-muted-foreground/60 shrink-0 hidden sm:inline">{hint}</span>}
|
|
568
|
+
<CopyButton onCopy={() => onCopy(code, fieldId)} copied={copiedField === fieldId} size="sm" />
|
|
569
|
+
</div>
|
|
570
|
+
</div>
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/* ── Copy Button ── */
|
|
575
|
+
|
|
576
|
+
function CopyButton({ onCopy, copied, title, size }: { onCopy: () => void; copied: boolean; title?: string; size?: 'sm' | 'md' }) {
|
|
577
|
+
const sz = size === 'sm' ? 11 : 14;
|
|
578
|
+
const pad = size === 'sm' ? 'p-1' : 'p-2';
|
|
579
|
+
return (
|
|
580
|
+
<button type="button" onClick={onCopy} title={title ?? 'Copy'}
|
|
581
|
+
className={`shrink-0 ${pad} rounded-lg border transition-colors focus-visible:ring-2 focus-visible:ring-ring ${
|
|
582
|
+
copied ? 'border-success/50 bg-success/10 text-success' : 'border-border text-muted-foreground hover:text-foreground hover:bg-muted'
|
|
583
|
+
}`}>
|
|
584
|
+
{copied ? <Check size={sz} /> : <Copy size={sz} />}
|
|
585
|
+
</button>
|
|
586
|
+
);
|
|
587
|
+
}
|