@nextclaw/ui 0.12.9 → 0.12.10
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 +61 -0
- package/dist/assets/ChannelsList-M9FTK1Ak.js +8 -0
- package/dist/assets/DocBrowser-CH7-GxlL.js +1 -0
- package/dist/assets/{DocBrowser-6ReNjvzF.js → DocBrowser-DMfr0Oow.js} +1 -1
- package/dist/assets/{DocBrowserContext-B6SpA7Qs.js → DocBrowserContext-BXydqby-.js} +1 -1
- package/dist/assets/{LogoBadge-ByNLYg65.js → LogoBadge-hO7tY7hE.js} +1 -1
- package/dist/assets/ModelConfig-CNIgLf0e.js +1 -0
- package/dist/assets/{ProviderScopedModelInput-Da7khnBA.js → ProviderScopedModelInput-B3HWP4oz.js} +1 -1
- package/dist/assets/ProvidersList-CHjMnRhX.js +1 -0
- package/dist/assets/RuntimeConfig-psp8nMSG.js +1 -0
- package/dist/assets/SearchConfig-CSoKip1f.js +1 -0
- package/dist/assets/{SecretsConfig-D281Rotl.js → SecretsConfig-MEt6MjuD.js} +2 -2
- package/dist/assets/SessionsConfig-DifCiXwR.js +2 -0
- package/dist/assets/{app-query-client-VnFElj4E.js → app-query-client-9jNewezV.js} +1 -1
- package/dist/assets/{book-open-BdcxxoQu.js → book-open-DzdUViDm.js} +1 -1
- package/dist/assets/chat-page-CLp0UV0Y.js +58 -0
- package/dist/assets/chat-session-display-DsYHx0RZ.js +1 -0
- package/dist/assets/{chunk-JZWAC4HX-DK5HPmIK.js → chunk-JZWAC4HX-C5dEc8hV.js} +1 -1
- package/dist/assets/{client-_i4MU2bB.js → client-C-8fH7-c.js} +1 -1
- package/dist/assets/{config-DtIQwrHF.js → config-CBScxsdV.js} +1 -1
- package/dist/assets/config-split-page-BUout_Ak.js +1 -0
- package/dist/assets/{createLucideIcon-BSeTgkZW.js → createLucideIcon-dy5ie7Ox.js} +1 -1
- package/dist/assets/desktop-update-config-2BS6BMkW.js +1 -0
- package/dist/assets/{dist-ccBFUi-o.js → dist-BruyLa92.js} +1 -1
- package/dist/assets/{dist-6TrrnPCR.js → dist-Cy7_j6hA.js} +1 -1
- package/dist/assets/{download-BhDxnyvU.js → download-BD0ETkB-.js} +1 -1
- package/dist/assets/{external-link-BgErLCNT.js → external-link-kZSAO8nT.js} +1 -1
- package/dist/assets/{hash-Bl7dr_UG.js → hash-BHJC2Ovu.js} +1 -1
- package/dist/assets/{i18n-eDHeDY0n.js → i18n-CpTZLchQ.js} +1 -1
- package/dist/assets/index-mW8W2FUu.css +1 -0
- package/dist/assets/index-zDZfXoI4.js +6 -0
- package/dist/assets/{infiniteQueryBehavior-ZDS92Qpp.js → infiniteQueryBehavior-CyER9hv0.js} +1 -1
- package/dist/assets/loader-circle-Bc2gCU33.js +1 -0
- package/dist/assets/{logos-x89HbrZ4.js → logos-B7gRObP8.js} +1 -1
- package/dist/assets/marketplace-page-3qVMnF3d.js +1 -0
- package/dist/assets/marketplace-page-BhFIeQzI.js +49 -0
- package/dist/assets/mcp-marketplace-page-DYfteJ1D.js +40 -0
- package/dist/assets/{page-layout-vZnghcFy.js → page-layout-0UcO9H9Z.js} +1 -1
- package/dist/assets/play-CKDjSQFL.js +1 -0
- package/dist/assets/plus-CG0QrVY_.js +1 -0
- package/dist/assets/{refresh-ccw-DT98i__E.js → refresh-ccw-COVhNHtN.js} +1 -1
- package/dist/assets/{refresh-cw-C47QSEwg.js → refresh-cw-Bcv40SXy.js} +1 -1
- package/dist/assets/remote-access-page-CWHG-sug.js +1 -0
- package/dist/assets/{rotate-cw-JtFzpNn6.js → rotate-cw-oHMKJMC8.js} +1 -1
- package/dist/assets/{save-3S6-H3Xw.js → save-EqJPOF0G.js} +1 -1
- package/dist/assets/search-BCAlB8nz.js +1 -0
- package/dist/assets/security-config-Slh0Mayz.js +1 -0
- package/dist/assets/select-CVz0t7MF.js +41 -0
- package/dist/assets/setting-row-CbVHAuQt.js +1 -0
- package/dist/assets/skeleton-D5rdKvzy.js +1 -0
- package/dist/assets/{status-dot-vbanNPFU.js → status-dot-DpPtVzQT.js} +1 -1
- package/dist/assets/{switch-BsLtHOH-.js → switch-CM29eCAR.js} +1 -1
- package/dist/assets/{tabs-custom-D3HYMt6k.js → tabs-custom-YcZUWn3o.js} +1 -1
- package/dist/assets/tag-chip-DMXdnLcj.js +1 -0
- package/dist/assets/{trash-2-G48scll7.js → trash-2-mJT6oWa2.js} +1 -1
- package/dist/assets/{use-infinite-scroll-loader-DkNhD-42.js → use-infinite-scroll-loader-DJ1L81Dz.js} +1 -1
- package/dist/assets/{useConfirmDialog-BkvTN-vd.js → useConfirmDialog-BsVuqu1x.js} +1 -1
- package/dist/assets/{useMutation-CBWjE2uj.js → useMutation-CNcz2fgt.js} +1 -1
- package/dist/assets/x-Czwxm82I.js +1 -0
- package/dist/index.html +22 -22
- package/dist/runtime-icons/claude.ico +0 -0
- package/dist/runtime-icons/codex-openai.svg +6 -0
- package/dist/runtime-icons/hermes-agent.png +0 -0
- package/package.json +6 -6
- package/public/runtime-icons/claude.ico +0 -0
- package/public/runtime-icons/codex-openai.svg +6 -0
- package/public/runtime-icons/hermes-agent.png +0 -0
- package/src/account/components/account-panel.tsx +217 -97
- package/src/account/managers/account.manager.ts +3 -2
- package/src/api/chat-session-type.types.ts +7 -0
- package/src/api/runtime-control.types.ts +8 -0
- package/src/api/types.ts +8 -0
- package/src/app.tsx +221 -57
- package/src/components/agents/agent-dialogs.tsx +499 -0
- package/src/components/agents/agents-page.test.tsx +238 -0
- package/src/components/agents/agents-page.tsx +435 -0
- package/src/components/chat/ChatSidebar.tsx +11 -35
- package/src/components/chat/chat-conversation-panel.test.tsx +20 -0
- package/src/components/chat/chat-conversation-panel.tsx +83 -13
- package/src/components/chat/chat-page-shell.tsx +19 -13
- package/src/components/chat/chat-session-type-option-item.test.tsx +46 -0
- package/src/components/chat/chat-session-type-option-item.tsx +68 -0
- package/src/components/chat/chat-session-workspace-file-preview.test.tsx +87 -0
- package/src/components/chat/chat-session-workspace-file-preview.tsx +14 -43
- package/src/components/chat/chat-session-workspace-panel-nav.tsx +8 -2
- package/src/components/chat/chat-sidebar-project-groups.tsx +11 -36
- package/src/components/chat/ncp/__tests__/ncp-session-adapter.cancelled-tool.test.ts +77 -0
- package/src/components/chat/ncp/ncp-chat-page.tsx +2 -0
- package/src/components/chat/ncp/ncp-session-adapter.test.ts +1 -0
- package/src/components/chat/ncp/ncp-session-adapter.ts +3 -0
- package/src/components/chat/ncp/page/ncp-chat-derived-state.ts +10 -4
- package/src/components/chat/stores/chat-input.store.ts +2 -1
- package/src/components/chat/stores/chat-thread.store.ts +3 -1
- package/src/components/chat/useChatSessionTypeState.ts +10 -1
- package/src/components/chat/workspace/chat-session-workspace-file-breadcrumbs.tsx +86 -0
- package/src/components/common/BrandHeader.tsx +3 -1
- package/src/components/common/session-context-icon.tsx +15 -2
- package/src/components/common/{TagInput.tsx → tag-input.tsx} +25 -17
- package/src/components/config/ChannelForm.test.tsx +89 -3
- package/src/components/config/ChannelForm.tsx +157 -188
- package/src/components/config/ChannelsList.test.tsx +163 -119
- package/src/components/config/ChannelsList.tsx +90 -101
- package/src/components/config/ProviderForm.tsx +108 -146
- package/src/components/config/ProvidersList.tsx +100 -123
- package/src/components/config/SearchConfig.tsx +423 -393
- package/src/components/config/channel-form-fields-section.tsx +70 -37
- package/src/components/config/config-split-page.tsx +109 -0
- package/src/components/config/provider-enabled-field.tsx +17 -10
- package/src/components/config/runtime-control-card.test.tsx +56 -0
- package/src/components/config/runtime-control-card.tsx +25 -0
- package/src/components/config/runtime-presence-card.tsx +93 -79
- package/src/components/layout/AppLayout.tsx +25 -37
- package/src/components/layout/app-layout.test.tsx +46 -14
- package/src/components/layout/runtime-status-entry.test.tsx +157 -0
- package/src/components/layout/runtime-status-entry.tsx +143 -0
- package/src/components/marketplace/marketplace-detail-doc.ts +93 -0
- package/src/components/marketplace/marketplace-list-card.tsx +288 -0
- package/src/components/marketplace/marketplace-page-data.ts +129 -0
- package/src/components/marketplace/marketplace-page.test.tsx +339 -0
- package/src/components/marketplace/marketplace-page.tsx +596 -0
- package/src/components/marketplace/mcp/mcp-marketplace-card.tsx +128 -0
- package/src/components/marketplace/mcp/mcp-marketplace-dialogs.tsx +191 -0
- package/src/components/marketplace/mcp/mcp-marketplace-doc.ts +152 -0
- package/src/components/marketplace/mcp/mcp-marketplace-page.test.tsx +223 -0
- package/src/components/marketplace/mcp/mcp-marketplace-page.tsx +414 -0
- package/src/components/remote/remote-access-page.test.tsx +105 -0
- package/src/components/remote/remote-access-page.tsx +248 -0
- package/src/components/ui/notice-card.tsx +129 -0
- package/src/components/ui/setting-row.tsx +51 -0
- package/src/components/ui/tag-chip.tsx +39 -0
- package/src/components/ui/textarea.tsx +19 -0
- package/src/hooks/useConfig.ts +2 -1
- package/src/index.css +24 -0
- package/src/lib/app-resource-uri.test.ts +20 -0
- package/src/lib/app-resource-uri.ts +29 -0
- package/src/lib/i18n.remote.ts +1 -1
- package/src/lib/i18n.runtime-control.ts +31 -0
- package/src/lib/i18n.ts +5 -8
- package/src/lib/session-context.utils.test.ts +71 -0
- package/src/lib/session-context.utils.ts +28 -3
- package/src/lib/session-project/workspace-file-breadcrumb.test.ts +83 -0
- package/src/lib/session-project/workspace-file-breadcrumb.ts +188 -0
- package/dist/assets/ChannelsList-Ita2Zm1_.js +0 -8
- package/dist/assets/DocBrowser-BNwbPHf4.js +0 -1
- package/dist/assets/MarketplacePage-CjX2MWww.js +0 -1
- package/dist/assets/MarketplacePage-D0sDlYX4.js +0 -49
- package/dist/assets/McpMarketplacePage-BGKJm1sJ.js +0 -40
- package/dist/assets/ModelConfig-BzZenCH-.js +0 -1
- package/dist/assets/ProvidersList-BbVzRxjY.js +0 -1
- package/dist/assets/RemoteAccessPage-BaDH_X1Q.js +0 -1
- package/dist/assets/RuntimeConfig-F_XKGgLm.js +0 -1
- package/dist/assets/SearchConfig-BGkzXQP-.js +0 -1
- package/dist/assets/SessionsConfig-ChHQ7M5c.js +0 -2
- package/dist/assets/chat-page-Doe0yTtB.js +0 -58
- package/dist/assets/chat-session-display-cw78aiI_.js +0 -1
- package/dist/assets/config-layout-CHs0mAaR.js +0 -1
- package/dist/assets/desktop-update-config-Dpcf4BKG.js +0 -1
- package/dist/assets/index-CF9xve0E.js +0 -6
- package/dist/assets/index-FgA52VBt.css +0 -1
- package/dist/assets/loader-circle-ACM1s51e.js +0 -1
- package/dist/assets/play-CFUwCA2E.js +0 -1
- package/dist/assets/plus-rYsv72JG.js +0 -1
- package/dist/assets/popover-Bg1VoTZ6.js +0 -1
- package/dist/assets/search-3kFR_zh9.js +0 -1
- package/dist/assets/security-config-BWaiARNk.js +0 -1
- package/dist/assets/select-DJ2MUjBB.js +0 -41
- package/dist/assets/skeleton-ByQepn0M.js +0 -1
- package/dist/assets/x-ByDbItbq.js +0 -1
- package/src/components/agents/AgentDialogs.tsx +0 -400
- package/src/components/agents/AgentsPage.test.tsx +0 -217
- package/src/components/agents/AgentsPage.tsx +0 -352
- package/src/components/config/config-layout.ts +0 -10
- package/src/components/marketplace/MarketplacePage.test.tsx +0 -322
- package/src/components/marketplace/MarketplacePage.tsx +0 -827
- package/src/components/marketplace/mcp/McpMarketplacePage.test.tsx +0 -208
- package/src/components/marketplace/mcp/McpMarketplacePage.tsx +0 -580
- package/src/components/remote/RemoteAccessPage.test.tsx +0 -103
- package/src/components/remote/RemoteAccessPage.tsx +0 -144
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
3
|
+
import { useRuntimeControl } from '@/hooks/use-runtime-control';
|
|
4
|
+
import { runtimeControlManager } from '@/runtime-control/runtime-control.manager';
|
|
5
|
+
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
|
6
|
+
import { t } from '@/lib/i18n';
|
|
7
|
+
import { cn } from '@/lib/utils';
|
|
8
|
+
import { toast } from 'sonner';
|
|
9
|
+
|
|
10
|
+
type RuntimeStatusTone = 'healthy' | 'attention' | 'inactive';
|
|
11
|
+
|
|
12
|
+
const runtimeStatusToneStyles: Record<RuntimeStatusTone, string> = {
|
|
13
|
+
healthy: 'bg-emerald-500 shadow-[0_0_0_3px_rgba(16,185,129,0.14)]',
|
|
14
|
+
attention: 'bg-amber-400 shadow-[0_0_0_3px_rgba(251,191,36,0.16)]',
|
|
15
|
+
inactive: 'bg-gray-300 shadow-[0_0_0_3px_rgba(156,163,175,0.12)]'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type RuntimeStatusSummary = {
|
|
19
|
+
actionLabel: string | null;
|
|
20
|
+
description: string;
|
|
21
|
+
reasonLines: string[];
|
|
22
|
+
title: string;
|
|
23
|
+
tone: RuntimeStatusTone;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function buildRuntimeStatusSummary(
|
|
27
|
+
view: ReturnType<typeof useRuntimeControl>['data']
|
|
28
|
+
): RuntimeStatusSummary {
|
|
29
|
+
if (!view) {
|
|
30
|
+
return {
|
|
31
|
+
tone: 'inactive',
|
|
32
|
+
title: t('runtimeStatusLoadingTitle'),
|
|
33
|
+
description: t('runtimeStatusLoadingDescription'),
|
|
34
|
+
reasonLines: [],
|
|
35
|
+
actionLabel: null
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (view.pendingRestart) {
|
|
40
|
+
return {
|
|
41
|
+
tone: 'attention',
|
|
42
|
+
title: t('runtimeStatusPendingRestartTitle'),
|
|
43
|
+
description: t('runtimeStatusPendingRestartDescription'),
|
|
44
|
+
reasonLines:
|
|
45
|
+
view.pendingRestart.changedPaths.length > 0
|
|
46
|
+
? view.pendingRestart.changedPaths.map((path) =>
|
|
47
|
+
t('runtimeStatusPendingRestartReasonItem').replace('{path}', path)
|
|
48
|
+
)
|
|
49
|
+
: [view.pendingRestart.message],
|
|
50
|
+
actionLabel: view.canRestartService.available ? t('runtimeStatusRestartAction') : null
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
tone: view.lifecycle === 'healthy' ? 'healthy' : 'inactive',
|
|
56
|
+
title: t('runtimeStatusHealthyTitle'),
|
|
57
|
+
description: t('runtimeStatusHealthyDescription'),
|
|
58
|
+
reasonLines: [],
|
|
59
|
+
actionLabel: null
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function RuntimeStatusEntry() {
|
|
64
|
+
const queryClient = useQueryClient();
|
|
65
|
+
const runtimeControlQuery = useRuntimeControl();
|
|
66
|
+
const [isRestarting, setIsRestarting] = useState(false);
|
|
67
|
+
const runtimeView = runtimeControlQuery.data;
|
|
68
|
+
const summary = buildRuntimeStatusSummary(runtimeView);
|
|
69
|
+
const title = runtimeControlQuery.isError ? t('runtimeControlLoadFailed') : summary.title;
|
|
70
|
+
const description =
|
|
71
|
+
runtimeControlQuery.isError && runtimeControlQuery.error instanceof Error
|
|
72
|
+
? runtimeControlQuery.error.message
|
|
73
|
+
: summary.description;
|
|
74
|
+
const canRestart = Boolean(runtimeView?.pendingRestart && runtimeView.canRestartService.available);
|
|
75
|
+
|
|
76
|
+
const handleRestart = async () => {
|
|
77
|
+
if (!canRestart) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
setIsRestarting(true);
|
|
81
|
+
try {
|
|
82
|
+
const result = await runtimeControlManager.controlService('restart-service');
|
|
83
|
+
toast.success(result.message);
|
|
84
|
+
await queryClient.invalidateQueries({ queryKey: ['runtime-control'] });
|
|
85
|
+
} catch (error) {
|
|
86
|
+
const message = error instanceof Error ? error.message : t('runtimeControlActionFailed');
|
|
87
|
+
toast.error(`${t('runtimeControlActionFailed')}: ${message}`);
|
|
88
|
+
} finally {
|
|
89
|
+
setIsRestarting(false);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<Popover>
|
|
95
|
+
<PopoverTrigger asChild>
|
|
96
|
+
<button
|
|
97
|
+
type="button"
|
|
98
|
+
className="inline-flex items-center justify-center rounded-full p-0.5 transition-transform hover:scale-105"
|
|
99
|
+
aria-label={title}
|
|
100
|
+
title={title}
|
|
101
|
+
data-testid="runtime-status-entry"
|
|
102
|
+
>
|
|
103
|
+
<span className={cn('h-2.5 w-2.5 rounded-full', runtimeStatusToneStyles[summary.tone])} />
|
|
104
|
+
</button>
|
|
105
|
+
</PopoverTrigger>
|
|
106
|
+
<PopoverContent
|
|
107
|
+
align="start"
|
|
108
|
+
sideOffset={10}
|
|
109
|
+
className="w-[290px] space-y-3 rounded-2xl border border-gray-200 bg-white p-4"
|
|
110
|
+
>
|
|
111
|
+
<div className="space-y-1">
|
|
112
|
+
<div className="text-sm font-semibold text-gray-900">{title}</div>
|
|
113
|
+
<p className="text-xs leading-5 text-gray-600">{description}</p>
|
|
114
|
+
</div>
|
|
115
|
+
{summary.reasonLines.length > 0 ? (
|
|
116
|
+
<div className="space-y-2">
|
|
117
|
+
{summary.reasonLines.map((reason) => (
|
|
118
|
+
<div
|
|
119
|
+
key={reason}
|
|
120
|
+
className="rounded-xl border border-amber-200 bg-amber-50 px-3 py-2 text-xs leading-5 text-amber-900"
|
|
121
|
+
>
|
|
122
|
+
{reason}
|
|
123
|
+
</div>
|
|
124
|
+
))}
|
|
125
|
+
</div>
|
|
126
|
+
) : null}
|
|
127
|
+
{summary.actionLabel ? (
|
|
128
|
+
<div className="flex items-center justify-between border-t border-gray-100 pt-1">
|
|
129
|
+
<span className="text-[11px] text-gray-500">{t('runtimeStatusActionHint')}</span>
|
|
130
|
+
<button
|
|
131
|
+
type="button"
|
|
132
|
+
onClick={() => void handleRestart()}
|
|
133
|
+
disabled={isRestarting}
|
|
134
|
+
className="text-sm font-semibold text-sky-600 transition-colors hover:text-sky-700 disabled:text-gray-400"
|
|
135
|
+
>
|
|
136
|
+
{isRestarting ? t('runtimeStatusRestartingAction') : summary.actionLabel}
|
|
137
|
+
</button>
|
|
138
|
+
</div>
|
|
139
|
+
) : null}
|
|
140
|
+
</PopoverContent>
|
|
141
|
+
</Popover>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
function escapeHtml(text: string): string {
|
|
2
|
+
return text
|
|
3
|
+
.replace(/&/g, "&")
|
|
4
|
+
.replace(/</g, "<")
|
|
5
|
+
.replace(/>/g, ">")
|
|
6
|
+
.replace(/"/g, """)
|
|
7
|
+
.replace(/'/g, "'");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function buildGenericDetailDataUrl(params: {
|
|
11
|
+
title: string;
|
|
12
|
+
typeLabel: string;
|
|
13
|
+
spec: string;
|
|
14
|
+
summary?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
metadataRaw?: string;
|
|
17
|
+
contentRaw?: string;
|
|
18
|
+
sourceUrl?: string;
|
|
19
|
+
sourceLabel?: string;
|
|
20
|
+
tags?: string[];
|
|
21
|
+
author?: string;
|
|
22
|
+
}): string {
|
|
23
|
+
const {
|
|
24
|
+
title,
|
|
25
|
+
typeLabel,
|
|
26
|
+
spec,
|
|
27
|
+
summary: rawSummary,
|
|
28
|
+
description: rawDescription,
|
|
29
|
+
metadataRaw,
|
|
30
|
+
contentRaw,
|
|
31
|
+
sourceUrl,
|
|
32
|
+
sourceLabel,
|
|
33
|
+
tags,
|
|
34
|
+
author,
|
|
35
|
+
} = params;
|
|
36
|
+
const metadata = metadataRaw?.trim() || "-";
|
|
37
|
+
const content = contentRaw?.trim() || "-";
|
|
38
|
+
const summary = rawSummary?.trim();
|
|
39
|
+
const description = rawDescription?.trim();
|
|
40
|
+
const shouldShowDescription = Boolean(description) && description !== summary;
|
|
41
|
+
|
|
42
|
+
const html = `<!doctype html>
|
|
43
|
+
<html>
|
|
44
|
+
<head>
|
|
45
|
+
<meta charset="utf-8" />
|
|
46
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
47
|
+
<title>${escapeHtml(title)}</title>
|
|
48
|
+
<style>
|
|
49
|
+
:root { color-scheme: light; }
|
|
50
|
+
body { margin: 0; background: #f7f9fc; color: #0f172a; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
|
|
51
|
+
.wrap { max-width: 980px; margin: 0 auto; padding: 28px 20px 40px; }
|
|
52
|
+
.hero { border: 1px solid #dbeafe; border-radius: 16px; background: linear-gradient(180deg, #eff6ff, #ffffff); padding: 20px; box-shadow: 0 6px 20px rgba(30, 64, 175, 0.08); }
|
|
53
|
+
.hero h1 { margin: 0; font-size: 26px; }
|
|
54
|
+
.meta { margin-top: 8px; color: #475569; font-size: 13px; overflow-wrap: anywhere; word-break: break-word; }
|
|
55
|
+
.summary { margin-top: 14px; font-size: 14px; line-height: 1.7; color: #334155; }
|
|
56
|
+
.grid { display: grid; grid-template-columns: 260px 1fr; gap: 14px; margin-top: 16px; }
|
|
57
|
+
.card { border: 1px solid #e2e8f0; background: #fff; border-radius: 14px; overflow: hidden; }
|
|
58
|
+
.card h2 { margin: 0; padding: 12px 14px; font-size: 13px; font-weight: 700; color: #1d4ed8; border-bottom: 1px solid #e2e8f0; background: #f8fafc; }
|
|
59
|
+
.card .body { padding: 12px 14px; font-size: 13px; color: #334155; line-height: 1.7; }
|
|
60
|
+
.code { white-space: pre-wrap; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; line-height: 1.6; margin: 0; }
|
|
61
|
+
.tags { margin-top: 10px; }
|
|
62
|
+
.tag { display: inline-block; margin: 0 6px 6px 0; padding: 4px 9px; border-radius: 999px; background: #e0e7ff; color: #3730a3; font-size: 11px; }
|
|
63
|
+
.source { color: #2563eb; text-decoration: none; overflow-wrap: anywhere; word-break: break-all; }
|
|
64
|
+
@media (max-width: 860px) { .grid { grid-template-columns: 1fr; } }
|
|
65
|
+
</style>
|
|
66
|
+
</head>
|
|
67
|
+
<body>
|
|
68
|
+
<main class="wrap">
|
|
69
|
+
<section class="hero">
|
|
70
|
+
<h1>${escapeHtml(title)}</h1>
|
|
71
|
+
<div class="meta">${escapeHtml(typeLabel)} · ${escapeHtml(spec)}${author ? ` · ${escapeHtml(author)}` : ""}</div>
|
|
72
|
+
${summary ? `<p class="summary">${escapeHtml(summary)}</p>` : ""}
|
|
73
|
+
${shouldShowDescription ? `<p class="summary">${escapeHtml(description as string)}</p>` : ""}
|
|
74
|
+
${tags && tags.length > 0 ? `<div class="tags">${tags.map((tag) => `<span class="tag">${escapeHtml(tag)}</span>`).join("")}</div>` : ""}
|
|
75
|
+
${sourceUrl ? `<p class="meta" style="margin-top:12px;">${escapeHtml(sourceLabel ?? "Source")}: <a class="source" href="${escapeHtml(sourceUrl)}" target="_blank" rel="noopener noreferrer">${escapeHtml(sourceUrl)}</a></p>` : ""}
|
|
76
|
+
</section>
|
|
77
|
+
|
|
78
|
+
<section class="grid">
|
|
79
|
+
<article class="card">
|
|
80
|
+
<h2>Metadata</h2>
|
|
81
|
+
<div class="body"><pre class="code">${escapeHtml(metadata)}</pre></div>
|
|
82
|
+
</article>
|
|
83
|
+
<article class="card">
|
|
84
|
+
<h2>Content</h2>
|
|
85
|
+
<div class="body"><pre class="code">${escapeHtml(content)}</pre></div>
|
|
86
|
+
</article>
|
|
87
|
+
</section>
|
|
88
|
+
</main>
|
|
89
|
+
</body>
|
|
90
|
+
</html>`;
|
|
91
|
+
|
|
92
|
+
return `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;
|
|
93
|
+
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MarketplaceInstalledRecord,
|
|
3
|
+
MarketplaceItemSummary,
|
|
4
|
+
MarketplaceManageAction,
|
|
5
|
+
} from "@/api/types";
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import {
|
|
8
|
+
Tooltip,
|
|
9
|
+
TooltipContent,
|
|
10
|
+
TooltipProvider,
|
|
11
|
+
TooltipTrigger,
|
|
12
|
+
} from "@/components/ui/tooltip";
|
|
13
|
+
import { t } from "@/lib/i18n";
|
|
14
|
+
import { cn } from "@/lib/utils";
|
|
15
|
+
import {
|
|
16
|
+
buildLocaleFallbacks,
|
|
17
|
+
pickLocalizedText,
|
|
18
|
+
} from "@/components/marketplace/marketplace-localization";
|
|
19
|
+
|
|
20
|
+
export type InstallState = {
|
|
21
|
+
installingSpecs: ReadonlySet<string>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type ManageState = {
|
|
25
|
+
actionsByTarget: ReadonlyMap<string, MarketplaceManageAction>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const ITEM_ICON_COLORS = [
|
|
29
|
+
"bg-amber-600",
|
|
30
|
+
"bg-orange-500",
|
|
31
|
+
"bg-yellow-600",
|
|
32
|
+
"bg-emerald-600",
|
|
33
|
+
"bg-teal-600",
|
|
34
|
+
"bg-cyan-600",
|
|
35
|
+
"bg-stone-600",
|
|
36
|
+
"bg-rose-500",
|
|
37
|
+
"bg-violet-500",
|
|
38
|
+
] as const;
|
|
39
|
+
|
|
40
|
+
function getAvatarColor(text: string) {
|
|
41
|
+
let hash = 0;
|
|
42
|
+
for (let i = 0; i < text.length; i++) {
|
|
43
|
+
hash = text.charCodeAt(i) + ((hash << 5) - hash);
|
|
44
|
+
}
|
|
45
|
+
return ITEM_ICON_COLORS[Math.abs(hash) % ITEM_ICON_COLORS.length];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function ItemIcon({ name, fallback }: { name?: string; fallback: string }) {
|
|
49
|
+
const displayName = name || fallback;
|
|
50
|
+
const letters = displayName.substring(0, 2).toUpperCase();
|
|
51
|
+
const colorClass = getAvatarColor(displayName);
|
|
52
|
+
return (
|
|
53
|
+
<div
|
|
54
|
+
className={cn(
|
|
55
|
+
"flex h-10 w-10 shrink-0 items-center justify-center rounded-xl text-sm font-semibold text-white",
|
|
56
|
+
colorClass,
|
|
57
|
+
)}
|
|
58
|
+
>
|
|
59
|
+
{letters}
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function MarketplaceListCardMeta(props: {
|
|
65
|
+
title: string;
|
|
66
|
+
spec: string;
|
|
67
|
+
summary: string;
|
|
68
|
+
}) {
|
|
69
|
+
const { title, spec, summary } = props;
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<TooltipProvider delayDuration={400}>
|
|
73
|
+
<Tooltip>
|
|
74
|
+
<TooltipTrigger asChild>
|
|
75
|
+
<div className="truncate text-[14px] font-semibold leading-tight text-gray-900">
|
|
76
|
+
{title}
|
|
77
|
+
</div>
|
|
78
|
+
</TooltipTrigger>
|
|
79
|
+
<TooltipContent className="max-w-[300px] text-xs">
|
|
80
|
+
{title}
|
|
81
|
+
</TooltipContent>
|
|
82
|
+
</Tooltip>
|
|
83
|
+
|
|
84
|
+
<div className="mb-1.5 mt-0.5 flex items-center gap-1.5">
|
|
85
|
+
{spec ? (
|
|
86
|
+
<Tooltip>
|
|
87
|
+
<TooltipTrigger asChild>
|
|
88
|
+
<span className="max-w-full truncate font-mono text-[11px] text-gray-400">
|
|
89
|
+
{spec}
|
|
90
|
+
</span>
|
|
91
|
+
</TooltipTrigger>
|
|
92
|
+
<TooltipContent className="max-w-[300px] break-all font-mono text-xs">
|
|
93
|
+
{spec}
|
|
94
|
+
</TooltipContent>
|
|
95
|
+
</Tooltip>
|
|
96
|
+
) : null}
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<Tooltip>
|
|
100
|
+
<TooltipTrigger asChild>
|
|
101
|
+
<p className="line-clamp-1 text-left text-[12px] leading-relaxed text-gray-500/90 transition-colors">
|
|
102
|
+
{summary}
|
|
103
|
+
</p>
|
|
104
|
+
</TooltipTrigger>
|
|
105
|
+
{summary ? (
|
|
106
|
+
<TooltipContent className="max-w-[400px] text-xs leading-relaxed">
|
|
107
|
+
{summary}
|
|
108
|
+
</TooltipContent>
|
|
109
|
+
) : null}
|
|
110
|
+
</Tooltip>
|
|
111
|
+
</TooltipProvider>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function readToggleLabel(
|
|
116
|
+
busyAction: MarketplaceManageAction | undefined,
|
|
117
|
+
isDisabled: boolean,
|
|
118
|
+
) {
|
|
119
|
+
if (busyAction && busyAction !== "uninstall") {
|
|
120
|
+
return busyAction === "enable"
|
|
121
|
+
? t("marketplaceEnabling")
|
|
122
|
+
: t("marketplaceDisabling");
|
|
123
|
+
}
|
|
124
|
+
return isDisabled ? t("marketplaceEnable") : t("marketplaceDisable");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function MarketplaceListCardActions(props: {
|
|
128
|
+
item?: MarketplaceItemSummary;
|
|
129
|
+
record?: MarketplaceInstalledRecord;
|
|
130
|
+
isInstalling: boolean;
|
|
131
|
+
busyAction: MarketplaceManageAction | undefined;
|
|
132
|
+
busyForRecord: boolean;
|
|
133
|
+
isDisabled: boolean;
|
|
134
|
+
onInstall: (item: MarketplaceItemSummary) => void;
|
|
135
|
+
onManage: (
|
|
136
|
+
action: MarketplaceManageAction,
|
|
137
|
+
record: MarketplaceInstalledRecord,
|
|
138
|
+
) => void;
|
|
139
|
+
}) {
|
|
140
|
+
const {
|
|
141
|
+
item,
|
|
142
|
+
record,
|
|
143
|
+
isInstalling,
|
|
144
|
+
busyAction,
|
|
145
|
+
busyForRecord,
|
|
146
|
+
isDisabled,
|
|
147
|
+
onInstall,
|
|
148
|
+
onManage,
|
|
149
|
+
} = props;
|
|
150
|
+
const pluginRecord = record?.type === "plugin" ? record : undefined;
|
|
151
|
+
const canUninstall =
|
|
152
|
+
(record?.type === "plugin" && record.origin !== "bundled") ||
|
|
153
|
+
(record?.type === "skill" && record.source === "workspace");
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<div className="flex h-full shrink-0 items-center">
|
|
157
|
+
{item && !record ? (
|
|
158
|
+
<Button
|
|
159
|
+
type="button"
|
|
160
|
+
size="sm"
|
|
161
|
+
variant="primary"
|
|
162
|
+
onClick={(event) => {
|
|
163
|
+
event.stopPropagation();
|
|
164
|
+
onInstall(item);
|
|
165
|
+
}}
|
|
166
|
+
disabled={isInstalling}
|
|
167
|
+
className="rounded-xl"
|
|
168
|
+
>
|
|
169
|
+
{isInstalling ? t("marketplaceInstalling") : t("marketplaceInstall")}
|
|
170
|
+
</Button>
|
|
171
|
+
) : null}
|
|
172
|
+
|
|
173
|
+
{pluginRecord ? (
|
|
174
|
+
<Button
|
|
175
|
+
type="button"
|
|
176
|
+
size="sm"
|
|
177
|
+
variant="outline"
|
|
178
|
+
disabled={busyForRecord}
|
|
179
|
+
onClick={(event) => {
|
|
180
|
+
event.stopPropagation();
|
|
181
|
+
onManage(isDisabled ? "enable" : "disable", pluginRecord);
|
|
182
|
+
}}
|
|
183
|
+
className="rounded-xl border-gray-200/80 text-gray-600"
|
|
184
|
+
>
|
|
185
|
+
{readToggleLabel(busyAction, isDisabled)}
|
|
186
|
+
</Button>
|
|
187
|
+
) : null}
|
|
188
|
+
|
|
189
|
+
{record && canUninstall ? (
|
|
190
|
+
<Button
|
|
191
|
+
type="button"
|
|
192
|
+
size="sm"
|
|
193
|
+
variant="outline"
|
|
194
|
+
disabled={busyForRecord}
|
|
195
|
+
onClick={(event) => {
|
|
196
|
+
event.stopPropagation();
|
|
197
|
+
onManage("uninstall", record);
|
|
198
|
+
}}
|
|
199
|
+
className="rounded-xl border-rose-100 text-rose-500 hover:border-rose-200 hover:bg-rose-50 hover:text-rose-600"
|
|
200
|
+
>
|
|
201
|
+
{busyAction === "uninstall"
|
|
202
|
+
? t("marketplaceRemoving")
|
|
203
|
+
: t("marketplaceUninstall")}
|
|
204
|
+
</Button>
|
|
205
|
+
) : null}
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function MarketplaceListCard(props: {
|
|
211
|
+
item?: MarketplaceItemSummary;
|
|
212
|
+
record?: MarketplaceInstalledRecord;
|
|
213
|
+
language: string;
|
|
214
|
+
installState: InstallState;
|
|
215
|
+
manageState: ManageState;
|
|
216
|
+
onOpen: () => void;
|
|
217
|
+
onInstall: (item: MarketplaceItemSummary) => void;
|
|
218
|
+
onManage: (
|
|
219
|
+
action: MarketplaceManageAction,
|
|
220
|
+
record: MarketplaceInstalledRecord,
|
|
221
|
+
) => void;
|
|
222
|
+
}) {
|
|
223
|
+
const {
|
|
224
|
+
item,
|
|
225
|
+
record,
|
|
226
|
+
language,
|
|
227
|
+
installState,
|
|
228
|
+
manageState,
|
|
229
|
+
onOpen,
|
|
230
|
+
onInstall,
|
|
231
|
+
onManage,
|
|
232
|
+
} = props;
|
|
233
|
+
const localeFallbacks = buildLocaleFallbacks(language);
|
|
234
|
+
const title =
|
|
235
|
+
item?.name ??
|
|
236
|
+
record?.label ??
|
|
237
|
+
record?.id ??
|
|
238
|
+
record?.spec ??
|
|
239
|
+
t("marketplaceUnknownItem");
|
|
240
|
+
const summary =
|
|
241
|
+
pickLocalizedText(item?.summaryI18n, item?.summary, localeFallbacks) ||
|
|
242
|
+
(record ? t("marketplaceInstalledLocalSummary") : "");
|
|
243
|
+
const spec = item?.install.spec ?? record?.spec ?? "";
|
|
244
|
+
const targetId = record?.id || record?.spec;
|
|
245
|
+
const busyAction = targetId
|
|
246
|
+
? manageState.actionsByTarget.get(targetId)
|
|
247
|
+
: undefined;
|
|
248
|
+
const busyForRecord = Boolean(busyAction);
|
|
249
|
+
const isDisabled = record
|
|
250
|
+
? record.enabled === false || record.runtimeStatus === "disabled"
|
|
251
|
+
: false;
|
|
252
|
+
const installSpec = item?.install.spec;
|
|
253
|
+
const isInstalling =
|
|
254
|
+
typeof installSpec === "string" &&
|
|
255
|
+
installState.installingSpecs.has(installSpec);
|
|
256
|
+
|
|
257
|
+
return (
|
|
258
|
+
<article
|
|
259
|
+
onClick={onOpen}
|
|
260
|
+
className="group flex cursor-pointer items-start justify-between gap-3.5 rounded-2xl border border-gray-200/40 bg-white px-5 py-4 shadow-sm transition-all hover:border-blue-300/80 hover:shadow-md"
|
|
261
|
+
>
|
|
262
|
+
<div className="flex h-full min-w-0 flex-1 items-start gap-3">
|
|
263
|
+
<ItemIcon
|
|
264
|
+
name={title}
|
|
265
|
+
fallback={spec || t("marketplaceTypeExtension")}
|
|
266
|
+
/>
|
|
267
|
+
<div className="flex h-full min-w-0 flex-1 flex-col justify-center">
|
|
268
|
+
<MarketplaceListCardMeta
|
|
269
|
+
title={title}
|
|
270
|
+
spec={spec}
|
|
271
|
+
summary={summary}
|
|
272
|
+
/>
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
|
|
276
|
+
<MarketplaceListCardActions
|
|
277
|
+
item={item}
|
|
278
|
+
record={record}
|
|
279
|
+
isInstalling={isInstalling}
|
|
280
|
+
busyAction={busyAction}
|
|
281
|
+
busyForRecord={busyForRecord}
|
|
282
|
+
isDisabled={isDisabled}
|
|
283
|
+
onInstall={onInstall}
|
|
284
|
+
onManage={onManage}
|
|
285
|
+
/>
|
|
286
|
+
</article>
|
|
287
|
+
);
|
|
288
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MarketplaceInstalledRecord,
|
|
3
|
+
MarketplaceItemSummary,
|
|
4
|
+
} from "@/api/types";
|
|
5
|
+
import { pickLocalizedText } from "@/components/marketplace/marketplace-localization";
|
|
6
|
+
|
|
7
|
+
export type InstalledRenderEntry = {
|
|
8
|
+
key: string;
|
|
9
|
+
record: MarketplaceInstalledRecord;
|
|
10
|
+
item?: MarketplaceItemSummary;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function normalizeMarketplaceKey(value: string | undefined): string {
|
|
14
|
+
return (value ?? "").trim().toLowerCase();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function toLookupKey(
|
|
18
|
+
type: MarketplaceItemSummary["type"],
|
|
19
|
+
value: string | undefined,
|
|
20
|
+
): string {
|
|
21
|
+
const normalized = normalizeMarketplaceKey(value);
|
|
22
|
+
return normalized.length > 0 ? `${type}:${normalized}` : "";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function buildCatalogLookup(
|
|
26
|
+
items: MarketplaceItemSummary[],
|
|
27
|
+
): Map<string, MarketplaceItemSummary> {
|
|
28
|
+
const lookup = new Map<string, MarketplaceItemSummary>();
|
|
29
|
+
|
|
30
|
+
for (const item of items) {
|
|
31
|
+
const candidates = [item.install.spec, item.slug, item.id];
|
|
32
|
+
for (const candidate of candidates) {
|
|
33
|
+
const lookupKey = toLookupKey(item.type, candidate);
|
|
34
|
+
if (!lookupKey || lookup.has(lookupKey)) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
lookup.set(lookupKey, item);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return lookup;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function buildInstalledRecordLookup(
|
|
45
|
+
records: MarketplaceInstalledRecord[],
|
|
46
|
+
): Map<string, MarketplaceInstalledRecord> {
|
|
47
|
+
const lookup = new Map<string, MarketplaceInstalledRecord>();
|
|
48
|
+
|
|
49
|
+
for (const record of records) {
|
|
50
|
+
const candidates = [record.spec, record.id, record.label];
|
|
51
|
+
for (const candidate of candidates) {
|
|
52
|
+
const lookupKey = toLookupKey(record.type, candidate);
|
|
53
|
+
if (!lookupKey || lookup.has(lookupKey)) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
lookup.set(lookupKey, record);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return lookup;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function findInstalledRecordForItem(
|
|
64
|
+
item: MarketplaceItemSummary,
|
|
65
|
+
installedRecordLookup: Map<string, MarketplaceInstalledRecord>,
|
|
66
|
+
): MarketplaceInstalledRecord | undefined {
|
|
67
|
+
const candidates = [item.install.spec, item.slug, item.id];
|
|
68
|
+
for (const candidate of candidates) {
|
|
69
|
+
const lookupKey = toLookupKey(item.type, candidate);
|
|
70
|
+
if (!lookupKey) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const record = installedRecordLookup.get(lookupKey);
|
|
74
|
+
if (record) {
|
|
75
|
+
return record;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function findCatalogItemForRecord(
|
|
82
|
+
record: MarketplaceInstalledRecord,
|
|
83
|
+
catalogLookup: Map<string, MarketplaceItemSummary>,
|
|
84
|
+
): MarketplaceItemSummary | undefined {
|
|
85
|
+
const bySpec = catalogLookup.get(toLookupKey(record.type, record.spec));
|
|
86
|
+
if (bySpec) {
|
|
87
|
+
return bySpec;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const byId = catalogLookup.get(toLookupKey(record.type, record.id));
|
|
91
|
+
if (byId) {
|
|
92
|
+
return byId;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return catalogLookup.get(toLookupKey(record.type, record.label));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function matchInstalledSearch(
|
|
99
|
+
record: MarketplaceInstalledRecord,
|
|
100
|
+
item: MarketplaceItemSummary | undefined,
|
|
101
|
+
query: string,
|
|
102
|
+
localeFallbacks: string[],
|
|
103
|
+
): boolean {
|
|
104
|
+
const normalizedQuery = normalizeMarketplaceKey(query);
|
|
105
|
+
if (!normalizedQuery) {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const localizedSummary = pickLocalizedText(
|
|
110
|
+
item?.summaryI18n,
|
|
111
|
+
item?.summary,
|
|
112
|
+
localeFallbacks,
|
|
113
|
+
);
|
|
114
|
+
const values = [
|
|
115
|
+
record.id,
|
|
116
|
+
record.spec,
|
|
117
|
+
record.label,
|
|
118
|
+
item?.name,
|
|
119
|
+
item?.slug,
|
|
120
|
+
item?.summary,
|
|
121
|
+
localizedSummary,
|
|
122
|
+
...(item?.tags ?? []),
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
return values
|
|
126
|
+
.map((value) => normalizeMarketplaceKey(value))
|
|
127
|
+
.filter(Boolean)
|
|
128
|
+
.some((value) => value.includes(normalizedQuery));
|
|
129
|
+
}
|