@nextclaw/ui 0.5.37 → 0.5.38

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/assets/{ChannelsList-DtvhbEV9.js → ChannelsList-3B_zyiKA.js} +1 -1
  3. package/dist/assets/{ChatPage-Bw_aXB4R.js → ChatPage-DusH09PT.js} +1 -1
  4. package/dist/assets/{CronConfig-BZLXcDbm.js → CronConfig-5GTz5wPt.js} +1 -1
  5. package/dist/assets/DocBrowser-BtqGmg0N.js +1 -0
  6. package/dist/assets/MarketplacePage-BEW4M9BT.js +49 -0
  7. package/dist/assets/{ModelConfig-Bi8Q4_NG.js → ModelConfig-CwxXYqME.js} +1 -1
  8. package/dist/assets/{ProvidersList-D2OB0siE.js → ProvidersList-D4oaYHpJ.js} +1 -1
  9. package/dist/assets/{RuntimeConfig-Bz9aUkwu.js → RuntimeConfig-BwTxGi_U.js} +1 -1
  10. package/dist/assets/{SecretsConfig-Bqi-biOL.js → SecretsConfig-x36MY4ym.js} +1 -1
  11. package/dist/assets/{SessionsConfig-DcWT2QvI.js → SessionsConfig-qEffYDZ0.js} +1 -1
  12. package/dist/assets/{card-DwZkVl7S.js → card-Bq6uwDJQ.js} +1 -1
  13. package/dist/assets/index-DMEuanmd.css +1 -0
  14. package/dist/assets/index-wB2uPrKu.js +2 -0
  15. package/dist/assets/{label-BBDuC6Nm.js → label-Cq1vSfWg.js} +1 -1
  16. package/dist/assets/{logos-DMFt4YDI.js → logos-BKBMs40Q.js} +1 -1
  17. package/dist/assets/{page-layout-hPFzCUTQ.js → page-layout-D8MW2vP-.js} +1 -1
  18. package/dist/assets/{switch-CwkcbkEs.js → switch-CycMxy31.js} +1 -1
  19. package/dist/assets/{tabs-custom-TUrWRyYy.js → tabs-custom-N4olWJSw.js} +1 -1
  20. package/dist/assets/{useConfig-DZVUrqQz.js → useConfig-tR_KAfMV.js} +1 -1
  21. package/dist/assets/{useConfirmDialog-D5X0Iqid.js → useConfirmDialog-DE0Yp8Ai.js} +1 -1
  22. package/dist/index.html +2 -2
  23. package/package.json +1 -1
  24. package/src/api/marketplace.ts +24 -0
  25. package/src/api/types.ts +28 -0
  26. package/src/components/config/ModelConfig.tsx +1 -0
  27. package/src/components/config/ProviderForm.tsx +1 -0
  28. package/src/components/doc-browser/DocBrowser.tsx +382 -323
  29. package/src/components/doc-browser/DocBrowserContext.tsx +389 -157
  30. package/src/components/layout/Sidebar.tsx +1 -1
  31. package/src/components/marketplace/MarketplacePage.tsx +252 -12
  32. package/src/lib/i18n.ts +21 -2
  33. package/dist/assets/DocBrowser-BY0TiFOc.js +0 -1
  34. package/dist/assets/MarketplacePage-BDlAw7fO.js +0 -1
  35. package/dist/assets/index-C1NAfZSm.js +0 -2
  36. package/dist/assets/index-DWgSvrx4.css +0 -1
@@ -2,13 +2,19 @@
2
2
  import type {
3
3
  MarketplaceInstalledRecord,
4
4
  MarketplaceItemSummary,
5
+ MarketplaceLocalizedTextMap,
5
6
  MarketplaceManageAction,
7
+ MarketplacePluginContentView,
8
+ MarketplaceSkillContentView,
6
9
  MarketplaceSort,
7
10
  MarketplaceItemType
8
11
  } from '@/api/types';
12
+ import { fetchMarketplacePluginContent, fetchMarketplaceSkillContent } from '@/api/marketplace';
9
13
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
10
14
  import { Tabs } from '@/components/ui/tabs-custom';
11
15
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
16
+ import { useDocBrowser } from '@/components/doc-browser';
17
+ import { useI18n } from '@/components/providers/I18nProvider';
12
18
  import { useConfirmDialog } from '@/hooks/useConfirmDialog';
