@nextclaw/ui 0.14.4 → 0.15.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/CHANGELOG.md +54 -0
- package/dist/assets/api-DLtsiBKm.js +13 -0
- package/dist/assets/appearance-settings-page-CtkeYXux.js +1 -0
- package/dist/assets/book-open-BQwWBDKb.js +1 -0
- package/dist/assets/channels-list-page-Cv8o9Hxg.js +8 -0
- package/dist/assets/chat-page-BtA7H1Ea.js +1 -0
- package/dist/assets/{config-split-page-CRhlZ_pH.js → config-split-page-EidRBpP2.js} +1 -1
- package/dist/assets/confirm-dialog-iIJFFp2T.js +5 -0
- package/dist/assets/desktop-update-config-C6n_TB1r.js +1 -0
- package/dist/assets/{dist-CikkhOlq.js → dist-CBxWOazz.js} +1 -1
- package/dist/assets/{dist-PVA_xL1D.js → dist-D2eZC3OR.js} +1 -1
- package/dist/assets/doc-browser-B76XOXmW.js +1 -0
- package/dist/assets/doc-browser-DBnj9UMB.js +1 -0
- package/dist/assets/{doc-browser-context-MTw4EvKd.js → doc-browser-context-Sv-044Cl.js} +1 -1
- package/dist/assets/{ellipsis-y6vDtq4M.js → ellipsis-2uzhypw5.js} +1 -1
- package/dist/assets/es2015-DhQo_oS6.js +41 -0
- package/dist/assets/{external-link-DVJIQf_G.js → external-link-CM5dnUI6.js} +1 -1
- package/dist/assets/host-capabilities-9bB_998Z.js +1 -0
- package/dist/assets/i18n-provider-di1waTGX.js +1 -0
- package/dist/assets/index-CBRP-7-a.css +1 -0
- package/dist/assets/index-Z1fYpbNd.js +104 -0
- package/dist/assets/loader-circle-BjmN5DeJ.js +1 -0
- package/dist/assets/mcp-marketplace-page-C_MVQUbT.js +9 -0
- package/dist/assets/mcp-marketplace-page-CoZIiD6G.js +1 -0
- package/dist/assets/model-config-page-Cdr9P6TQ.js +1 -0
- package/dist/assets/plus-3ztRZD_l.js +1 -0
- package/dist/assets/provider-scoped-model-input-g1WkGiJ8.js +1 -0
- package/dist/assets/providers-config-page-CWlO7J4A.js +1 -0
- package/dist/assets/{react-VNqT5Zp7.js → react-DjpzoZXB.js} +1 -1
- package/dist/assets/refresh-cw-DaTv3zxE.js +1 -0
- package/dist/assets/remote-CKWZfVVB.js +1 -0
- package/dist/assets/rotate-cw-CHcaRUDz.js +1 -0
- package/dist/assets/runtime-config-page-CfTqh1nv.js +1 -0
- package/dist/assets/{save-BOJ477H-.js → save-BZof1uie.js} +1 -1
- package/dist/assets/{search-BEti5beA.js → search-MxAarS_g.js} +1 -1
- package/dist/assets/search-config-page-Dm8daYny.js +1 -0
- package/dist/assets/{secrets-config-page--IzrIOid.js → secrets-config-page-D8AbcR05.js} +2 -2
- package/dist/assets/security-config-Ds0LsJmo.js +1 -0
- package/dist/assets/select-D7GcNP0z.js +1 -0
- package/dist/assets/setting-row-CuozlnJd.js +1 -0
- package/dist/assets/side-dock-BcLau5S_.js +3 -0
- package/dist/assets/skeleton-shfmXuYp.js +1 -0
- package/dist/assets/switch-C1BWMU5c.js +1 -0
- package/dist/assets/tag-chip-DCUrQUCP.js +1 -0
- package/dist/assets/tooltip-DfAHHA76.js +1 -0
- package/dist/assets/use-config-DAdbT5QJ.js +1 -0
- package/dist/assets/use-confirm-dialog-eWcJ6V-s.js +1 -0
- package/dist/assets/x-DqL1mYLC.js +1 -0
- package/dist/index.html +53 -17
- package/index.html +25 -2
- package/package.json +9 -9
- package/src/app/components/layout/__tests__/app-layout.test.tsx +27 -0
- package/src/app/components/layout/__tests__/settings-entry-page.test.tsx +9 -1
- package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +6 -3
- package/src/app/components/layout/app-layout.tsx +10 -3
- package/src/app/components/layout/sidebar-items.tsx +8 -8
- package/src/app/components/layout/sidebar-rail.styles.ts +4 -2
- package/src/app/components/layout/sidebar.tsx +21 -28
- package/src/app/configs/app-navigation.config.ts +11 -5
- package/src/app/index.tsx +8 -0
- package/src/app/managers/__tests__/viewport-layout.manager.test.ts +59 -0
- package/src/app/managers/viewport-layout.manager.ts +39 -0
- package/src/app/presenters/app.presenter.ts +25 -2
- package/src/app/styles/design-system.css +91 -4
- package/src/features/chat/components/conversation/__tests__/chat-conversation-content.test.tsx +86 -0
- package/src/features/chat/components/conversation/chat-conversation-content.tsx +47 -17
- package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +7 -0
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +9 -5
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +5 -5
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +51 -2
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +170 -1
- package/src/features/chat/features/conversation/components/session-conversation-area.tsx +23 -1
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +24 -7
- package/src/features/chat/features/conversation/components/session-queued-input-rows.tsx +96 -0
- package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-controller.test.tsx +181 -0
- package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +351 -89
- package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +29 -4
- package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +28 -3
- package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +108 -8
- package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +50 -14
- package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +77 -6
- package/src/features/chat/features/message/components/__tests__/chat-inline-panel-app-card.test.tsx +21 -12
- package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +166 -0
- package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +19 -11
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +38 -64
- package/src/features/chat/features/message/utils/__tests__/chat-message-process-summary.utils.test.ts +59 -0
- package/src/features/chat/features/message/utils/__tests__/chat-message-show-content-tool-card.utils.test.ts +56 -1
- package/src/features/chat/features/message/utils/chat-inline-panel-app-card.utils.ts +0 -2
- package/src/features/chat/features/message/utils/chat-message-process-summary.utils.ts +80 -0
- package/src/features/chat/features/message/utils/chat-message-show-content-tool-card.utils.ts +12 -3
- package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +60 -0
- package/src/features/chat/features/message/utils/chat-message.utils.ts +3 -0
- package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-agent-runtime.test.tsx +39 -0
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +2 -2
- package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +1 -1
- package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +3 -1
- package/src/features/chat/features/session/components/session-header/chat-session-metadata-dialog.tsx +1 -1
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +270 -115
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumb-browser.tsx +121 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +75 -20
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +70 -5
- package/src/features/chat/features/workspace/utils/chat-workspace-file-tab-persistence.utils.ts +83 -0
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +79 -1
- package/src/features/chat/managers/__tests__/chat-ui.manager.test.ts +2 -0
- package/src/features/chat/managers/chat-thread.manager.ts +9 -3
- package/src/features/chat/managers/chat-ui.manager.ts +5 -1
- package/src/features/chat/presenters/chat.presenter.ts +2 -1
- package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +4 -0
- package/src/features/chat/stores/chat-thread.store.ts +6 -64
- package/src/features/chat/types/chat-message.types.ts +4 -0
- package/src/features/marketplace/components/__tests__/marketplace-page-detail.test.tsx +33 -26
- package/src/features/marketplace/components/__tests__/marketplace-page.test.tsx +1 -0
- package/src/features/marketplace/components/curated-shelves/__tests__/marketplace-curated-scene-route.test.tsx +1 -0
- package/src/features/marketplace/components/detail-doc/__tests__/marketplace-detail-doc.test.tsx +115 -0
- package/src/features/marketplace/components/marketplace-detail-doc.tsx +441 -0
- package/src/features/marketplace/components/mcp/__tests__/mcp-marketplace-page.test.tsx +3 -3
- package/src/features/marketplace/components/mcp/mcp-marketplace-doc.ts +0 -60
- package/src/features/marketplace/components/mcp/mcp-marketplace-page.tsx +64 -29
- package/src/features/marketplace/hooks/use-marketplace-item-detail.ts +42 -31
- package/src/features/marketplace/index.ts +3 -0
- package/src/features/marketplace/stores/marketplace-detail-doc.store.ts +38 -0
- package/src/features/panel-apps/components/__tests__/panel-apps-list.test.tsx +32 -2
- package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +11 -0
- package/src/features/pwa/managers/pwa-shell-theme.manager.ts +8 -3
- package/src/features/settings/pages/__tests__/appearance-settings-page.test.tsx +25 -0
- package/src/features/settings/pages/appearance-settings-page.tsx +40 -0
- package/src/features/side-dock/components/__tests__/side-dock.test.tsx +44 -2
- package/src/features/side-dock/components/side-dock.tsx +100 -13
- package/src/features/side-dock/configs/side-dock-built-in-items.config.ts +10 -0
- package/src/features/side-dock/managers/__tests__/side-dock.manager.test.ts +32 -2
- package/src/features/side-dock/managers/side-dock.manager.ts +17 -7
- package/src/features/side-dock/stores/side-dock.store.ts +9 -0
- package/src/features/side-dock/types/side-dock.types.ts +10 -3
- package/src/features/side-dock/utils/side-dock-item.utils.ts +15 -2
- package/src/index.css +501 -27
- package/src/platforms/desktop/components/desktop-window-chrome.tsx +1 -1
- package/src/shared/components/doc-browser/__tests__/doc-browser-context.test.tsx +30 -1
- package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +206 -38
- package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +34 -11
- package/src/shared/components/doc-browser/doc-browser-renderer.types.ts +1 -1
- package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +23 -5
- package/src/shared/components/doc-browser/doc-browser.tsx +128 -75
- package/src/shared/components/doc-browser/managers/doc-browser.manager.ts +9 -1
- package/src/shared/components/doc-browser/utils/doc-browser-floating-panel.utils.ts +83 -0
- package/src/shared/components/ui/__tests__/dialog.test.tsx +27 -0
- package/src/shared/components/ui/actions/icon-action-button.tsx +14 -2
- package/src/shared/components/ui/dialog.tsx +2 -2
- package/src/shared/components/ui/popover.tsx +1 -1
- package/src/shared/components/ui/select.tsx +1 -1
- package/src/shared/components/ui/switch.tsx +4 -3
- package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +54 -0
- package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +4 -5
- package/src/shared/components/ui/tooltip.tsx +1 -1
- package/src/shared/lib/api/utils/server-path.utils.ts +27 -1
- package/src/shared/lib/i18n/locales/en-US/chat.json +15 -6
- package/src/shared/lib/i18n/locales/en-US/core.json +2 -0
- package/src/shared/lib/i18n/locales/en-US/doc-browser.json +11 -0
- package/src/shared/lib/i18n/locales/en-US/marketplace.json +5 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +15 -6
- package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -0
- package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +11 -0
- package/src/shared/lib/i18n/locales/zh-CN/marketplace.json +5 -0
- package/src/shared/lib/session-project/__tests__/workspace-file-breadcrumb.test.ts +14 -0
- package/src/shared/lib/session-project/index.ts +1 -1
- package/src/shared/lib/session-project/{workspace-file-breadcrumb.ts → workspace-file-breadcrumb.utils.ts} +62 -8
- package/src/shared/lib/theme/index.ts +51 -13
- package/dist/assets/api-BP_8IaxB.js +0 -13
- package/dist/assets/channels-list-page-DEh5Jjqx.js +0 -8
- package/dist/assets/chat-page-BRB0N5WW.js +0 -105
- package/dist/assets/confirm-dialog-DsivFJ4r.js +0 -5
- package/dist/assets/desktop-update-config-B0w9p2ep.js +0 -1
- package/dist/assets/doc-browser-BoNJ7-tE.js +0 -1
- package/dist/assets/doc-browser-CdJ_d4ik.js +0 -1
- package/dist/assets/index-BiE-K8-q.css +0 -1
- package/dist/assets/index-D9BAE_L_.js +0 -105
- package/dist/assets/mcp-marketplace-page-B1SZNY5m.js +0 -40
- package/dist/assets/mcp-marketplace-page-Cq_Tw42T.js +0 -1
- package/dist/assets/model-config-page-DotaTUHb.js +0 -1
- package/dist/assets/plus-BbZqquJe.js +0 -1
- package/dist/assets/provider-scoped-model-input-DsFRuWJH.js +0 -1
- package/dist/assets/providers-config-page-DW4tQSLN.js +0 -1
- package/dist/assets/remote-B8TiUfwW.js +0 -1
- package/dist/assets/rotate-cw-DjHDTkCv.js +0 -1
- package/dist/assets/runtime-config-page-S6ErRHR8.js +0 -1
- package/dist/assets/search-config-page-DGj5am03.js +0 -1
- package/dist/assets/select-hoj8dK4K.js +0 -41
- package/dist/assets/tag-chip-T8-h-die.js +0 -1
- package/dist/assets/use-config-CjRV2hwL.js +0 -1
- package/dist/assets/x-D9PXs_xP.js +0 -1
- package/src/features/marketplace/components/detail-doc/__tests__/marketplace-detail-doc.test.ts +0 -40
- package/src/features/marketplace/components/detail-doc/marketplace-detail-doc-renderer.ts +0 -201
- package/src/features/marketplace/components/marketplace-detail-doc.ts +0 -142
- /package/dist/assets/{config-hints-BkYBc7z0.js → config-hints-fGnUjDe9.js} +0 -0
- /package/dist/assets/{middleware-CkFT3R4v.js → middleware-BILWbJE4.js} +0 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { AlertCircle, FileText } from 'lucide-react';
|
|
3
|
+
import type { DocBrowserCustomTabRenderers } from '@/shared/components/doc-browser/doc-browser-renderer.types';
|
|
4
|
+
import type {
|
|
5
|
+
DocBrowserContextValue,
|
|
6
|
+
DocBrowserRouteTarget,
|
|
7
|
+
} from '@/shared/components/doc-browser/types/doc-browser.types';
|
|
8
|
+
import { Skeleton } from '@/shared/components/ui/skeleton';
|
|
9
|
+
import { t } from '@/shared/lib/i18n';
|
|
10
|
+
import { cn } from '@/shared/lib/utils';
|
|
11
|
+
import {
|
|
12
|
+
setMarketplaceDetailDocEntry,
|
|
13
|
+
useMarketplaceDetailDocStore,
|
|
14
|
+
type MarketplaceDetailDocEntry,
|
|
15
|
+
} from '@/features/marketplace/stores/marketplace-detail-doc.store';
|
|
16
|
+
|
|
17
|
+
export const MARKETPLACE_DETAIL_TAB_KIND = 'marketplace-detail';
|
|
18
|
+
|
|
19
|
+
type MetadataEntry = {
|
|
20
|
+
key: string;
|
|
21
|
+
value: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const DETAIL_URL_PREFIX = 'nextclaw://marketplace-detail';
|
|
25
|
+
|
|
26
|
+
export function createMarketplaceDetailDocId(scope: string, key: string): string {
|
|
27
|
+
return `${scope}:${key}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createMarketplaceDetailDocUrl(detailId: string): string {
|
|
31
|
+
return `${DETAIL_URL_PREFIX}/${encodeURIComponent(detailId)}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readMarketplaceDetailDocId(url: string): string | null {
|
|
35
|
+
try {
|
|
36
|
+
const parsed = new URL(url);
|
|
37
|
+
if (parsed.protocol !== 'nextclaw:' || parsed.hostname !== 'marketplace-detail') {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const rawId = parsed.pathname.replace(/^\//, '');
|
|
41
|
+
return rawId ? decodeURIComponent(rawId) : null;
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createMarketplaceDetailDocTarget(
|
|
48
|
+
entry: MarketplaceDetailDocEntry,
|
|
49
|
+
): DocBrowserRouteTarget {
|
|
50
|
+
const url = createMarketplaceDetailDocUrl(entry.id);
|
|
51
|
+
return {
|
|
52
|
+
dedupeKey: `marketplace-detail:${entry.id}`,
|
|
53
|
+
historyPolicy: 'managed',
|
|
54
|
+
kind: MARKETPLACE_DETAIL_TAB_KIND,
|
|
55
|
+
resourceUri: url,
|
|
56
|
+
title: entry.title,
|
|
57
|
+
url,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function openMarketplaceDetailDoc(
|
|
62
|
+
docBrowser: Pick<DocBrowserContextValue, 'openTarget'>,
|
|
63
|
+
entry: MarketplaceDetailDocEntry,
|
|
64
|
+
options?: { activate?: boolean },
|
|
65
|
+
): void {
|
|
66
|
+
setMarketplaceDetailDocEntry(entry);
|
|
67
|
+
docBrowser.openTarget(createMarketplaceDetailDocTarget(entry), {
|
|
68
|
+
activate: options?.activate,
|
|
69
|
+
dedupeKey: `marketplace-detail:${entry.id}`,
|
|
70
|
+
title: entry.title,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function stripYamlFrontmatter(text: string): string {
|
|
75
|
+
const lines = text.replace(/\r\n/g, '\n').split('\n');
|
|
76
|
+
if (lines[0]?.trim() !== '---') {
|
|
77
|
+
return text;
|
|
78
|
+
}
|
|
79
|
+
const closingIndex = lines.findIndex((line, index) => index > 0 && line.trim() === '---');
|
|
80
|
+
return closingIndex > 0 ? lines.slice(closingIndex + 1).join('\n').trim() : text;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function renderInlineMarkdown(text: string): ReactNode[] {
|
|
84
|
+
const nodes: ReactNode[] = [];
|
|
85
|
+
const tokenPattern = /(`[^`]+`|\*\*[^*]+\*\*|\[[^\]]+\]\(https?:\/\/[^)\s]+\))/g;
|
|
86
|
+
let cursor = 0;
|
|
87
|
+
let index = 0;
|
|
88
|
+
for (const match of text.matchAll(tokenPattern)) {
|
|
89
|
+
if (match.index === undefined) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (match.index > cursor) {
|
|
93
|
+
nodes.push(text.slice(cursor, match.index));
|
|
94
|
+
}
|
|
95
|
+
const token = match[0];
|
|
96
|
+
const key = `${index}-${match.index}`;
|
|
97
|
+
if (token.startsWith('`')) {
|
|
98
|
+
nodes.push(
|
|
99
|
+
<code key={key} className="rounded border border-border bg-muted px-1 py-0.5 font-mono text-[0.88em] text-foreground">
|
|
100
|
+
{token.slice(1, -1)}
|
|
101
|
+
</code>,
|
|
102
|
+
);
|
|
103
|
+
} else if (token.startsWith('**')) {
|
|
104
|
+
nodes.push(<strong key={key}>{token.slice(2, -2)}</strong>);
|
|
105
|
+
} else {
|
|
106
|
+
const linkMatch = token.match(/^\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)$/);
|
|
107
|
+
nodes.push(
|
|
108
|
+
linkMatch ? (
|
|
109
|
+
<a key={key} href={linkMatch[2]} target="_blank" rel="noopener noreferrer">
|
|
110
|
+
{linkMatch[1]}
|
|
111
|
+
</a>
|
|
112
|
+
) : token,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
cursor = match.index + token.length;
|
|
116
|
+
index += 1;
|
|
117
|
+
}
|
|
118
|
+
if (cursor < text.length) {
|
|
119
|
+
nodes.push(text.slice(cursor));
|
|
120
|
+
}
|
|
121
|
+
return nodes;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function isMarkdownBlockStart(line: string): boolean {
|
|
125
|
+
return /^(#{1,4})\s+/.test(line)
|
|
126
|
+
|| /^([-*])\s+/.test(line)
|
|
127
|
+
|| /^\d+\.\s+/.test(line)
|
|
128
|
+
|| /^>\s?/.test(line)
|
|
129
|
+
|| /^```/.test(line);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function renderCodeBlock(lines: string[], index: number, fenceMatch: RegExpMatchArray) {
|
|
133
|
+
const codeLines: string[] = [];
|
|
134
|
+
let nextIndex = index + 1;
|
|
135
|
+
while (nextIndex < lines.length && !lines[nextIndex]?.trim().startsWith('```')) {
|
|
136
|
+
codeLines.push(lines[nextIndex] ?? '');
|
|
137
|
+
nextIndex += 1;
|
|
138
|
+
}
|
|
139
|
+
const language = fenceMatch[1] ?? '';
|
|
140
|
+
return {
|
|
141
|
+
node: (
|
|
142
|
+
<div key={index} className="relative my-3 overflow-hidden rounded-lg border border-border bg-muted/70">
|
|
143
|
+
{language ? (
|
|
144
|
+
<span className="absolute right-3 top-2 font-mono text-[10px] text-muted-foreground">
|
|
145
|
+
{language}
|
|
146
|
+
</span>
|
|
147
|
+
) : null}
|
|
148
|
+
<pre className="m-0 overflow-x-auto p-3 text-xs leading-6 text-foreground">
|
|
149
|
+
<code>{codeLines.join('\n')}</code>
|
|
150
|
+
</pre>
|
|
151
|
+
</div>
|
|
152
|
+
),
|
|
153
|
+
nextIndex: nextIndex + 1,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function renderListBlock(lines: string[], index: number, ordered: boolean) {
|
|
158
|
+
const items: string[] = [];
|
|
159
|
+
let nextIndex = index;
|
|
160
|
+
const matcher = ordered ? /^\d+\.\s+/ : /^[-*]\s+/;
|
|
161
|
+
while (nextIndex < lines.length && matcher.test(lines[nextIndex]?.trim() ?? '')) {
|
|
162
|
+
items.push((lines[nextIndex] ?? '').trim().replace(matcher, ''));
|
|
163
|
+
nextIndex += 1;
|
|
164
|
+
}
|
|
165
|
+
const Tag = ordered ? 'ol' : 'ul';
|
|
166
|
+
return {
|
|
167
|
+
node: (
|
|
168
|
+
<Tag key={index} className={cn('my-2 pl-5', ordered ? 'list-decimal' : 'list-disc')}>
|
|
169
|
+
{items.map((item, itemIndex) => (
|
|
170
|
+
<li key={`${index}-${itemIndex}`} className="my-1">
|
|
171
|
+
{renderInlineMarkdown(item)}
|
|
172
|
+
</li>
|
|
173
|
+
))}
|
|
174
|
+
</Tag>
|
|
175
|
+
),
|
|
176
|
+
nextIndex,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function renderHeadingNode(level: number, key: number, children: ReactNode[]) {
|
|
181
|
+
const className = "mb-2 mt-5 font-semibold leading-tight text-foreground";
|
|
182
|
+
if (level === 1) {
|
|
183
|
+
return <h1 key={key} className={className}>{children}</h1>;
|
|
184
|
+
}
|
|
185
|
+
if (level === 2) {
|
|
186
|
+
return <h2 key={key} className={className}>{children}</h2>;
|
|
187
|
+
}
|
|
188
|
+
if (level === 3) {
|
|
189
|
+
return <h3 key={key} className={className}>{children}</h3>;
|
|
190
|
+
}
|
|
191
|
+
return <h4 key={key} className={className}>{children}</h4>;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function renderMarkdownBlock(lines: string[], index: number) {
|
|
195
|
+
const trimmed = lines[index]?.trim() ?? '';
|
|
196
|
+
const fenceMatch = trimmed.match(/^```(\S*)/);
|
|
197
|
+
if (fenceMatch) {
|
|
198
|
+
return renderCodeBlock(lines, index, fenceMatch);
|
|
199
|
+
}
|
|
200
|
+
const headingMatch = trimmed.match(/^(#{1,4})\s+(.+)$/);
|
|
201
|
+
if (headingMatch) {
|
|
202
|
+
const level = Math.min(4, headingMatch[1]?.length ?? 2);
|
|
203
|
+
return {
|
|
204
|
+
node: renderHeadingNode(level, index, renderInlineMarkdown(headingMatch[2] ?? '')),
|
|
205
|
+
nextIndex: index + 1,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
if (/^[-*]\s+/.test(trimmed)) {
|
|
209
|
+
return renderListBlock(lines, index, false);
|
|
210
|
+
}
|
|
211
|
+
if (/^\d+\.\s+/.test(trimmed)) {
|
|
212
|
+
return renderListBlock(lines, index, true);
|
|
213
|
+
}
|
|
214
|
+
if (/^>\s?/.test(trimmed)) {
|
|
215
|
+
const quotes: string[] = [];
|
|
216
|
+
let nextIndex = index;
|
|
217
|
+
while (nextIndex < lines.length && /^>\s?/.test(lines[nextIndex]?.trim() ?? '')) {
|
|
218
|
+
quotes.push((lines[nextIndex] ?? '').trim().replace(/^>\s?/, ''));
|
|
219
|
+
nextIndex += 1;
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
node: (
|
|
223
|
+
<blockquote key={index} className="my-3 rounded-lg border-l-4 border-primary/50 bg-muted/60 px-3 py-2 text-muted-foreground">
|
|
224
|
+
{renderInlineMarkdown(quotes.join(' '))}
|
|
225
|
+
</blockquote>
|
|
226
|
+
),
|
|
227
|
+
nextIndex,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const paragraphLines: string[] = [];
|
|
232
|
+
let nextIndex = index;
|
|
233
|
+
while (nextIndex < lines.length) {
|
|
234
|
+
const paragraphLine = lines[nextIndex]?.trim() ?? '';
|
|
235
|
+
if (!paragraphLine || isMarkdownBlockStart(paragraphLine)) {
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
paragraphLines.push(paragraphLine);
|
|
239
|
+
nextIndex += 1;
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
node: <p key={index}>{renderInlineMarkdown(paragraphLines.join(' '))}</p>,
|
|
243
|
+
nextIndex,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function renderMarkdown(markdown: string): ReactNode[] {
|
|
248
|
+
const lines = stripYamlFrontmatter(markdown).replace(/\r\n/g, '\n').split('\n');
|
|
249
|
+
const blocks: ReactNode[] = [];
|
|
250
|
+
let index = 0;
|
|
251
|
+
while (index < lines.length) {
|
|
252
|
+
if (!(lines[index] ?? '').trim()) {
|
|
253
|
+
index += 1;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
const result = renderMarkdownBlock(lines, index);
|
|
257
|
+
blocks.push(result.node);
|
|
258
|
+
index = result.nextIndex;
|
|
259
|
+
}
|
|
260
|
+
return blocks;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function stringifyMetadataValue(value: unknown): string {
|
|
264
|
+
if (Array.isArray(value)) {
|
|
265
|
+
const values = value
|
|
266
|
+
.map((entry) => stringifyMetadataValue(entry))
|
|
267
|
+
.filter((entry) => entry.trim().length > 0);
|
|
268
|
+
return values.join(', ');
|
|
269
|
+
}
|
|
270
|
+
if (value && typeof value === 'object') {
|
|
271
|
+
return '';
|
|
272
|
+
}
|
|
273
|
+
return String(value ?? '');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function readJsonMetadata(raw: string): MetadataEntry[] {
|
|
277
|
+
try {
|
|
278
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
279
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
280
|
+
return [];
|
|
281
|
+
}
|
|
282
|
+
return Object.entries(parsed)
|
|
283
|
+
.map(([key, value]) => ({ key, value: stringifyMetadataValue(value) }))
|
|
284
|
+
.filter((entry) => entry.value.trim().length > 0);
|
|
285
|
+
} catch {
|
|
286
|
+
return [];
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function readYamlLikeMetadata(raw: string): MetadataEntry[] {
|
|
291
|
+
return raw
|
|
292
|
+
.replace(/\r\n/g, '\n')
|
|
293
|
+
.split('\n')
|
|
294
|
+
.map((line) => line.match(/^([A-Za-z0-9_.-]+):\s*(.+)$/))
|
|
295
|
+
.filter((match): match is RegExpMatchArray => Boolean(match))
|
|
296
|
+
.map((match) => ({
|
|
297
|
+
key: match[1] ?? '',
|
|
298
|
+
value: match[2]?.replace(/^["']|["']$/g, '') ?? '',
|
|
299
|
+
}))
|
|
300
|
+
.filter((entry) => entry.key && entry.value.trim().length > 0);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function readMetadata(raw: string): MetadataEntry[] {
|
|
304
|
+
const jsonEntries = readJsonMetadata(raw);
|
|
305
|
+
return jsonEntries.length > 0 ? jsonEntries : readYamlLikeMetadata(raw);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function MarketplaceDetailSkeleton() {
|
|
309
|
+
return (
|
|
310
|
+
<div className="space-y-4 p-5">
|
|
311
|
+
<section className="rounded-xl border border-border bg-card p-5">
|
|
312
|
+
<Skeleton className="h-7 w-1/2" />
|
|
313
|
+
<Skeleton className="mt-3 h-4 w-3/4" />
|
|
314
|
+
<Skeleton className="mt-4 h-4 w-full" />
|
|
315
|
+
<Skeleton className="mt-3 h-4 w-2/3" />
|
|
316
|
+
</section>
|
|
317
|
+
<section className="grid gap-4 lg:grid-cols-[minmax(220px,0.42fr)_minmax(0,1fr)]">
|
|
318
|
+
<Skeleton className="h-56 rounded-xl" />
|
|
319
|
+
<Skeleton className="h-56 rounded-xl" />
|
|
320
|
+
</section>
|
|
321
|
+
</div>
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function MetadataView({ raw }: { raw: string }) {
|
|
326
|
+
const entries = readMetadata(raw);
|
|
327
|
+
if (entries.length === 0) {
|
|
328
|
+
return <pre className="m-0 whitespace-pre-wrap break-words font-mono text-xs leading-6">{raw}</pre>;
|
|
329
|
+
}
|
|
330
|
+
return (
|
|
331
|
+
<dl className="m-0">
|
|
332
|
+
{entries.map((entry) => (
|
|
333
|
+
<div key={entry.key} className="grid grid-cols-[minmax(104px,0.34fr)_minmax(0,1fr)] gap-3 border-b border-border/70 py-2 last:border-b-0">
|
|
334
|
+
<dt className="break-words font-semibold text-primary">{entry.key}</dt>
|
|
335
|
+
<dd className="m-0 break-words text-card-foreground">{entry.value}</dd>
|
|
336
|
+
</div>
|
|
337
|
+
))}
|
|
338
|
+
</dl>
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function DetailSection({ title, children }: { title: string; children: ReactNode }) {
|
|
343
|
+
return (
|
|
344
|
+
<article className="overflow-hidden rounded-xl border border-border bg-card shadow-sm">
|
|
345
|
+
<h2 className="border-b border-border bg-muted/40 px-4 py-3 text-sm font-semibold text-card-foreground">
|
|
346
|
+
{title}
|
|
347
|
+
</h2>
|
|
348
|
+
<div className="p-4 text-sm leading-7 text-card-foreground">{children}</div>
|
|
349
|
+
</article>
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function MarketplaceDetailUnavailable() {
|
|
354
|
+
return (
|
|
355
|
+
<div className="flex h-full items-center justify-center bg-background p-6 text-center text-muted-foreground">
|
|
356
|
+
<div className="max-w-sm">
|
|
357
|
+
<AlertCircle className="mx-auto mb-3 h-7 w-7 text-muted-foreground" />
|
|
358
|
+
<h2 className="text-base font-semibold text-foreground">{t('marketplaceDetailUnavailableTitle')}</h2>
|
|
359
|
+
<p className="mt-2 text-sm leading-6">{t('marketplaceDetailUnavailableDescription')}</p>
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function MarketplaceDetailDocContent({ entry }: { entry: MarketplaceDetailDocEntry }) {
|
|
366
|
+
const metadata = entry.metadataRaw?.trim() || '-';
|
|
367
|
+
const content = entry.contentRaw?.trim() || '-';
|
|
368
|
+
const summary = entry.summary?.trim();
|
|
369
|
+
const description = entry.description?.trim();
|
|
370
|
+
const shouldShowDescription = Boolean(description) && description !== summary;
|
|
371
|
+
|
|
372
|
+
return (
|
|
373
|
+
<main className="h-full overflow-auto bg-background text-foreground custom-scrollbar">
|
|
374
|
+
<div className="mx-auto max-w-[940px] space-y-4 p-5 pb-9">
|
|
375
|
+
<section className="rounded-xl border border-border bg-card p-5 shadow-sm">
|
|
376
|
+
<div className="flex items-start gap-3">
|
|
377
|
+
<div className="mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-border bg-muted text-primary">
|
|
378
|
+
<FileText className="h-4 w-4" />
|
|
379
|
+
</div>
|
|
380
|
+
<div className="min-w-0">
|
|
381
|
+
<h1 className="break-words text-2xl font-semibold leading-tight text-foreground">{entry.title}</h1>
|
|
382
|
+
<p className="mt-2 break-words text-xs text-muted-foreground">
|
|
383
|
+
{entry.typeLabel} · {entry.spec}
|
|
384
|
+
{entry.author ? ` · ${entry.author}` : ''}
|
|
385
|
+
</p>
|
|
386
|
+
</div>
|
|
387
|
+
</div>
|
|
388
|
+
{summary ? <p className="mt-4 text-sm leading-7 text-card-foreground">{summary}</p> : null}
|
|
389
|
+
{shouldShowDescription ? <p className="mt-3 text-sm leading-7 text-card-foreground">{description}</p> : null}
|
|
390
|
+
{entry.tags && entry.tags.length > 0 ? (
|
|
391
|
+
<div className="mt-4 flex flex-wrap gap-2">
|
|
392
|
+
{entry.tags.map((tag) => (
|
|
393
|
+
<span key={tag} className="rounded-full border border-border bg-muted px-3 py-1 text-xs text-muted-foreground">
|
|
394
|
+
{tag}
|
|
395
|
+
</span>
|
|
396
|
+
))}
|
|
397
|
+
</div>
|
|
398
|
+
) : null}
|
|
399
|
+
{entry.sourceUrl ? (
|
|
400
|
+
<p className="mt-4 break-all text-xs text-muted-foreground">
|
|
401
|
+
{entry.sourceLabel ?? t('marketplaceDetailSource')}: {' '}
|
|
402
|
+
<a href={entry.sourceUrl} target="_blank" rel="noopener noreferrer" className="font-medium text-primary hover:underline">
|
|
403
|
+
{entry.sourceUrl}
|
|
404
|
+
</a>
|
|
405
|
+
</p>
|
|
406
|
+
) : null}
|
|
407
|
+
</section>
|
|
408
|
+
<section className="grid grid-cols-[repeat(auto-fit,minmax(min(300px,100%),1fr))] gap-4">
|
|
409
|
+
<DetailSection title={t('marketplaceDetailMetadata')}>
|
|
410
|
+
<MetadataView raw={metadata} />
|
|
411
|
+
</DetailSection>
|
|
412
|
+
<DetailSection title={t('marketplaceDetailContent')}>
|
|
413
|
+
<div className="marketplace-detail-markdown space-y-3 text-sm leading-7 text-card-foreground [&_a]:font-medium [&_a]:text-primary [&_a:hover]:underline">
|
|
414
|
+
{renderMarkdown(content)}
|
|
415
|
+
</div>
|
|
416
|
+
</DetailSection>
|
|
417
|
+
</section>
|
|
418
|
+
</div>
|
|
419
|
+
</main>
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function MarketplaceDetailDoc({ currentUrl }: { currentUrl: string }) {
|
|
424
|
+
const detailId = readMarketplaceDetailDocId(currentUrl);
|
|
425
|
+
const entry = useMarketplaceDetailDocStore((state) => (detailId ? state.entries[detailId] : undefined));
|
|
426
|
+
if (!detailId || !entry) {
|
|
427
|
+
return <MarketplaceDetailUnavailable />;
|
|
428
|
+
}
|
|
429
|
+
if (entry.status === 'loading') {
|
|
430
|
+
return <MarketplaceDetailSkeleton />;
|
|
431
|
+
}
|
|
432
|
+
return <MarketplaceDetailDocContent entry={entry} />;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export const MARKETPLACE_DETAIL_DOC_BROWSER_RENDERERS: DocBrowserCustomTabRenderers = {
|
|
436
|
+
[MARKETPLACE_DETAIL_TAB_KIND]: {
|
|
437
|
+
getTitle: (tab) => tab.title,
|
|
438
|
+
renderContent: ({ currentUrl }) => <MarketplaceDetailDoc currentUrl={currentUrl} />,
|
|
439
|
+
renderIcon: () => <FileText className="h-4 w-4 text-primary" />,
|
|
440
|
+
},
|
|
441
|
+
};
|
|
@@ -39,7 +39,7 @@ const mocks = vi.hoisted(() => ({
|
|
|
39
39
|
isPending: false,
|
|
40
40
|
},
|
|
41
41
|
confirm: vi.fn(),
|
|
42
|
-
|
|
42
|
+
docOpenTarget: vi.fn(),
|
|
43
43
|
}));
|
|
44
44
|
|
|
45
45
|
vi.mock("@tanstack/react-query", async (importOriginal) => {
|
|
@@ -61,7 +61,7 @@ vi.mock("@/app/components/i18n-provider", () => ({
|
|
|
61
61
|
|
|
62
62
|
vi.mock("@/shared/components/doc-browser", () => ({
|
|
63
63
|
useDocBrowser: () => ({
|
|
64
|
-
|
|
64
|
+
openTarget: mocks.docOpenTarget,
|
|
65
65
|
}),
|
|
66
66
|
}));
|
|
67
67
|
|
|
@@ -116,7 +116,7 @@ describe("McpMarketplacePage", () => {
|
|
|
116
116
|
mocks.manageMutation.mutateAsync.mockReset();
|
|
117
117
|
mocks.doctorMutation.mutateAsync.mockReset();
|
|
118
118
|
mocks.confirm.mockReset();
|
|
119
|
-
mocks.
|
|
119
|
+
mocks.docOpenTarget.mockReset();
|
|
120
120
|
mocks.itemsQuery = createItemsQuery();
|
|
121
121
|
mocks.installedQuery = createInstalledQuery();
|
|
122
122
|
});
|
|
@@ -55,66 +55,6 @@ export function findInstalledRecordForItem(
|
|
|
55
55
|
return undefined;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
function escape(value: string) {
|
|
59
|
-
return value
|
|
60
|
-
.replace(/&/g, "&")
|
|
61
|
-
.replace(/</g, "<")
|
|
62
|
-
.replace(/>/g, ">")
|
|
63
|
-
.replace(/"/g, """)
|
|
64
|
-
.replace(/'/g, "'");
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function buildDocDataUrl(
|
|
68
|
-
title: string,
|
|
69
|
-
metadata: string,
|
|
70
|
-
content: string,
|
|
71
|
-
sourceUrl?: string,
|
|
72
|
-
summary?: string,
|
|
73
|
-
): string {
|
|
74
|
-
const html = `<!doctype html>
|
|
75
|
-
<html>
|
|
76
|
-
<head>
|
|
77
|
-
<meta charset="utf-8" />
|
|
78
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
79
|
-
<title>${escape(title)}</title>
|
|
80
|
-
<style>
|
|
81
|
-
body { margin: 0; background: #f8fafc; color: #0f172a; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
|
|
82
|
-
.wrap { max-width: 980px; margin: 0 auto; padding: 28px 20px 40px; }
|
|
83
|
-
.hero { border: 1px solid #dbeafe; border-radius: 16px; background: linear-gradient(180deg, #eff6ff, #ffffff); padding: 20px; }
|
|
84
|
-
.hero h1 { margin: 0; font-size: 26px; }
|
|
85
|
-
.grid { display: grid; grid-template-columns: 280px 1fr; gap: 14px; margin-top: 16px; }
|
|
86
|
-
.card { border: 1px solid #e2e8f0; background: #fff; border-radius: 14px; overflow: hidden; }
|
|
87
|
-
.card h2 { margin: 0; padding: 12px 14px; font-size: 13px; font-weight: 700; color: #1d4ed8; border-bottom: 1px solid #e2e8f0; background: #f8fafc; }
|
|
88
|
-
.body { padding: 12px 14px; }
|
|
89
|
-
pre { margin: 0; white-space: pre-wrap; line-height: 1.7; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
|
|
90
|
-
a { color: #2563eb; text-decoration: none; }
|
|
91
|
-
@media (max-width: 860px) { .grid { grid-template-columns: 1fr; } }
|
|
92
|
-
</style>
|
|
93
|
-
</head>
|
|
94
|
-
<body>
|
|
95
|
-
<main class="wrap">
|
|
96
|
-
<section class="hero">
|
|
97
|
-
<h1>${escape(title)}</h1>
|
|
98
|
-
${summary ? `<p>${escape(summary)}</p>` : ""}
|
|
99
|
-
${sourceUrl ? `<p><a href="${escape(sourceUrl)}" target="_blank" rel="noopener noreferrer">${escape(sourceUrl)}</a></p>` : ""}
|
|
100
|
-
</section>
|
|
101
|
-
<section class="grid">
|
|
102
|
-
<article class="card">
|
|
103
|
-
<h2>Metadata</h2>
|
|
104
|
-
<div class="body"><pre>${escape(metadata)}</pre></div>
|
|
105
|
-
</article>
|
|
106
|
-
<article class="card">
|
|
107
|
-
<h2>Content</h2>
|
|
108
|
-
<div class="body"><pre>${escape(content)}</pre></div>
|
|
109
|
-
</article>
|
|
110
|
-
</section>
|
|
111
|
-
</main>
|
|
112
|
-
</body>
|
|
113
|
-
</html>`;
|
|
114
|
-
|
|
115
|
-
return `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
58
|
export function readSummary(
|
|
119
59
|
localeFallbacks: string[],
|
|
120
60
|
item?: MarketplaceItemSummary,
|
|
@@ -20,11 +20,14 @@ import {
|
|
|
20
20
|
import { buildLocaleFallbacks } from "@/features/marketplace/components/marketplace-localization";
|
|
21
21
|
import { McpMarketplaceCard } from "@/features/marketplace/components/mcp/mcp-marketplace-card";
|
|
22
22
|
import {
|
|
23
|
-
buildDocDataUrl,
|
|
24
23
|
buildInstalledRecordLookup,
|
|
25
24
|
findInstalledRecordForItem,
|
|
26
25
|
readSummary,
|
|
27
26
|
} from "@/features/marketplace/components/mcp/mcp-marketplace-doc";
|
|
27
|
+
import {
|
|
28
|
+
createMarketplaceDetailDocId,
|
|
29
|
+
openMarketplaceDetailDoc,
|
|
30
|
+
} from "@/features/marketplace/components/marketplace-detail-doc";
|
|
28
31
|
import {
|
|
29
32
|
DoctorDialog,
|
|
30
33
|
InstallDialog,
|
|
@@ -133,45 +136,77 @@ export function McpMarketplacePage() {
|
|
|
133
136
|
record?: MarketplaceInstalledRecord,
|
|
134
137
|
) => {
|
|
135
138
|
const title = item?.name ?? record?.label ?? record?.id ?? "MCP";
|
|
136
|
-
const
|
|
137
|
-
?
|
|
138
|
-
|
|
139
|
-
|
|
139
|
+
const detailId = createMarketplaceDetailDocId(
|
|
140
|
+
item ? "mcp" : "mcp-local",
|
|
141
|
+
item?.slug ?? record?.id ?? record?.spec ?? title,
|
|
142
|
+
);
|
|
140
143
|
const summary = readSummary(localeFallbacks, item, record);
|
|
141
144
|
if (!item) {
|
|
142
|
-
|
|
145
|
+
openMarketplaceDetailDoc(docBrowser, {
|
|
146
|
+
id: detailId,
|
|
143
147
|
title,
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
148
|
+
typeLabel: t("marketplaceTypeMcp"),
|
|
149
|
+
spec: record?.spec ?? "-",
|
|
150
|
+
status: "ready",
|
|
147
151
|
summary,
|
|
148
|
-
|
|
149
|
-
|
|
152
|
+
metadataRaw: JSON.stringify(record ?? {}, null, 2),
|
|
153
|
+
contentRaw: t("marketplaceInstalledLocalSummary"),
|
|
154
|
+
sourceUrl: record?.docsUrl,
|
|
155
|
+
sourceLabel: t("marketplaceDetailSource"),
|
|
156
|
+
});
|
|
150
157
|
return;
|
|
151
158
|
}
|
|
159
|
+
|
|
160
|
+
openMarketplaceDetailDoc(docBrowser, {
|
|
161
|
+
id: detailId,
|
|
162
|
+
title,
|
|
163
|
+
typeLabel: t("marketplaceTypeMcp"),
|
|
164
|
+
spec: item.install.spec,
|
|
165
|
+
status: "loading",
|
|
166
|
+
summary,
|
|
167
|
+
tags: item.tags,
|
|
168
|
+
author: item.author,
|
|
169
|
+
});
|
|
170
|
+
|
|
152
171
|
try {
|
|
153
172
|
const content = await fetchMcpMarketplaceContent(item.slug);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
173
|
+
openMarketplaceDetailDoc(
|
|
174
|
+
docBrowser,
|
|
175
|
+
{
|
|
176
|
+
id: detailId,
|
|
177
|
+
title,
|
|
178
|
+
typeLabel: t("marketplaceTypeMcp"),
|
|
179
|
+
spec: item.install.spec,
|
|
180
|
+
status: "ready",
|
|
181
|
+
summary,
|
|
182
|
+
metadataRaw: content.metadataRaw || JSON.stringify(item, null, 2),
|
|
183
|
+
contentRaw: content.bodyRaw || content.raw,
|
|
184
|
+
sourceUrl: content.sourceUrl,
|
|
185
|
+
sourceLabel: t("marketplaceDetailSource"),
|
|
186
|
+
tags: item.tags,
|
|
187
|
+
author: item.author,
|
|
188
|
+
},
|
|
189
|
+
{ activate: false },
|
|
160
190
|
);
|
|
161
|
-
docBrowser.open(url, openOptions);
|
|
162
191
|
} catch (error) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
192
|
+
openMarketplaceDetailDoc(
|
|
193
|
+
docBrowser,
|
|
194
|
+
{
|
|
195
|
+
id: detailId,
|
|
196
|
+
title,
|
|
197
|
+
typeLabel: t("marketplaceTypeMcp"),
|
|
198
|
+
spec: item.install.spec,
|
|
199
|
+
status: "error",
|
|
200
|
+
summary,
|
|
201
|
+
metadataRaw: JSON.stringify(
|
|
202
|
+
{ error: error instanceof Error ? error.message : String(error) },
|
|
203
|
+
null,
|
|
204
|
+
2,
|
|
205
|
+
),
|
|
206
|
+
contentRaw: summary,
|
|
207
|
+
},
|
|
208
|
+
{ activate: false },
|
|
173
209
|
);
|
|
174
|
-
docBrowser.open(url, openOptions);
|
|
175
210
|
}
|
|
176
211
|
};
|
|
177
212
|
|