13
19
  import {
14
20
  useInstallMarketplaceItem,
@@ -123,16 +129,68 @@ function findCatalogItemForRecord(
123
129
  return catalogLookup.get(toLookupKey(record.type, record.label));
124
130
  }
125
131
 
132
+ function buildLocaleFallbacks(language: string): string[] {
133
+ const normalized = language.trim().toLowerCase().replace(/_/g, '-');
134
+ const base = normalized.split('-')[0];
135
+ const fallbacks = [normalized, base, 'en'];
136
+ return Array.from(new Set(fallbacks.filter(Boolean)));
137
+ }
138
+
139
+ function normalizeLocaleTag(locale: string): string {
140
+ return locale.trim().toLowerCase().replace(/_/g, '-');
141
+ }
142
+
143
+ function pickLocalizedText(
144
+ localized: MarketplaceLocalizedTextMap | undefined,
145
+ fallback: string | undefined,
146
+ localeFallbacks: string[]
147
+ ): string {
148
+ if (localized) {
149
+ const entries = Object.entries(localized)
150
+ .map(([locale, text]) => ({ locale: normalizeLocaleTag(locale), text: typeof text === 'string' ? text.trim() : '' }))
151
+ .filter((entry) => entry.text.length > 0);
152
+
153
+ if (entries.length > 0) {
154
+ const exactMap = new Map(entries.map((entry) => [entry.locale, entry.text] as const));
155
+
156
+ for (const locale of localeFallbacks) {
157
+ const normalizedLocale = normalizeLocaleTag(locale);
158
+ const exact = exactMap.get(normalizedLocale);
159
+ if (exact) {
160
+ return exact;
161
+ }
162
+ }
163
+
164
+ for (const locale of localeFallbacks) {
165
+ const base = normalizeLocaleTag(locale).split('-')[0];
166
+ if (!base) {
167
+ continue;
168
+ }
169
+ const matched = entries.find((entry) => entry.locale === base || entry.locale.startsWith(`${base}-`));
170
+ if (matched) {
171
+ return matched.text;
172
+ }
173
+ }
174
+
175
+ return entries[0]?.text ?? '';
176
+ }
177
+ }
178
+
179
+ return fallback?.trim() ?? '';
180
+ }
181
+
126
182
  function matchInstalledSearch(
127
183
  record: MarketplaceInstalledRecord,
128
184
  item: MarketplaceItemSummary | undefined,
129
- query: string
185
+ query: string,
186
+ localeFallbacks: string[]
130
187
  ): boolean {
131
188
  const normalizedQuery = normalizeMarketplaceKey(query);
132
189
  if (!normalizedQuery) {
133
190
  return true;
134
191
  }
135
192
 
193
+ const localizedSummary = pickLocalizedText(item?.summaryI18n, item?.summary, localeFallbacks);
136
194
  const values = [
137
195
  record.id,
138
196
  record.spec,
@@ -140,6 +198,7 @@ function matchInstalledSearch(
140
198
  item?.name,
141
199
  item?.slug,
142
200
  item?.summary,
201
+ localizedSummary,
143
202
  ...(item?.tags ?? [])
144
203
  ];
145
204
 
@@ -166,12 +225,93 @@ function ItemIcon({ name, fallback }: { name?: string; fallback: string }) {
166
225
  const letters = displayName.substring(0, 2).toUpperCase();
167
226
  const colorClass = getAvatarColor(displayName);
168
227
  return (
169
- <div className={cn("flex items-center justify-center w-10 h-10 rounded-xl text-white font-semibold text-sm shrink-0", colorClass)}>
228
+ <div className={cn('flex items-center justify-center w-10 h-10 rounded-xl text-white font-semibold text-sm shrink-0', colorClass)}>
170
229
  {letters}
171
230
  </div>
172
231
  );
173
232
  }
174
233
 
234
+ function escapeHtml(text: string): string {
235
+ return text
236
+ .replace(/&/g, '&amp;')
237
+ .replace(/</g, '&lt;')
238
+ .replace(/>/g, '&gt;')
239
+ .replace(/"/g, '&quot;')
240
+ .replace(/'/g, '&#39;');
241
+ }
242
+
243
+ function buildGenericDetailDataUrl(params: {
244
+ title: string;
245
+ typeLabel: string;
246
+ spec: string;
247
+ summary?: string;
248
+ description?: string;
249
+ metadataRaw?: string;
250
+ contentRaw?: string;
251
+ sourceUrl?: string;
252
+ sourceLabel?: string;
253
+ tags?: string[];
254
+ author?: string;
255
+ }): string {
256
+ const metadata = params.metadataRaw?.trim() || '-';
257
+ const content = params.contentRaw?.trim() || '-';
258
+ const summary = params.summary?.trim();
259
+ const description = params.description?.trim();
260
+ const shouldShowDescription = Boolean(description) && description !== summary;
261
+
262
+ const html = `<!doctype html>
263
+ <html>
264
+ <head>
265
+ <meta charset="utf-8" />
266
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
267
+ <title>${escapeHtml(params.title)}</title>
268
+ <style>
269
+ :root { color-scheme: light; }
270
+ body { margin: 0; background: #f7f9fc; color: #0f172a; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
271
+ .wrap { max-width: 980px; margin: 0 auto; padding: 28px 20px 40px; }
272
+ .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); }
273
+ .hero h1 { margin: 0; font-size: 26px; }
274
+ .meta { margin-top: 8px; color: #475569; font-size: 13px; }
275
+ .summary { margin-top: 14px; font-size: 14px; line-height: 1.7; color: #334155; }
276
+ .grid { display: grid; grid-template-columns: 260px 1fr; gap: 14px; margin-top: 16px; }
277
+ .card { border: 1px solid #e2e8f0; background: #fff; border-radius: 14px; overflow: hidden; }
278
+ .card h2 { margin: 0; padding: 12px 14px; font-size: 13px; font-weight: 700; color: #1d4ed8; border-bottom: 1px solid #e2e8f0; background: #f8fafc; }
279
+ .card .body { padding: 12px 14px; font-size: 13px; color: #334155; line-height: 1.7; }
280
+ .code { white-space: pre-wrap; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; line-height: 1.6; margin: 0; }
281
+ .tags { margin-top: 10px; }
282
+ .tag { display: inline-block; margin: 0 6px 6px 0; padding: 4px 9px; border-radius: 999px; background: #e0e7ff; color: #3730a3; font-size: 11px; }
283
+ .source { color: #2563eb; text-decoration: none; }
284
+ @media (max-width: 860px) { .grid { grid-template-columns: 1fr; } }
285
+ </style>
286
+ </head>
287
+ <body>
288
+ <main class="wrap">
289
+ <section class="hero">
290
+ <h1>${escapeHtml(params.title)}</h1>
291
+ <div class="meta">${escapeHtml(params.typeLabel)} · ${escapeHtml(params.spec)}${params.author ? ` · ${escapeHtml(params.author)}` : ''}</div>
292
+ ${summary ? `<p class="summary">${escapeHtml(summary)}</p>` : ''}
293
+ ${shouldShowDescription ? `<p class="summary">${escapeHtml(description as string)}</p>` : ''}
294
+ ${params.tags && params.tags.length > 0 ? `<div class="tags">${params.tags.map((tag) => `<span class="tag">${escapeHtml(tag)}</span>`).join('')}</div>` : ''}
295
+ ${params.sourceUrl ? `<p class="meta" style="margin-top:12px;">${escapeHtml(params.sourceLabel ?? 'Source')}: <a class="source" href="${escapeHtml(params.sourceUrl)}" target="_blank" rel="noopener noreferrer">${escapeHtml(params.sourceUrl)}</a></p>` : ''}
296
+ </section>
297
+
298
+ <section class="grid">
299
+ <article class="card">
300
+ <h2>Metadata</h2>
301
+ <div class="body"><pre class="code">${escapeHtml(metadata)}</pre></div>
302
+ </article>
303
+ <article class="card">
304
+ <h2>Content</h2>
305
+ <div class="body"><pre class="code">${escapeHtml(content)}</pre></div>
306
+ </article>
307
+ </section>
308
+ </main>
309
+ </body>
310
+ </html>`;
311
+
312
+ return `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;
313
+ }
314
+
175
315
  function FilterPanel(props: {
176
316
  scope: ScopeType;
177
317
  searchText: string;
@@ -212,8 +352,10 @@ function FilterPanel(props: {
212
352
  function MarketplaceListCard(props: {
213
353
  item?: MarketplaceItemSummary;
214
354
  record?: MarketplaceInstalledRecord;
355
+ localeFallbacks: string[];
215
356
  installState: InstallState;
216
357
  manageState: ManageState;
358
+ onOpen: () => void;
217
359
  onInstall: (item: MarketplaceItemSummary) => void;
218
360
  onManage: (action: MarketplaceManageAction, record: MarketplaceInstalledRecord) => void;
219
361
  }) {
@@ -221,7 +363,8 @@ function MarketplaceListCard(props: {
221
363
  const pluginRecord = record?.type === 'plugin' ? record : undefined;
222
364
  const type = props.item?.type ?? record?.type;
223
365
  const title = props.item?.name ?? record?.label ?? record?.id ?? record?.spec ?? t('marketplaceUnknownItem');
224
- const summary = props.item?.summary ?? (record ? t('marketplaceInstalledLocalSummary') : '');
366
+ const summary = pickLocalizedText(props.item?.summaryI18n, props.item?.summary, props.localeFallbacks)
367
+ || (record ? t('marketplaceInstalledLocalSummary') : '');
225
368
  const spec = props.item?.install.spec ?? record?.spec ?? '';
226
369
 
227
370
  const targetId = record?.id || record?.spec;
@@ -239,14 +382,17 @@ function MarketplaceListCard(props: {
239
382
  const displayType = type === 'plugin' ? t('marketplaceTypePlugin') : type === 'skill' ? t('marketplaceTypeSkill') : t('marketplaceTypeExtension');
240
383
 
241
384
  return (
242
- <article className="group bg-white border border-gray-200/40 hover:border-gray-200/80 rounded-2xl px-5 py-4 hover:shadow-md shadow-sm transition-all flex items-start gap-3.5 justify-between cursor-default">
385
+ <article
386
+ onClick={props.onOpen}
387
+ className="group bg-white border border-gray-200/40 hover:border-blue-300/80 rounded-2xl px-5 py-4 hover:shadow-md shadow-sm transition-all flex items-start gap-3.5 justify-between cursor-pointer"
388
+ >
243
389
  <div className="flex gap-3 min-w-0 flex-1 h-full items-start">
244
390
  <ItemIcon name={title} fallback={spec || t('marketplaceTypeExtension')} />
245
391
  <div className="min-w-0 flex-1 flex flex-col justify-center h-full">
246
392
  <TooltipProvider delayDuration={400}>
247
393
  <Tooltip>
248
394
  <TooltipTrigger asChild>
249
- <div className="text-[14px] font-semibold text-gray-900 truncate leading-tight cursor-default">{title}</div>
395
+ <div className="text-[14px] font-semibold text-gray-900 truncate leading-tight">{title}</div>
250
396
  </TooltipTrigger>
251
397
  <TooltipContent className="max-w-[300px] text-xs">
252
398
  {title}
@@ -260,7 +406,7 @@ function MarketplaceListCard(props: {
260
406
  <span className="text-[10px] text-gray-300">•</span>
261
407
  <Tooltip>
262
408
  <TooltipTrigger asChild>
263
- <span className="text-[11px] text-gray-400 truncate max-w-full font-mono cursor-default">{spec}</span>
409
+ <span className="text-[11px] text-gray-400 truncate max-w-full font-mono">{spec}</span>
264
410
  </TooltipTrigger>
265
411
  <TooltipContent className="max-w-[300px] text-xs font-mono break-all">
266
412
  {spec}
@@ -272,7 +418,7 @@ function MarketplaceListCard(props: {
272
418
 
273
419
  <Tooltip>
274
420
  <TooltipTrigger asChild>
275
- <p className="text-[12px] text-gray-500/90 line-clamp-1 transition-colors leading-relaxed text-left cursor-default">{summary}</p>
421
+ <p className="text-[12px] text-gray-500/90 line-clamp-1 transition-colors leading-relaxed text-left">{summary}</p>
276
422
  </TooltipTrigger>
277
423
  {summary && (
278
424
  <TooltipContent className="max-w-[400px] text-xs leading-relaxed">
@@ -287,7 +433,10 @@ function MarketplaceListCard(props: {
287
433
  <div className="shrink-0 flex items-center h-full">
288
434
  {props.item && !record && (
289
435
  <button
290
- onClick={() => props.onInstall(props.item as MarketplaceItemSummary)}
436
+ onClick={(event) => {
437
+ event.stopPropagation();
438
+ props.onInstall(props.item as MarketplaceItemSummary);
439
+ }}
291
440
  disabled={isInstalling}
292
441
  className="inline-flex items-center gap-1.5 h-8 px-4 rounded-xl text-xs font-medium bg-primary text-white hover:bg-primary-600 disabled:opacity-50 transition-colors"
293
442
  >
@@ -298,7 +447,10 @@ function MarketplaceListCard(props: {
298
447
  {pluginRecord && canToggle && (
299
448
  <button
300
449
  disabled={props.manageState.isPending}
301
- onClick={() => props.onManage(isDisabled ? 'enable' : 'disable', pluginRecord)}
450
+ onClick={(event) => {
451
+ event.stopPropagation();
452
+ props.onManage(isDisabled ? 'enable' : 'disable', pluginRecord);
453
+ }}
302
454
  className="inline-flex items-center h-8 px-4 rounded-xl text-xs font-medium border border-gray-200/80 text-gray-600 bg-white hover:bg-gray-50 hover:border-gray-300 disabled:opacity-50 transition-colors"
303
455
  >
304
456
  {busyForRecord && props.manageState.action !== 'uninstall'
@@ -310,7 +462,10 @@ function MarketplaceListCard(props: {
310
462
  {record && canUninstall && (
311
463
  <button
312
464
  disabled={props.manageState.isPending}
313
- onClick={() => props.onManage('uninstall', record)}
465
+ onClick={(event) => {
466
+ event.stopPropagation();
467
+ props.onManage('uninstall', record);
468
+ }}
314
469
  className="inline-flex items-center h-8 px-4 rounded-xl text-xs font-medium border border-rose-100 text-rose-500 bg-white hover:bg-rose-50 hover:border-rose-200 disabled:opacity-50 transition-colors"
315
470
  >
316
471
  {busyForRecord && props.manageState.action === 'uninstall' ? t('marketplaceRemoving') : t('marketplaceUninstall')}
@@ -354,6 +509,8 @@ function PaginationBar(props: {
354
509
  export function MarketplacePage() {
355
510
  const navigate = useNavigate();
356
511
  const params = useParams<{ type?: string }>();
512
+ const { language } = useI18n();
513
+ const docBrowser = useDocBrowser();
357
514
 
358
515
  const routeType: MarketplaceRouteType | null = useMemo(() => {
359
516
  if (params.type === 'plugins' || params.type === 'skills') {
@@ -369,6 +526,8 @@ export function MarketplacePage() {
369
526
  }, [routeType, navigate]);
370
527
 
371
528
  const typeFilter: MarketplaceItemType = routeType === 'skills' ? 'skill' : 'plugin';
529
+ const localeFallbacks = useMemo(() => buildLocaleFallbacks(language), [language]);
530
+
372
531
  const isPluginModule = typeFilter === 'plugin';
373
532
  const copyKeys = isPluginModule
374
533
  ? {
@@ -461,7 +620,7 @@ export function MarketplacePage() {
461
620
  record,
462
621
  item: findCatalogItemForRecord(record, catalogLookup)
463
622
  }))
464
- .filter((entry) => matchInstalledSearch(entry.record, entry.item, query));
623
+ .filter((entry) => matchInstalledSearch(entry.record, entry.item, query, localeFallbacks));
465
624
 
466
625
  entries.sort((left, right) => {
467
626
  const leftTs = left.record.installedAt ? Date.parse(left.record.installedAt) : Number.NaN;
@@ -477,7 +636,7 @@ export function MarketplacePage() {
477
636
  });
478
637
 
479
638
  return entries;
480
- }, [installedRecords, typeFilter, catalogLookup, query]);
639
+ }, [installedRecords, typeFilter, catalogLookup, query, localeFallbacks]);
481
640
 
482
641
  const total = scope === 'installed' ? installedEntries.length : (itemsQuery.data?.total ?? 0);
483
642
  const totalPages = scope === 'installed' ? 1 : (itemsQuery.data?.totalPages ?? 0);
@@ -509,6 +668,7 @@ export function MarketplacePage() {
509
668
  { id: 'all', label: t(copyKeys.tabMarketplace) },
510
669
  { id: 'installed', label: t(copyKeys.tabInstalled), count: installedQuery.data?.total ?? 0 }
511
670
  ];
671
+
512
672
  const handleInstall = async (item: MarketplaceItemSummary) => {
513
673
  const installSpec = item.install.spec;
514
674
  if (installingSpecs.has(installSpec)) {
@@ -577,6 +737,82 @@ export function MarketplacePage() {
577
737
  });
578
738
  };
579
739
 
740
+ const openItemDetail = async (item?: MarketplaceItemSummary, record?: MarketplaceInstalledRecord) => {
741
+ const title = item?.name ?? record?.label ?? record?.id ?? record?.spec ?? t('marketplaceUnknownItem');
742
+
743
+ if (!item) {
744
+ const url = buildGenericDetailDataUrl({
745
+ title,
746
+ typeLabel: record?.type === 'plugin' ? t('marketplaceTypePlugin') : t('marketplaceTypeSkill'),
747
+ spec: record?.spec ?? '-',
748
+ summary: t('marketplaceInstalledLocalSummary'),
749
+ metadataRaw: JSON.stringify(record ?? {}, null, 2),
750
+ contentRaw: '-'
751
+ });
752
+ docBrowser.open(url, { newTab: true, title, kind: 'content' });
753
+ return;
754
+ }
755
+
756
+ const summary = pickLocalizedText(item.summaryI18n, item.summary, localeFallbacks);
757
+
758
+ if (item.type === 'skill') {
759
+ try {
760
+ const content: MarketplaceSkillContentView = await fetchMarketplaceSkillContent(item.slug);
761
+ const url = buildGenericDetailDataUrl({
762
+ title,
763
+ typeLabel: t('marketplaceTypeSkill'),
764
+ spec: item.install.spec,
765
+ summary,
766
+ metadataRaw: content.metadataRaw,
767
+ contentRaw: content.bodyRaw || content.raw,
768
+ sourceUrl: content.sourceUrl,
769
+ sourceLabel: `Source (${content.source})`,
770
+ tags: item.tags,
771
+ author: item.author
772
+ });
773
+ docBrowser.open(url, { newTab: true, title, kind: 'content' });
774
+ } catch (error) {
775
+ const url = buildGenericDetailDataUrl({
776
+ title,
777
+ typeLabel: t('marketplaceTypeSkill'),
778
+ spec: item.install.spec,
779
+ summary,
780
+ metadataRaw: JSON.stringify({ error: error instanceof Error ? error.message : String(error) }, null, 2),
781
+ contentRaw: t('marketplaceOperationFailed')
782
+ });
783
+ docBrowser.open(url, { newTab: true, title, kind: 'content' });
784
+ }
785
+ return;
786
+ }
787
+
788
+ try {
789
+ const content: MarketplacePluginContentView = await fetchMarketplacePluginContent(item.slug);
790
+ const url = buildGenericDetailDataUrl({
791
+ title,
792
+ typeLabel: t('marketplaceTypePlugin'),
793
+ spec: item.install.spec,
794
+ summary,
795
+ metadataRaw: content.metadataRaw,
796
+ contentRaw: content.bodyRaw || content.raw || item.summary,
797
+ sourceUrl: content.sourceUrl,
798
+ sourceLabel: `Source (${content.source})`,
799
+ tags: item.tags,
800
+ author: item.author
801
+ });
802
+ docBrowser.open(url, { newTab: true, title, kind: 'content' });
803
+ } catch (error) {
804
+ const url = buildGenericDetailDataUrl({
805
+ title,
806
+ typeLabel: t('marketplaceTypePlugin'),
807
+ spec: item.install.spec,
808
+ summary,
809
+ metadataRaw: JSON.stringify({ error: error instanceof Error ? error.message : String(error) }, null, 2),
810
+ contentRaw: '-'
811
+ });
812
+ docBrowser.open(url, { newTab: true, title, kind: 'content' });
813
+ }
814
+ };
815
+
580
816
  return (
581
817
  <PageLayout>
582
818
  <PageHeader title={t(copyKeys.pageTitle)} description={t(copyKeys.pageDescription)} />
@@ -628,8 +864,10 @@ export function MarketplacePage() {
628
864
  key={item.id}
629
865
  item={item}
630
866
  record={findInstalledRecordForItem(item, installedRecordLookup)}
867
+ localeFallbacks={localeFallbacks}
631
868
  installState={installState}
632
869
  manageState={manageState}
870
+ onOpen={() => void openItemDetail(item, findInstalledRecordForItem(item, installedRecordLookup))}
633
871
  onInstall={handleInstall}
634
872
  onManage={handleManage}
635
873
  />
@@ -640,8 +878,10 @@ export function MarketplacePage() {
640
878
  key={entry.key}
641
879
  item={entry.item}
642
880
  record={entry.record}
881
+ localeFallbacks={localeFallbacks}
643
882
  installState={installState}
644
883
  manageState={manageState}
884
+ onOpen={() => void openItemDetail(entry.item, entry.record)}
645
885
  onInstall={handleInstall}
646
886
  onManage={handleManage}
647
887
  />
package/src/lib/i18n.ts CHANGED
@@ -168,6 +168,10 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
168
168
  modelName: { zh: '模型', en: 'Model' },
169
169
  modelPickerNoOptions: { zh: '暂无可选模型', en: 'No model options available' },
170
170
  modelPickerUseCustom: { zh: '使用自定义模型:{value}', en: 'Use custom model: {value}' },
171
+ modelInputCustomHint: {
172
+ zh: '如果列表里没有目标模型,可直接输入自定义模型 ID。',
173
+ en: 'If the model is not listed, type a custom model ID directly.'
174
+ },
171
175
  maxTokens: { zh: '最大 Token 数', en: 'Max Tokens' },
172
176
  maxToolIterations: { zh: '最大工具迭代次数', en: 'Max Tool Iterations' },
173
177
  saveChanges: { zh: '保存变更', en: 'Save Changes' },
@@ -206,7 +210,14 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
206
210
  providerTestConnectionSuccess: { zh: '连接测试通过', en: 'Connection test passed' },
207
211
  providerTestConnectionFailed: { zh: '连接测试失败', en: 'Connection test failed' },
208
212
  providerModelsTitle: { zh: '可用模型列表', en: 'Available Models' },
209
- providerModelInputPlaceholder: { zh: '输入模型 ID(无需 provider 前缀)', en: 'Enter model id (without provider prefix)' },
213
+ providerModelInputPlaceholder: {
214
+ zh: '输入模型 ID(无需 provider 前缀,不在列表也可)',
215
+ en: 'Enter model id (without provider prefix; custom values allowed)'
216
+ },
217
+ providerModelInputHint: {
218
+ zh: '列表仅作参考,不在列表也可直接输入并添加。',
219
+ en: 'The list is only a reference. You can type and add models that are not listed.'
220
+ },
210
221
  providerAddModel: { zh: '添加模型', en: 'Add Model' },
211
222
  providerModelsEmpty: { zh: '当前没有模型,可直接输入并添加。', en: 'No models yet. Add one by typing model id.' },
212
223
  providerModelDefaultTag: { zh: '默认', en: 'Default' },
@@ -550,6 +561,11 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
550
561
  marketplaceOperationFailed: { zh: '操作失败', en: 'Operation failed' },
551
562
  marketplaceInstalledPluginsCountSuffix: { zh: '个已安装插件', en: 'installed plugins' },
552
563
  marketplaceInstalledSkillsCountSuffix: { zh: '个已安装技能', en: 'installed skills' },
564
+ marketplaceDetailPanelTitle: { zh: '详情预览', en: 'Detail Preview' },
565
+ marketplaceDetailPanelEmpty: { zh: '点击左侧任意插件/技能,在这里查看详情。', en: 'Click an item on the left to preview details here.' },
566
+ marketplaceDetailSummary: { zh: '摘要', en: 'Summary' },
567
+ marketplaceDetailDescription: { zh: '描述', en: 'Description' },
568
+ marketplaceOpenInDocBrowserTab: { zh: '新标签打开', en: 'Open in New Tab' },
553
569
 
554
570
  // Status
555
571
  connected: { zh: '已连接', en: 'Connected' },
@@ -582,7 +598,7 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
582
598
  headerValue: { zh: 'Header 值', en: 'Header Value' },
583
599
 
584
600
  // Doc Browser
585
- docBrowserTitle: { zh: '帮助文档', en: 'Help Docs' },
601
+ docBrowserTitle: { zh: '内嵌浏览器', en: 'Embedded Browser' },
586
602
  docBrowserSearchPlaceholder: { zh: '搜索,也可以输入文档地址直接打开', en: 'Search, or enter a doc URL to open' },
587
603
  docBrowserUrlPlaceholder: { zh: '输入文档路径,如 /guide/channels', en: 'Enter a doc path, e.g. /guide/channels' },
588
604
  docBrowserOpenExternal: { zh: '文档中心打开', en: 'Open in Docs' },
@@ -590,6 +606,9 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
590
606
  docBrowserDockMode: { zh: '固定到侧栏', en: 'Dock to Sidebar' },
591
607
  docBrowserClose: { zh: '关闭', en: 'Close' },
592
608
  docBrowserHelp: { zh: '帮助文档', en: 'Help Docs' },
609
+ docBrowserNewTab: { zh: '新建标签', en: 'New Tab' },
610
+ docBrowserCloseTab: { zh: '关闭标签', en: 'Close Tab' },
611
+ docBrowserTabUntitled: { zh: '未命名', en: 'Untitled' },
593
612
  };
594
613
 
595
614
  export function t(key: string, lang: I18nLanguage = getLanguage()): string {
@@ -1 +0,0 @@
1
- import{r as s,j as t,s as J,aS as K,aT as Q,ad as Z,aU as ee,aV as te,a0 as re,ak as se,aW as oe}from"./vendor-DN_iJQc4.js";import{u as ae,D as W,t as x,c as C}from"./index-C1NAfZSm.js";function ce(){const{isOpen:U,mode:h,currentUrl:i,navVersion:f,close:B,toggleMode:P,goBack:Y,goForward:F,canGoBack:I,canGoForward:O,navigate:v,syncUrl:z}=ae(),[b,y]=s.useState(""),[j,M]=s.useState(!1),[A,m]=s.useState(!1),[l,N]=s.useState(()=>({x:Math.max(40,window.innerWidth-520),y:80})),[a,R]=s.useState({w:480,h:600}),[E,T]=s.useState(420),c=s.useRef(null),d=s.useRef(null),p=s.useRef(null),k=s.useRef(null),S=s.useRef(f);s.useEffect(()=>{try{const e=new URL(i);y(e.pathname)}catch{y(i)}},[i]),s.useEffect(()=>{var e;if(f!==S.current){S.current=f;return}if((e=k.current)!=null&&e.contentWindow)try{const r=new URL(i).pathname;k.current.contentWindow.postMessage({type:"docs-navigate",path:r},"*")}catch{}},[i,f]),s.useEffect(()=>{h==="floating"&&N(e=>({x:Math.max(40,window.innerWidth-a.w-40),y:e.y}))},[h,a.w]),s.useEffect(()=>{const e=r=>{var o;((o=r.data)==null?void 0:o.type)==="docs-route-change"&&typeof r.data.url=="string"&&z(r.data.url)};return window.addEventListener("message",e),()=>window.removeEventListener("message",e)},[z]);const V=s.useCallback(e=>{e.preventDefault();const r=b.trim();r&&(r.startsWith("/")?v(`${W}${r}`):r.startsWith("http")?v(r):v(`${W}/${r}`))},[b,v]),_=s.useCallback(e=>{h==="floating"&&(M(!0),c.current={startX:e.clientX,startY:e.clientY,startPosX:l.x,startPosY:l.y})},[h,l]);s.useEffect(()=>{if(!j)return;const e=o=>{c.current&&N({x:c.current.startPosX+(o.clientX-c.current.startX),y:c.current.startPosY+(o.clientY-c.current.startY)})},r=()=>{M(!1),c.current=null};return window.addEventListener("mousemove",e),window.addEventListener("mouseup",r),()=>{window.removeEventListener("mousemove",e),window.removeEventListener("mouseup",r)}},[j]);const L=s.useCallback(e=>{e.preventDefault(),e.stopPropagation(),m(!0);const r=e.currentTarget.dataset.axis;d.current={startX:e.clientX,startY:e.clientY,startW:a.w,startH:a.h};const o=u=>{d.current&&R(g=>({w:r==="y"?g.w:Math.max(360,d.current.startW+(u.clientX-d.current.startX)),h:r==="x"?g.h:Math.max(400,d.current.startH+(u.clientY-d.current.startY))}))},w=()=>{m(!1),d.current=null,window.removeEventListener("mousemove",o),window.removeEventListener("mouseup",w)};window.addEventListener("mousemove",o),window.addEventListener("mouseup",w)},[a]),$=s.useCallback(e=>{e.preventDefault(),e.stopPropagation(),m(!0),p.current={startX:e.clientX,startW:E};const r=w=>{if(!p.current)return;const u=p.current.startX-w.clientX;T(Math.max(320,Math.min(800,p.current.startW+u)))},o=()=>{m(!1),p.current=null,window.removeEventListener("mousemove",r),window.removeEventListener("mouseup",o)};window.addEventListener("mousemove",r),window.addEventListener("mouseup",o)},[E]),G=s.useCallback(e=>{e.preventDefault(),e.stopPropagation(),m(!0);const r=e.clientX,o=a.w,w=l.x,u=H=>{const q=r-H.clientX,X=Math.max(360,o+q);R(D=>({...D,w:X})),N(D=>({...D,x:w-(X-o)}))},g=()=>{m(!1),window.removeEventListener("mousemove",u),window.removeEventListener("mouseup",g)};window.addEventListener("mousemove",u),window.addEventListener("mouseup",g)},[a.w,l.x]);if(!U)return null;const n=h==="docked";return t.jsxs("div",{className:C("flex flex-col bg-white overflow-hidden relative",n?"h-full border-l border-gray-200 shrink-0":"rounded-2xl shadow-2xl border border-gray-200"),style:n?{width:E}:{position:"fixed",left:l.x,top:l.y,width:a.w,height:a.h,zIndex:9999},children:[n&&t.jsx("div",{className:"absolute top-0 left-0 w-1.5 h-full cursor-ew-resize z-20 hover:bg-primary/10 transition-colors",onMouseDown:$}),t.jsxs("div",{className:C("flex items-center justify-between px-4 py-2.5 bg-gray-50 border-b border-gray-200 shrink-0 select-none",!n&&"cursor-grab active:cursor-grabbing"),onMouseDown:n?void 0:_,children:[t.jsxs("div",{className:"flex items-center gap-2.5",children:[t.jsx(J,{className:"w-4 h-4 text-primary"}),t.jsx("span",{className:"text-sm font-semibold text-gray-900",children:x("docBrowserTitle")})]}),t.jsxs("div",{className:"flex items-center gap-1",children:[t.jsx("button",{onClick:P,className:"hover:bg-gray-200 rounded-md p-1.5 text-gray-500 hover:text-gray-700 transition-colors",title:n?x("docBrowserFloatMode"):x("docBrowserDockMode"),children:n?t.jsx(K,{className:"w-3.5 h-3.5"}):t.jsx(Q,{className:"w-3.5 h-3.5"})}),t.jsx("button",{onClick:B,className:"hover:bg-gray-200 rounded-md p-1.5 text-gray-500 hover:text-gray-700 transition-colors",title:x("docBrowserClose"),children:t.jsx(Z,{className:"w-3.5 h-3.5"})})]})]}),t.jsxs("div",{className:"flex items-center gap-2 px-3.5 py-2 bg-white border-b border-gray-100 shrink-0",children:[t.jsx("button",{onClick:Y,disabled:!I,className:"p-1.5 rounded-md hover:bg-gray-100 disabled:opacity-30 disabled:cursor-not-allowed text-gray-600 transition-colors",children:t.jsx(ee,{className:"w-4 h-4"})}),t.jsx("button",{onClick:F,disabled:!O,className:"p-1.5 rounded-md hover:bg-gray-100 disabled:opacity-30 disabled:cursor-not-allowed text-gray-600 transition-colors",children:t.jsx(te,{className:"w-4 h-4"})}),t.jsxs("form",{onSubmit:V,className:"flex-1 relative",children:[t.jsx(re,{className:"w-3.5 h-3.5 absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"}),t.jsx("input",{type:"text",value:b,onChange:e=>y(e.target.value),placeholder:x("docBrowserSearchPlaceholder"),className:"w-full h-8 pl-8 pr-3 rounded-lg bg-gray-50 border border-gray-200 text-xs text-gray-700 focus:outline-none focus:ring-1 focus:ring-primary/30 focus:border-primary/40 transition-colors placeholder:text-gray-400"})]})]}),t.jsxs("div",{className:"flex-1 relative overflow-hidden",children:[t.jsx("iframe",{ref:k,src:i,className:"absolute inset-0 w-full h-full border-0",title:"NextClaw Documentation",sandbox:"allow-same-origin allow-scripts allow-popups allow-forms",allow:"clipboard-read; clipboard-write"},f),(A||j)&&t.jsx("div",{className:"absolute inset-0 z-10"})]}),t.jsx("div",{className:"flex items-center justify-between px-4 py-2 bg-gray-50 border-t border-gray-200 shrink-0",children:t.jsxs("a",{href:i,target:"_blank",rel:"noopener noreferrer","data-doc-external":!0,className:"flex items-center gap-1.5 text-xs text-primary hover:text-primary-hover font-medium transition-colors",children:[x("docBrowserOpenExternal"),t.jsx(se,{className:"w-3 h-3"})]})}),!n&&t.jsxs(t.Fragment,{children:[t.jsx("div",{className:"absolute top-0 left-0 w-1.5 h-full cursor-ew-resize z-20 hover:bg-primary/10 transition-colors",onMouseDown:G}),t.jsx("div",{className:"absolute top-0 right-0 w-1.5 h-full cursor-ew-resize z-20 hover:bg-primary/10 transition-colors",onMouseDown:L,"data-axis":"x"}),t.jsx("div",{className:"absolute bottom-0 left-0 h-1.5 w-full cursor-ns-resize z-20 hover:bg-primary/10 transition-colors",onMouseDown:L,"data-axis":"y"}),t.jsx("div",{className:"absolute bottom-0 right-0 w-4 h-4 cursor-se-resize z-30 flex items-center justify-center text-gray-300 hover:text-gray-500 transition-colors",onMouseDown:L,children:t.jsx(oe,{className:"w-3 h-3 rotate-[-45deg]"})})]})]})}export{ce as DocBrowser};
@@ -1 +0,0 @@
1
- import{r as o,j as s,at as Oe,au as $e,av as Fe,aw as xe,ax as qe,ay as He,az as E,aA as he,aB as Ke,aC as Ue,aD as ze,aE as Qe,aF as Be,aG as Ge,aH as Ve,aq as ye,ar as be,as as ke,ag as q,aI as Ye,aJ as Xe,aK as Je}from"./vendor-DN_iJQc4.js";import{c as ve,j as H,t as c,S as We,a as Ze,b as et,d as tt,e as ge}from"./index-C1NAfZSm.js";import{T as at}from"./tabs-custom-TUrWRyYy.js";import{P as Te,u as nt}from"./useConfirmDialog-D5X0Iqid.js";import{P as st,a as rt}from"./page-layout-hPFzCUTQ.js";var[K]=Ue("Tooltip",[he]),U=he(),Ce="TooltipProvider",lt=700,Y="tooltip.open",[ot,W]=K(Ce),Se=e=>{const{__scopeTooltip:t,delayDuration:a=lt,skipDelayDuration:n=300,disableHoverableContent:l=!1,children:r}=e,u=o.useRef(!0),b=o.useRef(!1),d=o.useRef(0);return o.useEffect(()=>{const x=d.current;return()=>window.clearTimeout(x)},[]),s.jsx(ot,{scope:t,isOpenDelayedRef:u,delayDuration:a,onOpen:o.useCallback(()=>{window.clearTimeout(d.current),u.current=!1},[]),onClose:o.useCallback(()=>{window.clearTimeout(d.current),d.current=window.setTimeout(()=>u.current=!0,n)},[n]),isPointerInTransitRef:b,onPointerInTransitChange:o.useCallback(x=>{b.current=x},[]),disableHoverableContent:l,children:r})};Se.displayName=Ce;var L="Tooltip",[it,A]=K(L),Pe=e=>{const{__scopeTooltip:t,children:a,open:n,defaultOpen:l,onOpenChange:r,disableHoverableContent:u,delayDuration:b}=e,d=W(L,e.__scopeTooltip),x=U(t),[i,h]=o.useState(null),k=Oe(),m=o.useRef(0),T=u??d.disableHoverableContent,y=b??d.delayDuration,C=o.useRef(!1),[P,f]=$e({prop:n,defaultProp:l??!1,onChange:O=>{O?(d.onOpen(),document.dispatchEvent(new CustomEvent(Y))):d.onClose(),r==null||r(O)},caller:L}),v=o.useMemo(()=>P?C.current?"delayed-open":"instant-open":"closed",[P]),j=o.useCallback(()=>{window.clearTimeout(m.current),m.current=0,C.current=!1,f(!0)},[f]),S=o.useCallback(()=>{window.clearTimeout(m.current),m.current=0,f(!1)},[f]),_=o.useCallback(()=>{window.clearTimeout(m.current),m.current=window.setTimeout(()=>{C.current=!0,f(!0),m.current=0},y)},[y,f]);return o.useEffect(()=>()=>{m.current&&(window.clearTimeout(m.current),m.current=0)},[]),s.jsx(Fe,{...x,children:s.jsx(it,{scope:t,contentId:k,open:P,stateAttribute:v,trigger:i,onTriggerChange:h,onTriggerEnter:o.useCallback(()=>{d.isOpenDelayedRef.current?_():j()},[d.isOpenDelayedRef,_,j]),onTriggerLeave:o.useCallback(()=>{T?S():(window.clearTimeout(m.current),m.current=0)},[S,T]),onOpen:j,onClose:S,disableHoverableContent:T,children:a})})};Pe.displayName=L;var X="TooltipTrigger",we=o.forwardRef((e,t)=>{const{__scopeTooltip:a,...n}=e,l=A(X,a),r=W(X,a),u=U(a),b=o.useRef(null),d=xe(t,b,l.onTriggerChange),x=o.useRef(!1),i=o.useRef(!1),h=o.useCallback(()=>x.current=!1,[]);return o.useEffect(()=>()=>document.removeEventListener("pointerup",h),[h]),s.jsx(qe,{asChild:!0,...u,children:s.jsx(He.button,{"aria-describedby":l.open?l.contentId:void 0,"data-state":l.stateAttribute,...n,ref:d,onPointerMove:E(e.onPointerMove,k=>{k.pointerType!=="touch"&&!i.current&&!r.isPointerInTransitRef.current&&(l.onTriggerEnter(),i.current=!0)}),onPointerLeave:E(e.onPointerLeave,()=>{l.onTriggerLeave(),i.current=!1}),onPointerDown:E(e.onPointerDown,()=>{l.open&&l.onClose(),x.current=!0,document.addEventListener("pointerup",h,{once:!0})}),onFocus:E(e.onFocus,()=>{x.current||l.onOpen()}),onBlur:E(e.onBlur,l.onClose),onClick:E(e.onClick,l.onClose)})})});we.displayName=X;var Z="TooltipPortal",[ct,ut]=K(Z,{forceMount:void 0}),je=e=>{const{__scopeTooltip:t,forceMount:a,children:n,container:l}=e,r=A(Z,t);return s.jsx(ct,{scope:t,forceMount:a,children:s.jsx(Te,{present:a||r.open,children:s.jsx(Ke,{asChild:!0,container:l,children:n})})})};je.displayName=Z;var R="TooltipContent",Ie=o.forwardRef((e,t)=>{const a=ut(R,e.__scopeTooltip),{forceMount:n=a.forceMount,side:l="top",...r}=e,u=A(R,e.__scopeTooltip);return s.jsx(Te,{present:n||u.open,children:u.disableHoverableContent?s.jsx(Ne,{side:l,...r,ref:t}):s.jsx(dt,{side:l,...r,ref:t})})}),dt=o.forwardRef((e,t)=>{const a=A(R,e.__scopeTooltip),n=W(R,e.__scopeTooltip),l=o.useRef(null),r=xe(t,l),[u,b]=o.useState(null),{trigger:d,onClose:x}=a,i=l.current,{onPointerInTransitChange:h}=n,k=o.useCallback(()=>{b(null),h(!1)},[h]),m=o.useCallback((T,y)=>{const C=T.currentTarget,P={x:T.clientX,y:T.clientY},f=xt(P,C.getBoundingClientRect()),v=ht(P,f),j=yt(y.getBoundingClientRect()),S=kt([...v,...j]);b(S),h(!0)},[h]);return o.useEffect(()=>()=>k(),[k]),o.useEffect(()=>{if(d&&i){const T=C=>m(C,i),y=C=>m(C,d);return d.addEventListener("pointerleave",T),i.addEventListener("pointerleave",y),()=>{d.removeEventListener("pointerleave",T),i.removeEventListener("pointerleave",y)}}},[d,i,m,k]),o.useEffect(()=>{if(u){const T=y=>{const C=y.target,P={x:y.clientX,y:y.clientY},f=(d==null?void 0:d.contains(C))||(i==null?void 0:i.contains(C)),v=!bt(P,u);f?k():v&&(k(),x())};return document.addEventListener("pointermove",T),()=>document.removeEventListener("pointermove",T)}},[d,i,u,x,k]),s.jsx(Ne,{...e,ref:r})}),[pt,mt]=K(L,{isInside:!1}),gt=Ge("TooltipContent"),Ne=o.forwardRef((e,t)=>{const{__scopeTooltip:a,children:n,"aria-label":l,onEscapeKeyDown:r,onPointerDownOutside:u,...b}=e,d=A(R,a),x=U(a),{onClose:i}=d;return o.useEffect(()=>(document.addEventListener(Y,i),()=>document.removeEventListener(Y,i)),[i]),o.useEffect(()=>{if(d.trigger){const h=k=>{const m=k.target;m!=null&&m.contains(d.trigger)&&i()};return window.addEventListener("scroll",h,{capture:!0}),()=>window.removeEventListener("scroll",h,{capture:!0})}},[d.trigger,i]),s.jsx(ze,{asChild:!0,disableOutsidePointerEvents:!1,onEscapeKeyDown:r,onPointerDownOutside:u,onFocusOutside:h=>h.preventDefault(),onDismiss:i,children:s.jsxs(Qe,{"data-state":d.stateAttribute,...x,...b,ref:t,style:{...b.style,"--radix-tooltip-content-transform-origin":"var(--radix-popper-transform-origin)","--radix-tooltip-content-available-width":"var(--radix-popper-available-width)","--radix-tooltip-content-available-height":"var(--radix-popper-available-height)","--radix-tooltip-trigger-width":"var(--radix-popper-anchor-width)","--radix-tooltip-trigger-height":"var(--radix-popper-anchor-height)"},children:[s.jsx(gt,{children:n}),s.jsx(pt,{scope:a,isInside:!0,children:s.jsx(Be,{id:d.contentId,role:"tooltip",children:l||n})})]})})});Ie.displayName=R;var Ee="TooltipArrow",ft=o.forwardRef((e,t)=>{const{__scopeTooltip:a,...n}=e,l=U(a);return mt(Ee,a).isInside?null:s.jsx(Ve,{...l,...n,ref:t})});ft.displayName=Ee;function xt(e,t){const a=Math.abs(t.top-e.y),n=Math.abs(t.bottom-e.y),l=Math.abs(t.right-e.x),r=Math.abs(t.left-e.x);switch(Math.min(a,n,l,r)){case r:return"left";case l:return"right";case a:return"top";case n:return"bottom";default:throw new Error("unreachable")}}function ht(e,t,a=5){const n=[];switch(t){case"top":n.push({x:e.x-a,y:e.y+a},{x:e.x+a,y:e.y+a});break;case"bottom":n.push({x:e.x-a,y:e.y-a},{x:e.x+a,y:e.y-a});break;case"left":n.push({x:e.x+a,y:e.y-a},{x:e.x+a,y:e.y+a});break;case"right":n.push({x:e.x-a,y:e.y-a},{x:e.x-a,y:e.y+a});break}return n}function yt(e){const{top:t,right:a,bottom:n,left:l}=e;return[{x:l,y:t},{x:a,y:t},{x:a,y:n},{x:l,y:n}]}function bt(e,t){const{x:a,y:n}=e;let l=!1;for(let r=0,u=t.length-1;r<t.length;u=r++){const b=t[r],d=t[u],x=b.x,i=b.y,h=d.x,k=d.y;i>n!=k>n&&a<(h-x)*(n-i)/(k-i)+x&&(l=!l)}return l}function kt(e){const t=e.slice();return t.sort((a,n)=>a.x<n.x?-1:a.x>n.x?1:a.y<n.y?-1:a.y>n.y?1:0),vt(t)}function vt(e){if(e.length<=1)return e.slice();const t=[];for(let n=0;n<e.length;n++){const l=e[n];for(;t.length>=2;){const r=t[t.length-1],u=t[t.length-2];if((r.x-u.x)*(l.y-u.y)>=(r.y-u.y)*(l.x-u.x))t.pop();else break}t.push(l)}t.pop();const a=[];for(let n=e.length-1;n>=0;n--){const l=e[n];for(;a.length>=2;){const r=a[a.length-1],u=a[a.length-2];if((r.x-u.x)*(l.y-u.y)>=(r.y-u.y)*(l.x-u.x))a.pop();else break}a.push(l)}return a.pop(),t.length===1&&a.length===1&&t[0].x===a[0].x&&t[0].y===a[0].y?t:t.concat(a)}var Tt=Se,Ct=Pe,St=we,Pt=je,Me=Ie;const wt=Tt,G=Ct,V=St,F=o.forwardRef(({className:e,sideOffset:t=4,...a},n)=>s.jsx(Pt,{children:s.jsx(Me,{ref:n,sideOffset:t,className:ve("z-[var(--z-tooltip)] overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",e),...a})}));F.displayName=Me.displayName;function z(e){return e==="plugin"?"plugins":"skills"}async function jt(e){var r,u;const t=new URLSearchParams,a=z(e.type);(r=e.q)!=null&&r.trim()&&t.set("q",e.q.trim()),(u=e.tag)!=null&&u.trim()&&t.set("tag",e.tag.trim()),e.sort&&t.set("sort",e.sort),typeof e.page=="number"&&Number.isFinite(e.page)&&t.set("page",String(Math.max(1,Math.trunc(e.page)))),typeof e.pageSize=="number"&&Number.isFinite(e.pageSize)&&t.set("pageSize",String(Math.max(1,Math.trunc(e.pageSize))));const n=t.toString(),l=await H.get(n?`/api/marketplace/${a}/items?${n}`:`/api/marketplace/${a}/items`);if(!l.ok)throw new Error(l.error.message);return l.data}async function It(e){const t=z(e.type),a=await H.post(`/api/marketplace/${t}/install`,e);if(!a.ok)throw new Error(a.error.message);return a.data}async function Nt(e){const t=z(e),a=await H.get(`/api/marketplace/${t}/installed`);if(!a.ok)throw new Error(a.error.message);return a.data}async function Et(e){const t=z(e.type),a=await H.post(`/api/marketplace/${t}/manage`,e);if(!a.ok)throw new Error(a.error.message);return a.data}function Mt(e){return ye({queryKey:["marketplace-items",e],queryFn:()=>jt(e),staleTime:15e3})}function Rt(e){return ye({queryKey:["marketplace-installed",e],queryFn:()=>Nt(e),staleTime:1e4})}function Dt(){const e=be();return ke({mutationFn:t=>It(t),onSuccess:t=>{e.invalidateQueries({queryKey:["marketplace-installed",t.type]}),e.refetchQueries({queryKey:["marketplace-installed",t.type],type:"active"}),e.refetchQueries({queryKey:["marketplace-items"],type:"active"});const a=t.type==="plugin"?c("marketplaceInstallSuccessPlugin"):c("marketplaceInstallSuccessSkill");q.success(t.message||a)},onError:t=>{q.error(t.message||c("marketplaceInstallFailed"))}})}function Lt(){const e=be();return ke({mutationFn:t=>Et(t),onSuccess:t=>{e.invalidateQueries({queryKey:["marketplace-installed",t.type]}),e.invalidateQueries({queryKey:["marketplace-items"]}),e.refetchQueries({queryKey:["marketplace-installed",t.type],type:"active"}),e.refetchQueries({queryKey:["marketplace-items"],type:"active"});const a=t.action==="enable"?c("marketplaceEnableSuccess"):t.action==="disable"?c("marketplaceDisableSuccess"):c("marketplaceUninstallSuccess");q.success(t.message||a)},onError:t=>{q.error(t.message||c("marketplaceOperationFailed"))}})}const At=12;function J(e){return(e??"").trim().toLowerCase()}function M(e,t){const a=J(t);return a.length>0?`${e}:${a}`:""}function _t(e){const t=new Map;for(const a of e){const n=[a.install.spec,a.slug,a.id];for(const l of n){const r=M(a.type,l);!r||t.has(r)||t.set(r,a)}}return t}function Ot(e){const t=new Map;for(const a of e){const n=[a.spec,a.id,a.label];for(const l of n){const r=M(a.type,l);!r||t.has(r)||t.set(r,a)}}return t}function $t(e,t){const a=[e.install.spec,e.slug,e.id];for(const n of a){const l=M(e.type,n);if(!l)continue;const r=t.get(l);if(r)return r}}function Ft(e,t){const a=t.get(M(e.type,e.spec));if(a)return a;const n=t.get(M(e.type,e.id));return n||t.get(M(e.type,e.label))}function qt(e,t,a){const n=J(a);return n?[e.id,e.spec,e.label,t==null?void 0:t.name,t==null?void 0:t.slug,t==null?void 0:t.summary,...(t==null?void 0:t.tags)??[]].map(r=>J(r)).filter(Boolean).some(r=>r.includes(n)):!0}function Ht(e){const t=["bg-amber-600","bg-orange-500","bg-yellow-600","bg-emerald-600","bg-teal-600","bg-cyan-600","bg-stone-600","bg-rose-500","bg-violet-500"];let a=0;for(let n=0;n<e.length;n++)a=e.charCodeAt(n)+((a<<5)-a);return t[Math.abs(a)%t.length]}function Kt({name:e,fallback:t}){const a=e||t,n=a.substring(0,2).toUpperCase(),l=Ht(a);return s.jsx("div",{className:ve("flex items-center justify-center w-10 h-10 rounded-xl text-white font-semibold text-sm shrink-0",l),children:n})}function Ut(e){return s.jsx("div",{className:"mb-4",children:s.jsxs("div",{className:"flex gap-3 items-center",children:[s.jsxs("div",{className:"flex-1 min-w-0 relative",children:[s.jsx(Je,{className:"h-4 w-4 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2"}),s.jsx("input",{value:e.searchText,onChange:t=>e.onSearchTextChange(t.target.value),placeholder:e.searchPlaceholder,className:"w-full h-9 border border-gray-200/80 rounded-xl pl-9 pr-3 text-sm focus:outline-none focus:ring-1 focus:ring-primary/40"})]}),e.scope==="all"&&s.jsxs(We,{value:e.sort,onValueChange:t=>e.onSortChange(t),children:[s.jsx(Ze,{className:"h-9 w-[150px] shrink-0 rounded-lg",children:s.jsx(et,{})}),s.jsxs(tt,{children:[s.jsx(ge,{value:"relevance",children:c("marketplaceSortRelevance")}),s.jsx(ge,{value:"updated",children:c("marketplaceSortUpdated")})]})]})]})})}function fe(e){var P,f,v,j,S;const t=e.record,a=(t==null?void 0:t.type)==="plugin"?t:void 0,n=((P=e.item)==null?void 0:P.type)??(t==null?void 0:t.type),l=((f=e.item)==null?void 0:f.name)??(t==null?void 0:t.label)??(t==null?void 0:t.id)??(t==null?void 0:t.spec)??c("marketplaceUnknownItem"),r=((v=e.item)==null?void 0:v.summary)??(t?c("marketplaceInstalledLocalSummary"):""),u=((j=e.item)==null?void 0:j.install.spec)??(t==null?void 0:t.spec)??"",b=(t==null?void 0:t.id)||(t==null?void 0:t.spec),d=!!b&&e.manageState.isPending&&e.manageState.targetId===b,x=!!a,i=(t==null?void 0:t.type)==="plugin"&&t.origin!=="bundled",h=(t==null?void 0:t.type)==="skill"&&t.source==="workspace",k=!!(i||h),m=t?t.enabled===!1||t.runtimeStatus==="disabled":!1,T=(S=e.item)==null?void 0:S.install.spec,y=typeof T=="string"&&e.installState.installingSpecs.has(T),C=n==="plugin"?c("marketplaceTypePlugin"):n==="skill"?c("marketplaceTypeSkill"):c("marketplaceTypeExtension");return s.jsxs("article",{className:"group bg-white border border-gray-200/40 hover:border-gray-200/80 rounded-2xl px-5 py-4 hover:shadow-md shadow-sm transition-all flex items-start gap-3.5 justify-between cursor-default",children:[s.jsxs("div",{className:"flex gap-3 min-w-0 flex-1 h-full items-start",children:[s.jsx(Kt,{name:l,fallback:u||c("marketplaceTypeExtension")}),s.jsx("div",{className:"min-w-0 flex-1 flex flex-col justify-center h-full",children:s.jsxs(wt,{delayDuration:400,children:[s.jsxs(G,{children:[s.jsx(V,{asChild:!0,children:s.jsx("div",{className:"text-[14px] font-semibold text-gray-900 truncate leading-tight cursor-default",children:l})}),s.jsx(F,{className:"max-w-[300px] text-xs",children:l})]}),s.jsxs("div",{className:"flex items-center gap-1.5 mt-0.5 mb-1.5",children:[s.jsx("span",{className:"text-[11px] text-gray-500 font-medium",children:C}),u&&s.jsxs(s.Fragment,{children:[s.jsx("span",{className:"text-[10px] text-gray-300",children:"•"}),s.jsxs(G,{children:[s.jsx(V,{asChild:!0,children:s.jsx("span",{className:"text-[11px] text-gray-400 truncate max-w-full font-mono cursor-default",children:u})}),s.jsx(F,{className:"max-w-[300px] text-xs font-mono break-all",children:u})]})]})]}),s.jsxs(G,{children:[s.jsx(V,{asChild:!0,children:s.jsx("p",{className:"text-[12px] text-gray-500/90 line-clamp-1 transition-colors leading-relaxed text-left cursor-default",children:r})}),r&&s.jsx(F,{className:"max-w-[400px] text-xs leading-relaxed",children:r})]})]})})]}),s.jsxs("div",{className:"shrink-0 flex items-center h-full",children:[e.item&&!t&&s.jsx("button",{onClick:()=>e.onInstall(e.item),disabled:y,className:"inline-flex items-center gap-1.5 h-8 px-4 rounded-xl text-xs font-medium bg-primary text-white hover:bg-primary-600 disabled:opacity-50 transition-colors",children:y?c("marketplaceInstalling"):c("marketplaceInstall")}),a&&x&&s.jsx("button",{disabled:e.manageState.isPending,onClick:()=>e.onManage(m?"enable":"disable",a),className:"inline-flex items-center h-8 px-4 rounded-xl text-xs font-medium border border-gray-200/80 text-gray-600 bg-white hover:bg-gray-50 hover:border-gray-300 disabled:opacity-50 transition-colors",children:d&&e.manageState.action!=="uninstall"?e.manageState.action==="enable"?c("marketplaceEnabling"):c("marketplaceDisabling"):m?c("marketplaceEnable"):c("marketplaceDisable")}),t&&k&&s.jsx("button",{disabled:e.manageState.isPending,onClick:()=>e.onManage("uninstall",t),className:"inline-flex items-center h-8 px-4 rounded-xl text-xs font-medium border border-rose-100 text-rose-500 bg-white hover:bg-rose-50 hover:border-rose-200 disabled:opacity-50 transition-colors",children:d&&e.manageState.action==="uninstall"?c("marketplaceRemoving"):c("marketplaceUninstall")})]})]})}function zt(e){return s.jsxs("div",{className:"mt-4 flex items-center justify-end gap-2",children:[s.jsx("button",{className:"h-8 px-3 rounded-xl border border-gray-200/80 text-sm text-gray-600 disabled:opacity-40",onClick:e.onPrev,disabled:e.page<=1||e.busy,children:c("prev")}),s.jsx("div",{className:"text-sm text-gray-600 min-w-20 text-center",children:e.totalPages===0?"0 / 0":`${e.page} / ${e.totalPages}`}),s.jsx("button",{className:"h-8 px-3 rounded-xl border border-gray-200/80 text-sm text-gray-600 disabled:opacity-40",onClick:e.onNext,disabled:e.totalPages===0||e.page>=e.totalPages||e.busy,children:c("next")})]})}function Xt(){var le,oe,ie,ce,ue,de,pe,me;const e=Ye(),t=Xe(),a=o.useMemo(()=>t.type==="plugins"||t.type==="skills"?t.type:null,[t.type]);o.useEffect(()=>{a||e("/marketplace/plugins",{replace:!0})},[a,e]);const n=a==="skills"?"skill":"plugin",r=n==="plugin"?{pageTitle:"marketplacePluginsPageTitle",pageDescription:"marketplacePluginsPageDescription",tabMarketplace:"marketplaceTabMarketplacePlugins",tabInstalled:"marketplaceTabInstalledPlugins",searchPlaceholder:"marketplaceSearchPlaceholderPlugins",sectionCatalog:"marketplaceSectionPlugins",sectionInstalled:"marketplaceSectionInstalledPlugins",errorLoadData:"marketplaceErrorLoadingPluginsData",errorLoadInstalled:"marketplaceErrorLoadingInstalledPlugins",emptyData:"marketplaceNoPlugins",emptyInstalled:"marketplaceNoInstalledPlugins",installedCountSuffix:"marketplaceInstalledPluginsCountSuffix"}:{pageTitle:"marketplaceSkillsPageTitle",pageDescription:"marketplaceSkillsPageDescription",tabMarketplace:"marketplaceTabMarketplaceSkills",tabInstalled:"marketplaceTabInstalledSkills",searchPlaceholder:"marketplaceSearchPlaceholderSkills",sectionCatalog:"marketplaceSectionSkills",sectionInstalled:"marketplaceSectionInstalledSkills",errorLoadData:"marketplaceErrorLoadingSkillsData",errorLoadInstalled:"marketplaceErrorLoadingInstalledSkills",emptyData:"marketplaceNoSkills",emptyInstalled:"marketplaceNoInstalledSkills",installedCountSuffix:"marketplaceInstalledSkillsCountSuffix"},[u,b]=o.useState(""),[d,x]=o.useState(""),[i,h]=o.useState("all"),[k,m]=o.useState("relevance"),[T,y]=o.useState(1),[C,P]=o.useState(new Set);o.useEffect(()=>{const p=setTimeout(()=>{y(1),x(u.trim())},250);return()=>clearTimeout(p)},[u]),o.useEffect(()=>{y(1)},[n]);const f=Rt(n),v=Mt({q:d||void 0,type:n,sort:k,page:T,pageSize:At}),j=Dt(),S=Lt(),{confirm:_,ConfirmDialog:O}=nt(),$=o.useMemo(()=>{var p;return((p=f.data)==null?void 0:p.records)??[]},[(le=f.data)==null?void 0:le.records]),N=o.useMemo(()=>{var p;return((p=v.data)==null?void 0:p.items)??[]},[(oe=v.data)==null?void 0:oe.items]),ee=o.useMemo(()=>_t(N),[N]),Re=o.useMemo(()=>Ot($),[$]),D=o.useMemo(()=>{const p=$.filter(g=>g.type===n).map(g=>({key:`${g.type}:${g.spec}:${g.id??""}`,record:g,item:Ft(g,ee)})).filter(g=>qt(g.record,g.item,d));return p.sort((g,w)=>{const I=g.record.installedAt?Date.parse(g.record.installedAt):Number.NaN,B=w.record.installedAt?Date.parse(w.record.installedAt):Number.NaN,Ae=!Number.isNaN(I),_e=!Number.isNaN(B);return Ae&&_e&&I!==B?B-I:g.record.spec.localeCompare(w.record.spec)}),p},[$,n,ee,d]),te=i==="installed"?D.length:((ie=v.data)==null?void 0:ie.total)??0,Q=i==="installed"?1:((ce=v.data)==null?void 0:ce.totalPages)??0,De=o.useMemo(()=>i==="installed"?f.isLoading?c("loading"):`${D.length} ${c(r.installedCountSuffix)}`:v.data?`${N.length} / ${te}`:c("loading"),[i,f.isLoading,D.length,v.data,N.length,te,r.installedCountSuffix]),ae={installingSpecs:C},ne={isPending:S.isPending,targetId:((ue=S.variables)==null?void 0:ue.id)||((de=S.variables)==null?void 0:de.spec),action:(pe=S.variables)==null?void 0:pe.action},Le=[{id:"all",label:c(r.tabMarketplace)},{id:"installed",label:c(r.tabInstalled),count:((me=f.data)==null?void 0:me.total)??0}],se=async p=>{const g=p.install.spec;if(!C.has(g)){P(w=>{const I=new Set(w);return I.add(g),I});try{await j.mutateAsync({type:p.type,spec:g,kind:p.install.kind,...p.type==="skill"?{skill:p.slug,installPath:`skills/${p.slug}`}:{}})}catch{}finally{P(w=>{if(!w.has(g))return w;const I=new Set(w);return I.delete(g),I})}}},re=async(p,g)=>{if(S.isPending)return;const w=g.id||g.spec;w&&(p==="uninstall"&&!await _({title:`${c("marketplaceUninstallTitle")} ${w}?`,description:c("marketplaceUninstallDescription"),confirmLabel:c("marketplaceUninstall"),variant:"destructive"})||S.mutate({type:g.type,action:p,id:w,spec:g.spec}))};return s.jsxs(st,{children:[s.jsx(rt,{title:c(r.pageTitle),description:c(r.pageDescription)}),s.jsx(at,{tabs:Le,activeTab:i,onChange:p=>{h(p),y(1)},className:"mb-4"}),s.jsx(Ut,{scope:i,searchText:u,searchPlaceholder:c(r.searchPlaceholder),sort:k,onSearchTextChange:b,onSortChange:p=>{y(1),m(p)}}),s.jsxs("section",{children:[s.jsxs("div",{className:"flex items-center justify-between mb-3",children:[s.jsx("h3",{className:"text-[14px] font-semibold text-gray-900",children:i==="installed"?c(r.sectionInstalled):c(r.sectionCatalog)}),s.jsx("span",{className:"text-[12px] text-gray-500",children:De})]}),i==="all"&&v.isError&&s.jsxs("div",{className:"p-4 rounded-xl bg-rose-50 border border-rose-200 text-rose-700 text-sm",children:[c(r.errorLoadData),": ",v.error.message]}),i==="installed"&&f.isError&&s.jsxs("div",{className:"p-4 rounded-xl bg-rose-50 border border-rose-200 text-rose-700 text-sm",children:[c(r.errorLoadInstalled),": ",f.error.message]}),s.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-2 2xl:grid-cols-3 gap-3",children:[i==="all"&&N.map(p=>s.jsx(fe,{item:p,record:$t(p,Re),installState:ae,manageState:ne,onInstall:se,onManage:re},p.id)),i==="installed"&&D.map(p=>s.jsx(fe,{item:p.item,record:p.record,installState:ae,manageState:ne,onInstall:se,onManage:re},p.key))]}),i==="all"&&!v.isLoading&&!v.isError&&N.length===0&&s.jsx("div",{className:"text-[13px] text-gray-500 py-8 text-center",children:c(r.emptyData)}),i==="installed"&&!f.isLoading&&!f.isError&&D.length===0&&s.jsx("div",{className:"text-[13px] text-gray-500 py-8 text-center",children:c(r.emptyInstalled)})]}),i==="all"&&s.jsx(zt,{page:T,totalPages:Q,busy:v.isFetching,onPrev:()=>y(p=>Math.max(1,p-1)),onNext:()=>y(p=>Q>0?Math.min(Q,p+1):p+1)}),s.jsx(O,{})]})}export{Xt as MarketplacePage};