@klarkxy/dsh-web-search-manager 0.2.5 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -10
- package/docs/README.zh-CN.md +14 -8
- package/lib/client.inner.cjs +513 -491
- package/lib/client.inner.cjs.map +1 -1
- package/lib/client.js +513 -491
- package/lib/contracts.d.ts +7 -1
- package/lib/contracts.js +6 -1
- package/lib/contracts.js.map +1 -1
- package/lib/index.d.ts +7 -2
- package/lib/index.js +287 -62
- package/lib/index.js.map +1 -1
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +15 -12
package/lib/client.inner.cjs
CHANGED
|
@@ -219,6 +219,17 @@ ${scope} {
|
|
|
219
219
|
/* --- Layout scaffolding. --- */
|
|
220
220
|
& .dsh-ui-stack { display: flex; flex-direction: column; gap: 12px; }
|
|
221
221
|
& .dsh-ui-stack-lg { display: flex; flex-direction: column; gap: 20px; }
|
|
222
|
+
/* Plugin detail pages share one reading column. These are layout recipes;
|
|
223
|
+
* controls keep their own primitive styles. Form spacing follows the host's
|
|
224
|
+
* settings-form/fields.module.css and SettingsForm.module.css. */
|
|
225
|
+
& .dsh-ui-page { display: flex; flex-direction: column; gap: 16px; width: 100%; max-width: 760px; min-width: 0; }
|
|
226
|
+
& .dsh-ui-form { display: flex; flex-direction: column; gap: 0; min-width: 0; }
|
|
227
|
+
&.dsh-ui-form > .dsh-ui-field, & .dsh-ui-form > .dsh-ui-field { padding: 12px 0; }
|
|
228
|
+
& .dsh-ui-form-footer { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding-top: 16px; min-width: 0; }
|
|
229
|
+
& .dsh-ui-fields-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 20px; min-width: 0; margin: 0; padding: 0; border: 0; }
|
|
230
|
+
@media (max-width: 560px) {
|
|
231
|
+
& .dsh-ui-fields-grid { grid-template-columns: minmax(0, 1fr); }
|
|
232
|
+
}
|
|
222
233
|
& .dsh-ui-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
223
234
|
& .dsh-ui-row-wrap { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
|
|
224
235
|
& .dsh-ui-spacer { flex: 1; min-width: 0; }
|
|
@@ -246,6 +257,9 @@ ${scope} {
|
|
|
246
257
|
* official primitive, so this only owns the vertical rhythm. --- */
|
|
247
258
|
& .dsh-ui-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
|
|
248
259
|
& .dsh-ui-field + .dsh-ui-field { border-top: 0.5px solid var(--dsw-alias-border-l2); padding-top: 12px; }
|
|
260
|
+
/* Grid fields share a row, so the stacked-field divider must not win later in
|
|
261
|
+
* the cascade. Reset it here, after the field recipe. */
|
|
262
|
+
& .dsh-ui-fields-grid > .dsh-ui-field { padding: 0; border: 0; }
|
|
249
263
|
& .dsh-ui-label { display: flex; align-items: center; gap: 6px; font-size: 13px; line-height: 20px; font-weight: 500; color: var(--dsw-alias-label-primary); }
|
|
250
264
|
& .dsh-ui-label-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
|
|
251
265
|
& .dsh-ui-help { font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); }
|
|
@@ -436,9 +450,87 @@ ${scope} {
|
|
|
436
450
|
& *, & *::before, & *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
|
|
437
451
|
}
|
|
438
452
|
}
|
|
439
|
-
`.replace(/^& (\.dsh-ui-[a-z0-9-]+)(
|
|
453
|
+
`.replace(/^& (\.dsh-ui-[a-z0-9-]+)(?=\s*\{)/gm, (_match, className) => `&${className}, & ${className}`);
|
|
440
454
|
}
|
|
441
455
|
//#endregion
|
|
456
|
+
//#region src/client-styles.ts
|
|
457
|
+
/**
|
|
458
|
+
* The shared contract paints this page: the type tiers, the card, the field,
|
|
459
|
+
* the button row, the notice, the focus ring and the state colours. What is
|
|
460
|
+
* left here is only the geometry the contract does not cover — the reading
|
|
461
|
+
* width of the panel, the ranked three-column row, the drag affordance, the
|
|
462
|
+
* drop indicator, the screen-reader-only announcer and the limits grid.
|
|
463
|
+
*
|
|
464
|
+
* Nothing below declares a colour of its own beyond naming a host token, and
|
|
465
|
+
* nothing carries a literal fallback: the host publishes both themes and a
|
|
466
|
+
* literal would pin one of them.
|
|
467
|
+
*
|
|
468
|
+
* Every plugin rule nests under the plugin root, as the contract does, so a
|
|
469
|
+
* .ws-* or .web-search-* class never paints a neighbouring plugin. Selectors
|
|
470
|
+
* that start with an element name keep an explicit `&`, the form every
|
|
471
|
+
* nesting implementation accepts.
|
|
472
|
+
*/
|
|
473
|
+
const webSearchClientStyles = `${officialUiCss("ws-root")}
|
|
474
|
+
.ws-root {
|
|
475
|
+
|
|
476
|
+
.ws-card-head { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding-bottom: 8px; }
|
|
477
|
+
.ws-sr-only {
|
|
478
|
+
position: absolute;
|
|
479
|
+
width: 1px;
|
|
480
|
+
height: 1px;
|
|
481
|
+
padding: 0;
|
|
482
|
+
margin: -1px;
|
|
483
|
+
overflow: hidden;
|
|
484
|
+
clip-path: inset(50%);
|
|
485
|
+
white-space: nowrap;
|
|
486
|
+
border: 0;
|
|
487
|
+
}
|
|
488
|
+
.ws-provider-note { display: flex; align-items: center; gap: 6px; }
|
|
489
|
+
.web-search-provider { display: grid; gap: 8px; min-width: 0; }
|
|
490
|
+
.web-search-provider-info { display: grid; gap: 3px; font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-secondary); overflow-wrap: anywhere; }
|
|
491
|
+
.web-search-provider-heading { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; min-height: 24px; }
|
|
492
|
+
& a { color: var(--dsw-alias-state-business-primary); font-size: 12px; line-height: 18px; text-underline-offset: 3px; }
|
|
493
|
+
/* The primitive renders a 32px control in an inline-flex wrapper; a settings
|
|
494
|
+
* field needs it to fill the column it sits in. */
|
|
495
|
+
.ws-input { width: 100%; }
|
|
496
|
+
.web-search-rank { margin: 0; padding: 0; list-style: none; }
|
|
497
|
+
.web-search-rank li {
|
|
498
|
+
position: relative;
|
|
499
|
+
display: grid;
|
|
500
|
+
grid-template-columns: 24px minmax(0, 1fr) auto;
|
|
501
|
+
gap: 10px;
|
|
502
|
+
align-items: start;
|
|
503
|
+
padding: 14px 0;
|
|
504
|
+
border-top: 0.5px solid var(--dsw-alias-border-l2);
|
|
505
|
+
}
|
|
506
|
+
& button.web-search-drag-handle {
|
|
507
|
+
display: grid;
|
|
508
|
+
place-items: center;
|
|
509
|
+
width: 24px;
|
|
510
|
+
height: 24px;
|
|
511
|
+
min-height: 24px;
|
|
512
|
+
padding: 0;
|
|
513
|
+
cursor: grab;
|
|
514
|
+
opacity: 0.65;
|
|
515
|
+
}
|
|
516
|
+
& button.web-search-drag-handle:hover { opacity: 1; }
|
|
517
|
+
& button.web-search-drag-handle:active { cursor: grabbing; }
|
|
518
|
+
& button.web-search-drag-handle[aria-disabled='true'] { cursor: wait; opacity: 0.4; }
|
|
519
|
+
.web-search-rank li[data-dragging] { opacity: 0.45; }
|
|
520
|
+
.web-search-rank li[data-drop]::after { content: ''; position: absolute; left: 0; right: 0; height: 2px; background: var(--dsw-alias-state-business-primary); pointer-events: none; }
|
|
521
|
+
.web-search-rank li[data-drop='before']::after { top: -1px; }
|
|
522
|
+
.web-search-rank li[data-drop='after']::after { bottom: -1px; }
|
|
523
|
+
.web-search-rank-index { color: var(--dsw-alias-label-tertiary); font-variant-numeric: tabular-nums; line-height: 24px; }
|
|
524
|
+
.web-search-rank-move { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; justify-content: flex-end; padding-top: 2px; }
|
|
525
|
+
.ws-danger:hover:not(:disabled),
|
|
526
|
+
.ws-danger[data-armed] { color: var(--dsw-alias-state-error-primary); border-color: var(--dsw-alias-state-error-primary); }
|
|
527
|
+
.ws-danger:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover-danger); }
|
|
528
|
+
@media (max-width: 560px) {
|
|
529
|
+
.web-search-rank-move { flex-direction: column; align-items: flex-end; }
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
`;
|
|
533
|
+
//#endregion
|
|
442
534
|
//#region ../dsh-plugin-kit/src/client-utils.ts
|
|
443
535
|
/** Fixed pause for failing background reads; manual work is never replayed. */
|
|
444
536
|
function createPluginReadGate(now = Date.now) {
|
|
@@ -467,8 +559,8 @@ function createPluginReadGate(now = Date.now) {
|
|
|
467
559
|
//#endregion
|
|
468
560
|
//#region src/contracts.ts
|
|
469
561
|
const WEB_SEARCH_RPC_CHANNEL = "/web-search-manager";
|
|
470
|
-
function
|
|
471
|
-
return
|
|
562
|
+
function providerKey(kind, id) {
|
|
563
|
+
return `${kind}:${id}`;
|
|
472
564
|
}
|
|
473
565
|
function safeConfigurationUrl(value) {
|
|
474
566
|
if (typeof value !== "string" || !value || /[\u0000-\u0020\\]/.test(value)) return;
|
|
@@ -485,6 +577,21 @@ function safeConfigurationUrl(value) {
|
|
|
485
577
|
//#region src/provider-info.ts
|
|
486
578
|
const PROVIDER_PRICING_CHECKED = "2026-09-21";
|
|
487
579
|
const providerInfo = {
|
|
580
|
+
perplexity: {
|
|
581
|
+
description: "通过 Perplexity Search API 获取网页来源,不生成回答。",
|
|
582
|
+
pricing: "按 Search API 请求计费;实际价格与额度以官方账户为准。",
|
|
583
|
+
pricingUrl: "https://docs.perplexity.ai/docs/getting-started/pricing"
|
|
584
|
+
},
|
|
585
|
+
you: {
|
|
586
|
+
description: "通过 You.com 获取网页与新闻结果,不请求额外全文提取。",
|
|
587
|
+
pricing: "按 Web Search API 请求计费;实际价格与额度以官方账户为准。",
|
|
588
|
+
pricingUrl: "https://you.com/docs/administration/billing"
|
|
589
|
+
},
|
|
590
|
+
"firecrawl-fetch": {
|
|
591
|
+
description: "读取网页 Markdown;与 Firecrawl 搜索共用 Key。",
|
|
592
|
+
pricing: "网页读取使用 Firecrawl 积分,与搜索分别计费;实际消耗以官方账户为准。",
|
|
593
|
+
pricingUrl: "https://www.firecrawl.dev/pricing"
|
|
594
|
+
},
|
|
488
595
|
ddg: {
|
|
489
596
|
description: "搜索 DuckDuckGo 公开网页,无需注册或 API Key。",
|
|
490
597
|
pricing: "免费使用,不产生搜索 API 费用。",
|
|
@@ -527,6 +634,18 @@ const providerInfo = {
|
|
|
527
634
|
}
|
|
528
635
|
};
|
|
529
636
|
const englishInfo = {
|
|
637
|
+
perplexity: {
|
|
638
|
+
description: "Get web sources through the Perplexity Search API without generating an answer.",
|
|
639
|
+
pricing: "Charged per Search API request; current prices and credits depend on your official account."
|
|
640
|
+
},
|
|
641
|
+
you: {
|
|
642
|
+
description: "Get web and news results through You.com without requesting extra full-page extraction.",
|
|
643
|
+
pricing: "Charged per Web Search API request; current prices and credits depend on your official account."
|
|
644
|
+
},
|
|
645
|
+
"firecrawl-fetch": {
|
|
646
|
+
description: "Read page Markdown using the same key as Firecrawl search.",
|
|
647
|
+
pricing: "Page reads use Firecrawl credits and are charged separately from search; your official account determines usage."
|
|
648
|
+
},
|
|
530
649
|
ddg: {
|
|
531
650
|
description: "Search public DuckDuckGo pages without registration or an API key.",
|
|
532
651
|
pricing: "Free to use; no search API fees."
|
|
@@ -561,6 +680,9 @@ const englishInfo = {
|
|
|
561
680
|
}
|
|
562
681
|
};
|
|
563
682
|
const bundledDescriptions = {
|
|
683
|
+
perplexity: ["Search API;返回网页来源,不生成回答。", "Search API returning web sources without generating an answer."],
|
|
684
|
+
you: ["网页与新闻搜索;不额外提取全文。", "Web and news search without extra full-page extraction."],
|
|
685
|
+
"firecrawl-fetch": ["读取网页 Markdown;与 Firecrawl 搜索共用 Key。", "Read page Markdown using the same key as Firecrawl search."],
|
|
564
686
|
ddg: ["无需注册。默认后端。", "No registration required. Default backend."],
|
|
565
687
|
"deepseek-official": ["使用已配置的 DeepSeek API Key,可能产生额外搜索费用。", "Uses the configured DeepSeek API key; additional search fees may apply."],
|
|
566
688
|
exa: ["独立 Search API。", "Independent Search API."],
|
|
@@ -580,6 +702,7 @@ function providerDisplayLabel(provider, locale) {
|
|
|
580
702
|
}
|
|
581
703
|
function bundledCatalogId(provider) {
|
|
582
704
|
if (!(typeof provider.configurationOwned === "boolean" ? provider.configurationOwned : !provider.configurationOwner || provider.configurationOwner === "@klarkxy/dsh-web-search-manager")) return;
|
|
705
|
+
if (provider.kind === "fetch" && provider.id === "firecrawl") return "firecrawl-fetch";
|
|
583
706
|
return provider.id === "deepseek-managed" ? "deepseek-official" : provider.id;
|
|
584
707
|
}
|
|
585
708
|
function missingPricing(locale) {
|
|
@@ -588,7 +711,11 @@ function missingPricing(locale) {
|
|
|
588
711
|
function usesBundledPricing(provider) {
|
|
589
712
|
if (provider.pricing?.trim()) return false;
|
|
590
713
|
const catalogId = bundledCatalogId(provider);
|
|
591
|
-
return Boolean(catalogId &&
|
|
714
|
+
return Boolean(catalogId && ![
|
|
715
|
+
"perplexity",
|
|
716
|
+
"you",
|
|
717
|
+
"firecrawl-fetch"
|
|
718
|
+
].includes(catalogId) && Object.hasOwn(providerInfo, catalogId));
|
|
592
719
|
}
|
|
593
720
|
function searchProviderInfo(provider, locale = "zh") {
|
|
594
721
|
const catalogId = bundledCatalogId(provider);
|
|
@@ -685,52 +812,61 @@ function bindStatusRefresh(options) {
|
|
|
685
812
|
};
|
|
686
813
|
}
|
|
687
814
|
function isProviderOn(status, provider) {
|
|
688
|
-
if (provider.kind === "fetch") return status.fetchActive && status
|
|
815
|
+
if (provider.kind === "fetch") return status.fetchActive && selectedBackend(status, "fetch")?.id === provider.id;
|
|
689
816
|
return status.searchActive && selectedSearchBackend(status)?.id === provider.id;
|
|
690
817
|
}
|
|
691
818
|
function dormantSearchPlaceholder(id) {
|
|
819
|
+
return dormantPlaceholder(id, "search");
|
|
820
|
+
}
|
|
821
|
+
function dormantPlaceholder(id, kind) {
|
|
692
822
|
return {
|
|
693
823
|
id,
|
|
694
824
|
label: id,
|
|
695
825
|
description: "",
|
|
696
826
|
billing: "unknown",
|
|
697
|
-
kind
|
|
827
|
+
kind,
|
|
698
828
|
configured: false,
|
|
699
829
|
calls: 0,
|
|
700
830
|
failures: 0,
|
|
701
831
|
configurationOwned: false
|
|
702
832
|
};
|
|
703
833
|
}
|
|
834
|
+
function isRegistered(status, id, kind) {
|
|
835
|
+
return status.providers.some((provider) => provider.kind === kind && provider.id === id);
|
|
836
|
+
}
|
|
704
837
|
function isRegisteredSearch(status, id) {
|
|
705
|
-
return status
|
|
838
|
+
return isRegistered(status, id, "search");
|
|
706
839
|
}
|
|
707
|
-
function
|
|
708
|
-
|
|
840
|
+
function providerOrder(settings, kind) {
|
|
841
|
+
return kind === "search" ? settings.searchOrder : settings.fetchOrder;
|
|
842
|
+
}
|
|
843
|
+
function catalogBackends(status, kind) {
|
|
844
|
+
const byId = new Map(status.providers.filter((provider) => provider.kind === kind).map((provider) => [provider.id, provider]));
|
|
709
845
|
const rows = [];
|
|
710
846
|
const seen = /* @__PURE__ */ new Set();
|
|
711
|
-
for (const id of status.settings
|
|
847
|
+
for (const id of providerOrder(status.settings, kind)) {
|
|
712
848
|
if (!id || seen.has(id)) continue;
|
|
713
849
|
seen.add(id);
|
|
714
|
-
rows.push(byId.get(id) ??
|
|
715
|
-
}
|
|
716
|
-
for (const provider of status.providers) {
|
|
717
|
-
if (provider.kind !== "search" || seen.has(provider.id)) continue;
|
|
718
|
-
rows.push(provider);
|
|
850
|
+
rows.push(byId.get(id) ?? dormantPlaceholder(id, kind));
|
|
719
851
|
}
|
|
852
|
+
for (const provider of byId.values()) if (!seen.has(provider.id)) rows.push(provider);
|
|
720
853
|
return rows;
|
|
721
854
|
}
|
|
855
|
+
function catalogSearchBackends(status) {
|
|
856
|
+
return catalogBackends(status, "search");
|
|
857
|
+
}
|
|
722
858
|
function searchBackends(status) {
|
|
723
|
-
|
|
724
|
-
|
|
859
|
+
return catalogSearchBackends(status).filter((provider) => status.settings.searchOrder.includes(provider.id));
|
|
860
|
+
}
|
|
861
|
+
function selectedBackend(status, kind) {
|
|
862
|
+
return providerOrder(status.settings, kind).flatMap((id) => status.providers.find((provider) => provider.kind === kind && provider.id === id) ?? []).find((provider) => provider.configured);
|
|
725
863
|
}
|
|
726
864
|
function selectedSearchBackend(status) {
|
|
727
|
-
|
|
728
|
-
const id = pickActiveSearch(backends.map((provider) => provider.id), (id) => Boolean(backends.find((provider) => provider.id === id)?.configured));
|
|
729
|
-
return backends.find((provider) => provider.id === id);
|
|
865
|
+
return selectedBackend(status, "search");
|
|
730
866
|
}
|
|
731
|
-
function droppedDormantIds(settings, order, providers) {
|
|
732
|
-
const known = new Set(providers.filter((provider) => provider.kind ===
|
|
733
|
-
return [...new Set(settings.
|
|
867
|
+
function droppedDormantIds(settings, order, providers, kind = "search") {
|
|
868
|
+
const known = new Set(providers.filter((provider) => provider.kind === kind).map((provider) => provider.id));
|
|
869
|
+
return [...new Set(providerOrder(settings, kind).filter((id) => id && !known.has(id) && !order.includes(id)))];
|
|
734
870
|
}
|
|
735
871
|
function configurationAccess(provider) {
|
|
736
872
|
if (ownsProviderConfiguration(provider)) return { kind: "owned" };
|
|
@@ -811,8 +947,9 @@ function ConfirmButton(props) {
|
|
|
811
947
|
const copy = {
|
|
812
948
|
zh: {
|
|
813
949
|
tabsLabel: "网络搜索设置",
|
|
814
|
-
|
|
815
|
-
|
|
950
|
+
tabSearch: "web-search",
|
|
951
|
+
tabFetch: "web-fetch",
|
|
952
|
+
tabLimits: "设置",
|
|
816
953
|
loading: "正在读取设置…",
|
|
817
954
|
loadFailed: "读取设置失败:",
|
|
818
955
|
reconnect: "重新连接",
|
|
@@ -853,13 +990,16 @@ const copy = {
|
|
|
853
990
|
maxFetchChars: "正文字符上限",
|
|
854
991
|
fetchTitle: "网页读取",
|
|
855
992
|
fetchProvider: "读取服务",
|
|
993
|
+
fetchBackends: "读取后端",
|
|
994
|
+
needFetchBackend: "需选择并配置读取服务。",
|
|
856
995
|
ownerSettings: "配置入口:",
|
|
857
996
|
unknownOwner: "请在服务所属插件中配置"
|
|
858
997
|
},
|
|
859
998
|
en: {
|
|
860
999
|
tabsLabel: "Web search settings",
|
|
861
|
-
|
|
862
|
-
|
|
1000
|
+
tabSearch: "web-search",
|
|
1001
|
+
tabFetch: "web-fetch",
|
|
1002
|
+
tabLimits: "Settings",
|
|
863
1003
|
loading: "Loading settings…",
|
|
864
1004
|
loadFailed: "Could not load settings: ",
|
|
865
1005
|
reconnect: "Reconnect",
|
|
@@ -900,6 +1040,8 @@ const copy = {
|
|
|
900
1040
|
maxFetchChars: "Max text chars",
|
|
901
1041
|
fetchTitle: "Read web pages",
|
|
902
1042
|
fetchProvider: "Fetch service",
|
|
1043
|
+
fetchBackends: "Fetch backends",
|
|
1044
|
+
needFetchBackend: "Choose and configure a fetch service.",
|
|
903
1045
|
ownerSettings: "Configure in: ",
|
|
904
1046
|
unknownOwner: "Configure this service in its plugin"
|
|
905
1047
|
}
|
|
@@ -916,7 +1058,7 @@ function uiMessage(message, locale, providers = []) {
|
|
|
916
1058
|
if ("key" in message) return copy[locale][message.key];
|
|
917
1059
|
if ("sources" in message) return testOkText(locale, message.sources);
|
|
918
1060
|
if ("moved" in message) {
|
|
919
|
-
const provider = providers.find((item) => item.id === message.moved);
|
|
1061
|
+
const provider = providers.find((item) => item.id === message.moved && item.kind === message.kind);
|
|
920
1062
|
return movedText(locale, provider ? providerDisplayLabel(provider, locale) : message.moved, message.position);
|
|
921
1063
|
}
|
|
922
1064
|
return message.invalid.map((key) => {
|
|
@@ -969,15 +1111,15 @@ function parseLimit(raw, min, max) {
|
|
|
969
1111
|
* Settings written by an immediate action (a switch or a reorder). It keeps the
|
|
970
1112
|
* saved limits and never includes typed-but-unsaved keys or limit drafts.
|
|
971
1113
|
*/
|
|
972
|
-
function immediateUpdate(settings, on, order, providers, remove = []) {
|
|
1114
|
+
function immediateUpdate(settings, on, order, providers, remove = [], kind = "search") {
|
|
973
1115
|
let searchOrder = order;
|
|
974
1116
|
if (providers) {
|
|
975
|
-
const known = new Set(providers.filter((provider) => provider.kind ===
|
|
1117
|
+
const known = new Set(providers.filter((provider) => provider.kind === kind).map((provider) => provider.id));
|
|
976
1118
|
const removed = new Set(remove);
|
|
977
1119
|
const following = order.filter((id) => !removed.has(id));
|
|
978
1120
|
if (following.some((id) => !known.has(id))) searchOrder = following;
|
|
979
1121
|
else {
|
|
980
|
-
searchOrder = settings.
|
|
1122
|
+
searchOrder = providerOrder(settings, kind).flatMap((id) => {
|
|
981
1123
|
if (removed.has(id)) return [];
|
|
982
1124
|
return known.has(id) ? following.splice(0, 1) : [id];
|
|
983
1125
|
});
|
|
@@ -986,8 +1128,13 @@ function immediateUpdate(settings, on, order, providers, remove = []) {
|
|
|
986
1128
|
}
|
|
987
1129
|
return {
|
|
988
1130
|
...editable(settings),
|
|
989
|
-
|
|
990
|
-
|
|
1131
|
+
...kind === "search" ? {
|
|
1132
|
+
searchOrder,
|
|
1133
|
+
searchEnabled: on
|
|
1134
|
+
} : {
|
|
1135
|
+
fetchOrder: searchOrder,
|
|
1136
|
+
fetchEnabled: on
|
|
1137
|
+
}
|
|
991
1138
|
};
|
|
992
1139
|
}
|
|
993
1140
|
function limitTexts(settings) {
|
|
@@ -1009,7 +1156,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1009
1156
|
const locale = useHostLocale(client);
|
|
1010
1157
|
const text = copy[locale];
|
|
1011
1158
|
const tabsId = (0, react.useId)();
|
|
1012
|
-
const [tab, setTab] = (0, react.useState)("
|
|
1159
|
+
const [tab, setTab] = (0, react.useState)("search");
|
|
1013
1160
|
const [status, setStatus] = (0, react.useState)();
|
|
1014
1161
|
const [limitDraft, setLimitDraft] = (0, react.useState)();
|
|
1015
1162
|
const [invalid, setInvalid] = (0, react.useState)({});
|
|
@@ -1026,6 +1173,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1026
1173
|
const statusRef = (0, react.useRef)(status);
|
|
1027
1174
|
statusRef.current = status;
|
|
1028
1175
|
const pendingRef = (0, react.useRef)(null);
|
|
1176
|
+
const dragRef = (0, react.useRef)(null);
|
|
1029
1177
|
const timerRef = (0, react.useRef)(void 0);
|
|
1030
1178
|
const handleRefs = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
1031
1179
|
const refocusRef = (0, react.useRef)(null);
|
|
@@ -1061,7 +1209,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1061
1209
|
mounted: mountedRef.current,
|
|
1062
1210
|
hidden: isPageHidden(hiddenRef.current),
|
|
1063
1211
|
busy: busyRef.current,
|
|
1064
|
-
reordering: pendingRef.current !== null,
|
|
1212
|
+
reordering: pendingRef.current !== null || dragRef.current !== null,
|
|
1065
1213
|
reading: readingRef.current
|
|
1066
1214
|
});
|
|
1067
1215
|
}
|
|
@@ -1098,7 +1246,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1098
1246
|
requestId: mine,
|
|
1099
1247
|
latestRequestId: requestSeq.current,
|
|
1100
1248
|
busy: busyRef.current,
|
|
1101
|
-
reordering: pendingRef.current !== null,
|
|
1249
|
+
reordering: pendingRef.current !== null || dragRef.current !== null,
|
|
1102
1250
|
hidden: isPageHidden(hiddenRef.current),
|
|
1103
1251
|
currentRevision: statusRef.current?.settings.revision,
|
|
1104
1252
|
incomingRevision: next.settings.revision
|
|
@@ -1148,7 +1296,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1148
1296
|
const current = statusRef.current;
|
|
1149
1297
|
const next = pendingRef.current;
|
|
1150
1298
|
if (current && next) call("update", {
|
|
1151
|
-
settings: immediateUpdate(current.settings, current.settings.searchEnabled, next, current.providers),
|
|
1299
|
+
settings: immediateUpdate(current.settings, next.kind === "search" ? current.settings.searchEnabled : current.settings.fetchEnabled, next.order, current.providers, [], next.kind),
|
|
1152
1300
|
expectedRevision: current.settings.revision
|
|
1153
1301
|
}).catch(() => {});
|
|
1154
1302
|
};
|
|
@@ -1166,6 +1314,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1166
1314
|
if (handle && document.activeElement !== handle) handle.focus();
|
|
1167
1315
|
});
|
|
1168
1316
|
async function action(run) {
|
|
1317
|
+
if (busyRef.current) return;
|
|
1169
1318
|
readGate.reset();
|
|
1170
1319
|
requestSeq.current += 1;
|
|
1171
1320
|
busyRef.current = true;
|
|
@@ -1183,18 +1332,17 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1183
1332
|
}
|
|
1184
1333
|
}
|
|
1185
1334
|
/** Switches and reorders: settings only, applied at once. */
|
|
1186
|
-
function runImmediate(on, order, remove = []) {
|
|
1335
|
+
function runImmediate(on, order, remove = [], kind = "search") {
|
|
1187
1336
|
clearTimeout(timerRef.current);
|
|
1188
1337
|
timerRef.current = void 0;
|
|
1189
1338
|
pendingRef.current = null;
|
|
1190
1339
|
action(async () => {
|
|
1191
1340
|
const current = statusRef.current;
|
|
1192
1341
|
if (!current) return;
|
|
1193
|
-
|
|
1194
|
-
settings: immediateUpdate(current.settings, on, order, current.providers, remove),
|
|
1342
|
+
adopt(await call("update", {
|
|
1343
|
+
settings: immediateUpdate(current.settings, on, order, current.providers, remove, kind),
|
|
1195
1344
|
expectedRevision: current.settings.revision
|
|
1196
|
-
});
|
|
1197
|
-
setStatus(next);
|
|
1345
|
+
}));
|
|
1198
1346
|
await load();
|
|
1199
1347
|
}).then(() => {
|
|
1200
1348
|
if (pendingRef.current === null) setPendingOrder(null);
|
|
@@ -1204,30 +1352,33 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1204
1352
|
async function saveKeys(only) {
|
|
1205
1353
|
if (!status) return;
|
|
1206
1354
|
for (const provider of status.providers) {
|
|
1207
|
-
if (
|
|
1208
|
-
const snapshot = keys[provider.id] ?? "";
|
|
1355
|
+
if (only && providerKey(provider.kind, provider.id) !== providerKey(only.kind, only.id)) continue;
|
|
1356
|
+
const snapshot = keys[providerKey(provider.kind, provider.id)] ?? "";
|
|
1209
1357
|
const typed = snapshot.trim();
|
|
1210
1358
|
if (!ownsProviderConfiguration(provider) || !provider.credentialRef || !typed) continue;
|
|
1211
1359
|
if (writable[provider.credentialRef] === false) throw new UiError("readOnly");
|
|
1212
1360
|
unwrap(await client.remote.credentials.set(provider.credentialRef, typed));
|
|
1213
|
-
setKeys((current) => current[provider.id] === snapshot ? {
|
|
1361
|
+
setKeys((current) => current[providerKey(provider.kind, provider.id)] === snapshot ? {
|
|
1214
1362
|
...current,
|
|
1215
|
-
[provider.id]: ""
|
|
1363
|
+
[providerKey(provider.kind, provider.id)]: ""
|
|
1216
1364
|
} : current);
|
|
1217
1365
|
}
|
|
1218
1366
|
}
|
|
1367
|
+
async function saveKeysForKind(kind) {
|
|
1368
|
+
for (const provider of statusRef.current?.providers ?? []) if (provider.kind === kind) await saveKeys(provider);
|
|
1369
|
+
}
|
|
1219
1370
|
async function removeKey(provider) {
|
|
1220
1371
|
if (!ownsProviderConfiguration(provider) || !provider.credentialRef || provider.credentialShared || !status) return;
|
|
1221
1372
|
unwrap(await client.remote.credentials.unset(provider.credentialRef));
|
|
1222
1373
|
setKeys((current) => ({
|
|
1223
1374
|
...current,
|
|
1224
|
-
[provider.id]: ""
|
|
1375
|
+
[providerKey(provider.kind, provider.id)]: ""
|
|
1225
1376
|
}));
|
|
1226
1377
|
await load();
|
|
1227
1378
|
setNote({ key: "keyDeleted" });
|
|
1228
1379
|
}
|
|
1229
1380
|
if (!status || !limitDraft) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1230
|
-
className: "web-search-settings ws-root dsh-ui-
|
|
1381
|
+
className: "web-search-settings ws-root dsh-ui-page",
|
|
1231
1382
|
children: [loadError ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1232
1383
|
role: "alert",
|
|
1233
1384
|
className: "dsh-ui-error",
|
|
@@ -1250,28 +1401,30 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1250
1401
|
}) : null]
|
|
1251
1402
|
});
|
|
1252
1403
|
const saved = status;
|
|
1253
|
-
const savedOrder = searchBackends(saved).map((provider) => provider.id);
|
|
1254
|
-
const order = pendingOrder ?? savedOrder;
|
|
1255
|
-
const catalog = catalogSearchBackends(saved);
|
|
1256
|
-
const backends = [...order.flatMap((id) => catalog.find((provider) => provider.id === id) ?? []), ...catalog.filter((provider) => !order.includes(provider.id))];
|
|
1257
|
-
const selected = selectedSearchBackend(saved);
|
|
1258
|
-
const on = saved.settings.searchEnabled;
|
|
1259
|
-
const canEnable = canEnableSearch(order, saved.providers, {}, writable);
|
|
1260
1404
|
const savedLimits = limitTexts(saved.settings);
|
|
1261
1405
|
const limitsDirty = LIMITS.some(([key]) => limitDraft[key] !== savedLimits[key]);
|
|
1262
1406
|
const keysDirty = Object.values(keys).some((value) => value.trim());
|
|
1263
|
-
const reordering = pendingOrder !== null;
|
|
1264
|
-
function move(id, nextIndex, keyboard = false) {
|
|
1407
|
+
const reordering = pendingOrder !== null || dragSource !== null;
|
|
1408
|
+
function move(kind, id, nextIndex, keyboard = false) {
|
|
1409
|
+
if (pendingRef.current && pendingRef.current.kind !== kind) return;
|
|
1410
|
+
const order = pendingRef.current?.order ?? providerOrder(statusRef.current.settings, kind);
|
|
1265
1411
|
const index = order.indexOf(id);
|
|
1266
1412
|
if (busy || index < 0 || nextIndex < 0 || nextIndex >= order.length || index === nextIndex) return;
|
|
1267
1413
|
const next = [...order];
|
|
1268
1414
|
const [item] = next.splice(index, 1);
|
|
1269
1415
|
next.splice(nextIndex, 0, item);
|
|
1270
|
-
pendingRef.current =
|
|
1271
|
-
|
|
1272
|
-
|
|
1416
|
+
pendingRef.current = {
|
|
1417
|
+
kind,
|
|
1418
|
+
order: next
|
|
1419
|
+
};
|
|
1420
|
+
setPendingOrder({
|
|
1421
|
+
kind,
|
|
1422
|
+
order: next
|
|
1423
|
+
});
|
|
1424
|
+
if (keyboard) refocusRef.current = providerKey(kind, id);
|
|
1273
1425
|
setSortAnnouncement({
|
|
1274
1426
|
moved: id,
|
|
1427
|
+
kind,
|
|
1275
1428
|
position: nextIndex + 1
|
|
1276
1429
|
});
|
|
1277
1430
|
clearTimeout(timerRef.current);
|
|
@@ -1279,10 +1432,11 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1279
1432
|
timerRef.current = void 0;
|
|
1280
1433
|
const batched = pendingRef.current;
|
|
1281
1434
|
const current = statusRef.current;
|
|
1282
|
-
if (batched && current) runImmediate(current.settings.searchEnabled, batched);
|
|
1435
|
+
if (batched && current) runImmediate(batched.kind === "search" ? current.settings.searchEnabled : current.settings.fetchEnabled, batched.order, [], batched.kind);
|
|
1283
1436
|
}, keyboard ? REORDER_DEBOUNCE_MS : 0);
|
|
1284
1437
|
}
|
|
1285
1438
|
function endDrag() {
|
|
1439
|
+
dragRef.current = null;
|
|
1286
1440
|
setDragSource(null);
|
|
1287
1441
|
setDropTarget(null);
|
|
1288
1442
|
}
|
|
@@ -1318,24 +1472,33 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1318
1472
|
});
|
|
1319
1473
|
}
|
|
1320
1474
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1321
|
-
className: "web-search-settings ws-root dsh-ui-
|
|
1475
|
+
className: "web-search-settings ws-root dsh-ui-page",
|
|
1322
1476
|
"data-testid": "web-search-settings",
|
|
1323
1477
|
children: [
|
|
1324
1478
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SegmentedTabs, {
|
|
1325
1479
|
value: tab,
|
|
1326
1480
|
onChange: setTab,
|
|
1327
1481
|
label: text.tabsLabel,
|
|
1328
|
-
items: [
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1482
|
+
items: [
|
|
1483
|
+
{
|
|
1484
|
+
value: "search",
|
|
1485
|
+
label: text.tabSearch,
|
|
1486
|
+
id: tabsId + "-search-tab",
|
|
1487
|
+
panelId: tabsId + "-search-panel"
|
|
1488
|
+
},
|
|
1489
|
+
{
|
|
1490
|
+
value: "fetch",
|
|
1491
|
+
label: text.tabFetch,
|
|
1492
|
+
id: tabsId + "-fetch-tab",
|
|
1493
|
+
panelId: tabsId + "-fetch-panel"
|
|
1494
|
+
},
|
|
1495
|
+
{
|
|
1496
|
+
value: "limits",
|
|
1497
|
+
label: text.tabLimits,
|
|
1498
|
+
id: tabsId + "-limits-tab",
|
|
1499
|
+
panelId: tabsId + "-limits-panel"
|
|
1500
|
+
}
|
|
1501
|
+
]
|
|
1339
1502
|
}),
|
|
1340
1503
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1341
1504
|
className: "ws-sr-only",
|
|
@@ -1374,374 +1537,294 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1374
1537
|
className: "dsh-ui-banner dsh-ui-banner--danger",
|
|
1375
1538
|
children: text.storageFailed
|
|
1376
1539
|
}),
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
checked: on,
|
|
1401
|
-
label: text.toolTitle,
|
|
1402
|
-
disabled: busy || !on && !canEnable,
|
|
1403
|
-
onChange: (next) => runImmediate(next, order)
|
|
1404
|
-
})]
|
|
1405
|
-
}),
|
|
1406
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
|
|
1407
|
-
className: "web-search-rank",
|
|
1408
|
-
"aria-label": text.backends,
|
|
1409
|
-
children: backends.map((provider) => {
|
|
1410
|
-
const participating = order.includes(provider.id);
|
|
1411
|
-
const rank = order.indexOf(provider.id);
|
|
1412
|
-
const dormant = !isRegisteredSearch(saved, provider.id);
|
|
1413
|
-
const active = !dormant && saved.searchActive && selected?.id === provider.id;
|
|
1414
|
-
const info = searchProviderInfo(provider, locale);
|
|
1415
|
-
const owned = ownsProviderConfiguration(provider);
|
|
1416
|
-
const access = configurationAccess(provider);
|
|
1417
|
-
const needsKey = owned && Boolean(provider.credentialRef);
|
|
1418
|
-
const shared = Boolean(provider.credentialShared);
|
|
1419
|
-
const showKey = !dormant && needsKey && participating && (!shared || !provider.configured);
|
|
1420
|
-
const canSort = participating && order.length > 1;
|
|
1421
|
-
const readOnly = writable[provider.credentialRef ?? ""] === false;
|
|
1422
|
-
const typedKey = (keys[provider.id] ?? "").trim();
|
|
1423
|
-
const availability = dormant ? "dormant" : provider.configured ? "available" : "unavailable";
|
|
1424
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
1425
|
-
className: active ? "is-active" : void 0,
|
|
1426
|
-
"data-testid": `web-search-rank-${provider.id}`,
|
|
1427
|
-
"data-availability": availability,
|
|
1428
|
-
"data-in-use": active || void 0,
|
|
1429
|
-
"data-dragging": dragSource === provider.id || void 0,
|
|
1430
|
-
"data-drop": dropTarget === provider.id && dragSource ? order.indexOf(dragSource) < rank ? "after" : "before" : void 0,
|
|
1431
|
-
onDragOver: (event) => {
|
|
1432
|
-
if (busy || !canSort || !dragSource || dragSource === provider.id) return;
|
|
1433
|
-
event.preventDefault();
|
|
1434
|
-
event.dataTransfer.dropEffect = "move";
|
|
1435
|
-
setDropTarget(provider.id);
|
|
1436
|
-
},
|
|
1437
|
-
onDragLeave: (event) => {
|
|
1438
|
-
if (!event.currentTarget.contains(event.relatedTarget)) setDropTarget(null);
|
|
1439
|
-
},
|
|
1440
|
-
onDrop: (event) => {
|
|
1441
|
-
if (busy || !canSort || !dragSource) return;
|
|
1442
|
-
event.preventDefault();
|
|
1443
|
-
move(dragSource, rank);
|
|
1444
|
-
endDrag();
|
|
1445
|
-
},
|
|
1446
|
-
children: [
|
|
1447
|
-
canSort ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1448
|
-
variant: "ghost",
|
|
1449
|
-
size: "sm",
|
|
1450
|
-
className: "web-search-drag-handle",
|
|
1451
|
-
ref: (element) => {
|
|
1452
|
-
if (element) handleRefs.current.set(provider.id, element);
|
|
1453
|
-
else handleRefs.current.delete(provider.id);
|
|
1454
|
-
},
|
|
1455
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
1456
|
-
width: "16",
|
|
1457
|
-
height: "20",
|
|
1458
|
-
viewBox: "0 0 16 20",
|
|
1459
|
-
fill: "currentColor",
|
|
1460
|
-
"aria-hidden": "true",
|
|
1461
|
-
children: [
|
|
1462
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1463
|
-
cx: "5",
|
|
1464
|
-
cy: "5",
|
|
1465
|
-
r: "1.5"
|
|
1466
|
-
}),
|
|
1467
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1468
|
-
cx: "11",
|
|
1469
|
-
cy: "5",
|
|
1470
|
-
r: "1.5"
|
|
1471
|
-
}),
|
|
1472
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1473
|
-
cx: "5",
|
|
1474
|
-
cy: "10",
|
|
1475
|
-
r: "1.5"
|
|
1476
|
-
}),
|
|
1477
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1478
|
-
cx: "11",
|
|
1479
|
-
cy: "10",
|
|
1480
|
-
r: "1.5"
|
|
1481
|
-
}),
|
|
1482
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1483
|
-
cx: "5",
|
|
1484
|
-
cy: "15",
|
|
1485
|
-
r: "1.5"
|
|
1486
|
-
}),
|
|
1487
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1488
|
-
cx: "11",
|
|
1489
|
-
cy: "15",
|
|
1490
|
-
r: "1.5"
|
|
1491
|
-
})
|
|
1492
|
-
]
|
|
1493
|
-
}),
|
|
1494
|
-
draggable: !busy,
|
|
1495
|
-
"aria-disabled": busy,
|
|
1496
|
-
"aria-label": text.dragPrefix + providerDisplayLabel(provider, locale),
|
|
1497
|
-
title: text.dragHint,
|
|
1498
|
-
onDragStart: (event) => {
|
|
1499
|
-
if (busy) {
|
|
1500
|
-
event.preventDefault();
|
|
1501
|
-
return;
|
|
1502
|
-
}
|
|
1503
|
-
event.dataTransfer.effectAllowed = "move";
|
|
1504
|
-
event.dataTransfer.setData("application/x-dsh-search-provider", provider.id);
|
|
1505
|
-
const row = event.currentTarget.closest("li");
|
|
1506
|
-
if (row) event.dataTransfer.setDragImage(row, 16, 16);
|
|
1507
|
-
setDragSource(provider.id);
|
|
1508
|
-
setSortAnnouncement("");
|
|
1509
|
-
},
|
|
1510
|
-
onDragEnd: endDrag,
|
|
1511
|
-
onKeyDown: (event) => {
|
|
1512
|
-
if (event.key !== "ArrowUp" && event.key !== "ArrowDown") return;
|
|
1513
|
-
event.preventDefault();
|
|
1514
|
-
move(provider.id, rank + (event.key === "ArrowUp" ? -1 : 1), true);
|
|
1515
|
-
}
|
|
1516
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1517
|
-
className: "web-search-rank-index",
|
|
1518
|
-
children: participating ? rank + 1 : "—"
|
|
1519
|
-
}),
|
|
1520
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1521
|
-
className: "web-search-provider",
|
|
1522
|
-
children: [
|
|
1523
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1524
|
-
className: "web-search-provider-heading",
|
|
1525
|
-
children: [
|
|
1526
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
|
|
1527
|
-
className: "dsh-ui-heading",
|
|
1528
|
-
children: providerDisplayLabel(provider, locale)
|
|
1529
|
-
}),
|
|
1530
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1531
|
-
className: "ws-provider-note",
|
|
1532
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: dormant ? "idle" : provider.configured ? "done" : "warning" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: dormant ? text.dormant : provider.configured ? text.available : text.unavailable })]
|
|
1533
|
-
}),
|
|
1534
|
-
active && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
|
|
1535
|
-
tone: "success",
|
|
1536
|
-
children: text.inUse
|
|
1537
|
-
}),
|
|
1538
|
-
provider.signupUrl && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppLink, {
|
|
1539
|
-
url: provider.signupUrl,
|
|
1540
|
-
label: providerDisplayLabel(provider, locale) + " " + text.signup,
|
|
1541
|
-
children: text.signup
|
|
1542
|
-
})
|
|
1543
|
-
]
|
|
1544
|
-
}),
|
|
1545
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1546
|
-
className: "web-search-provider-info",
|
|
1547
|
-
children: [info.description ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: info.description }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [info.pricing, info.pricingUrl && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [" ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppLink, {
|
|
1548
|
-
url: info.pricingUrl,
|
|
1549
|
-
label: providerDisplayLabel(provider, locale) + " " + text.pricing,
|
|
1550
|
-
children: provider.id === "ddg" ? text.website : text.pricing
|
|
1551
|
-
})] })] })]
|
|
1552
|
-
}),
|
|
1553
|
-
access.kind === "link" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1554
|
-
className: "dsh-ui-hint",
|
|
1555
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppLink, {
|
|
1556
|
-
url: access.url,
|
|
1557
|
-
label: text.openConfig,
|
|
1558
|
-
children: text.openConfig
|
|
1559
|
-
})
|
|
1560
|
-
}) : access.kind === "owner" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1561
|
-
className: "dsh-ui-hint",
|
|
1562
|
-
children: [text.ownerSettings, access.name]
|
|
1563
|
-
}) : access.kind === "unknown" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1564
|
-
className: "dsh-ui-hint",
|
|
1565
|
-
children: text.unknownOwner
|
|
1566
|
-
}) : null,
|
|
1567
|
-
shared && participating && owned ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", {
|
|
1568
|
-
className: "dsh-ui-meta",
|
|
1569
|
-
children: provider.credentialHint === "与模型设置共用 Key" ? locale === "zh" ? provider.credentialHint : "Shares a key with model settings" : provider.credentialHint ?? text.sharedKey
|
|
1570
|
-
}) : null,
|
|
1571
|
-
showKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1572
|
-
className: "dsh-ui-field",
|
|
1573
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1574
|
-
className: "dsh-ui-label",
|
|
1575
|
-
children: "API Key"
|
|
1576
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
1577
|
-
className: "ws-input",
|
|
1578
|
-
type: "password",
|
|
1579
|
-
autoComplete: "off",
|
|
1580
|
-
spellCheck: false,
|
|
1581
|
-
disabled: busy || readOnly,
|
|
1582
|
-
value: keys[provider.id] ?? "",
|
|
1583
|
-
placeholder: provider.configured ? text.keyConfigured : text.keyPlaceholder,
|
|
1584
|
-
onChange: (event) => setKeys((current) => ({
|
|
1585
|
-
...current,
|
|
1586
|
-
[provider.id]: event.target.value
|
|
1587
|
-
}))
|
|
1588
|
-
})]
|
|
1589
|
-
}) : null,
|
|
1590
|
-
showKey || needsKey && !shared && provider.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1591
|
-
className: "dsh-ui-actions",
|
|
1592
|
-
children: [showKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1593
|
-
variant: "outline",
|
|
1594
|
-
size: "sm",
|
|
1595
|
-
disabled: busy || readOnly || !typedKey,
|
|
1596
|
-
onClick: () => void action(async () => {
|
|
1597
|
-
await saveKeys(provider);
|
|
1598
|
-
await load();
|
|
1599
|
-
setNote({ key: "keySaved" });
|
|
1600
|
-
}),
|
|
1601
|
-
children: text.saveKey
|
|
1602
|
-
}) : null, needsKey && !shared && provider.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmButton, {
|
|
1603
|
-
disabled: busy || readOnly,
|
|
1604
|
-
label: text.deleteKey,
|
|
1605
|
-
confirmLabel: text.confirmDelete,
|
|
1606
|
-
onConfirm: () => void action(() => removeKey(provider))
|
|
1607
|
-
}) : null]
|
|
1608
|
-
}) : null
|
|
1609
|
-
]
|
|
1610
|
-
}),
|
|
1611
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1612
|
-
className: "web-search-rank-move",
|
|
1613
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
1614
|
-
checked: participating,
|
|
1615
|
-
label: providerDisplayLabel(provider, locale),
|
|
1616
|
-
disabled: busy,
|
|
1617
|
-
onChange: (next) => {
|
|
1618
|
-
const following = next ? [...order, provider.id] : order.filter((id) => id !== provider.id);
|
|
1619
|
-
runImmediate(nextSearchEnabled(saved.settings.searchEnabled, following, saved.providers, {}, writable), following, droppedDormantIds(saved.settings, following, saved.providers));
|
|
1620
|
-
}
|
|
1621
|
-
})
|
|
1622
|
-
})
|
|
1623
|
-
]
|
|
1624
|
-
}, provider.id);
|
|
1625
|
-
})
|
|
1626
|
-
}),
|
|
1627
|
-
backends.some(usesBundledPricing) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1628
|
-
className: "dsh-ui-hint",
|
|
1629
|
-
children: pricingCheckedText(locale, PROVIDER_PRICING_CHECKED)
|
|
1630
|
-
}) : null,
|
|
1631
|
-
on ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1632
|
-
className: "dsh-ui-actions",
|
|
1633
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1634
|
-
variant: "outline",
|
|
1635
|
-
size: "md",
|
|
1636
|
-
disabled: busy || reordering,
|
|
1637
|
-
onClick: () => void action(async () => {
|
|
1638
|
-
await saveKeys();
|
|
1639
|
-
const result = await call("test");
|
|
1640
|
-
await load();
|
|
1641
|
-
setNote({ sources: result.sources });
|
|
1642
|
-
}),
|
|
1643
|
-
children: text.test
|
|
1644
|
-
})
|
|
1645
|
-
}) : null
|
|
1646
|
-
]
|
|
1647
|
-
}), (saved.providers.some((provider) => provider.kind === "fetch") || saved.settings.fetchProvider) && (() => {
|
|
1648
|
-
const fetchId = saved.settings.fetchProvider;
|
|
1649
|
-
const fetchSelected = saved.providers.find((provider) => provider.kind === "fetch" && provider.id === fetchId);
|
|
1650
|
-
const fetchDormant = Boolean(fetchId) && !fetchSelected;
|
|
1651
|
-
const fetchActive = Boolean(fetchSelected && saved.fetchActive && fetchSelected.configured);
|
|
1652
|
-
const fetchAccess = fetchSelected ? configurationAccess(fetchSelected) : fetchDormant ? { kind: "unknown" } : void 0;
|
|
1653
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
1654
|
-
className: "dsh-ui-card dsh-ui-card--flat",
|
|
1655
|
-
"data-testid": "web-search-fetch",
|
|
1656
|
-
"data-availability": fetchDormant ? "dormant" : fetchSelected?.configured ? "available" : "unavailable",
|
|
1657
|
-
"data-in-use": fetchActive || void 0,
|
|
1540
|
+
["search", "fetch"].map((kind) => {
|
|
1541
|
+
const order = pendingOrder?.kind === kind ? pendingOrder.order : providerOrder(saved.settings, kind);
|
|
1542
|
+
const catalog = catalogBackends(saved, kind);
|
|
1543
|
+
const backends = [...order.flatMap((id) => catalog.find((provider) => provider.id === id) ?? []), ...catalog.filter((provider) => !order.includes(provider.id))];
|
|
1544
|
+
const selected = selectedBackend(saved, kind);
|
|
1545
|
+
const on = kind === "search" ? saved.settings.searchEnabled : saved.settings.fetchEnabled;
|
|
1546
|
+
const canEnable = order.some((id) => saved.providers.some((provider) => provider.kind === kind && provider.id === id && provider.configured));
|
|
1547
|
+
const sortingOther = pendingOrder !== null && pendingOrder.kind !== kind;
|
|
1548
|
+
const locked = busy || sortingOther || Boolean(dragSource && !dragSource.startsWith(kind + ":"));
|
|
1549
|
+
const title = kind === "search" ? text.toolTitle : text.fetchTitle;
|
|
1550
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1551
|
+
role: "tabpanel",
|
|
1552
|
+
id: tabsId + "-" + kind + "-panel",
|
|
1553
|
+
"aria-labelledby": tabsId + "-" + kind + "-tab",
|
|
1554
|
+
hidden: tab !== kind,
|
|
1555
|
+
tabIndex: 0,
|
|
1556
|
+
children: backends.length === 0 ? kind === "search" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1557
|
+
className: "dsh-ui-empty",
|
|
1558
|
+
children: text.empty
|
|
1559
|
+
}) : null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
1560
|
+
className: "web-search-card dsh-ui-card dsh-ui-card--flat",
|
|
1561
|
+
"data-testid": kind === "search" ? "web-search-tool" : "web-search-fetch",
|
|
1562
|
+
"data-on": on ? "true" : "false",
|
|
1658
1563
|
children: [
|
|
1659
1564
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
1660
1565
|
className: "ws-card-head",
|
|
1661
1566
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1662
1567
|
className: "dsh-ui-title",
|
|
1663
|
-
children:
|
|
1664
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1665
|
-
className: "
|
|
1666
|
-
children:
|
|
1667
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: fetchDormant ? "idle" : fetchSelected?.configured ? "done" : "warning" }),
|
|
1668
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: fetchDormant ? text.dormant : fetchSelected?.configured ? text.available : text.unavailable }),
|
|
1669
|
-
fetchActive ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
|
|
1670
|
-
tone: "success",
|
|
1671
|
-
children: text.inUse
|
|
1672
|
-
}) : null
|
|
1673
|
-
]
|
|
1568
|
+
children: title
|
|
1569
|
+
}), !canEnable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1570
|
+
className: "dsh-ui-hint",
|
|
1571
|
+
children: kind === "search" ? text.needBackend : text.needFetchBackend
|
|
1674
1572
|
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
1675
|
-
checked:
|
|
1676
|
-
label:
|
|
1677
|
-
disabled:
|
|
1678
|
-
onChange: (next) =>
|
|
1679
|
-
const current = statusRef.current;
|
|
1680
|
-
adopt(await call("update", {
|
|
1681
|
-
settings: {
|
|
1682
|
-
...editable(current.settings),
|
|
1683
|
-
fetchEnabled: next
|
|
1684
|
-
},
|
|
1685
|
-
expectedRevision: current.settings.revision
|
|
1686
|
-
}));
|
|
1687
|
-
await load();
|
|
1688
|
-
})
|
|
1573
|
+
checked: on,
|
|
1574
|
+
label: title,
|
|
1575
|
+
disabled: locked || dragSource !== null || !on && !canEnable,
|
|
1576
|
+
onChange: (next) => runImmediate(next, order, [], kind)
|
|
1689
1577
|
})]
|
|
1690
1578
|
}),
|
|
1691
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1692
|
-
className: "
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
"
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1579
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
|
|
1580
|
+
className: "web-search-rank",
|
|
1581
|
+
"aria-label": kind === "search" ? text.backends : text.fetchBackends,
|
|
1582
|
+
children: backends.map((provider) => {
|
|
1583
|
+
const participating = order.includes(provider.id);
|
|
1584
|
+
const rank = order.indexOf(provider.id);
|
|
1585
|
+
const dormant = !isRegistered(saved, provider.id, kind);
|
|
1586
|
+
const active = !dormant && (kind === "search" ? saved.searchActive : saved.fetchActive) && selected?.id === provider.id;
|
|
1587
|
+
const info = kind === "search" || ownsProviderConfiguration(provider) && provider.id === "firecrawl" ? searchProviderInfo(provider, locale) : {
|
|
1588
|
+
description: ownsProviderConfiguration(provider) && provider.id === "http-managed" && provider.description === "优先匿名 Jina Reader 读取,失败后本机 HTTP 直连;无需 Key。" && locale === "en" ? "Reads with anonymous Jina Reader first, then direct HTTP if needed; no key required." : provider.description,
|
|
1589
|
+
pricing: provider.pricing ?? "",
|
|
1590
|
+
pricingUrl: provider.pricingUrl
|
|
1591
|
+
};
|
|
1592
|
+
const owned = ownsProviderConfiguration(provider);
|
|
1593
|
+
const access = configurationAccess(provider);
|
|
1594
|
+
const needsKey = owned && Boolean(provider.credentialRef);
|
|
1595
|
+
const shared = Boolean(provider.credentialShared);
|
|
1596
|
+
const showKey = !dormant && needsKey && participating && (!shared || !provider.configured);
|
|
1597
|
+
const canSort = participating && order.length > 1;
|
|
1598
|
+
const readOnly = writable[provider.credentialRef ?? ""] === false;
|
|
1599
|
+
const typedKey = (keys[providerKey(provider.kind, provider.id)] ?? "").trim();
|
|
1600
|
+
const availability = dormant ? "dormant" : provider.configured ? "available" : "unavailable";
|
|
1601
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
1602
|
+
className: active ? "is-active" : void 0,
|
|
1603
|
+
"data-testid": `web-${kind}-rank-${provider.id}`,
|
|
1604
|
+
"data-availability": availability,
|
|
1605
|
+
"data-in-use": active || void 0,
|
|
1606
|
+
"data-dragging": dragSource === providerKey(kind, provider.id) || void 0,
|
|
1607
|
+
"data-drop": dropTarget === providerKey(kind, provider.id) && dragSource ? order.indexOf(dragSource.slice(kind.length + 1)) < rank ? "after" : "before" : void 0,
|
|
1608
|
+
onDragOver: (event) => {
|
|
1609
|
+
if (locked || !canSort || !dragSource?.startsWith(kind + ":") || dragSource === providerKey(kind, provider.id)) return;
|
|
1610
|
+
event.preventDefault();
|
|
1611
|
+
event.dataTransfer.dropEffect = "move";
|
|
1612
|
+
setDropTarget(providerKey(kind, provider.id));
|
|
1613
|
+
},
|
|
1614
|
+
onDragLeave: (event) => {
|
|
1615
|
+
if (!event.currentTarget.contains(event.relatedTarget)) setDropTarget(null);
|
|
1616
|
+
},
|
|
1617
|
+
onDrop: (event) => {
|
|
1618
|
+
if (locked || !canSort || !dragSource?.startsWith(kind + ":")) return;
|
|
1619
|
+
event.preventDefault();
|
|
1620
|
+
move(kind, dragSource.slice(kind.length + 1), rank);
|
|
1621
|
+
endDrag();
|
|
1622
|
+
},
|
|
1717
1623
|
children: [
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1624
|
+
canSort ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1625
|
+
variant: "ghost",
|
|
1626
|
+
size: "sm",
|
|
1627
|
+
className: "web-search-drag-handle",
|
|
1628
|
+
ref: (element) => {
|
|
1629
|
+
if (element) handleRefs.current.set(providerKey(kind, provider.id), element);
|
|
1630
|
+
else handleRefs.current.delete(providerKey(kind, provider.id));
|
|
1631
|
+
},
|
|
1632
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
1633
|
+
width: "16",
|
|
1634
|
+
height: "20",
|
|
1635
|
+
viewBox: "0 0 16 20",
|
|
1636
|
+
fill: "currentColor",
|
|
1637
|
+
"aria-hidden": "true",
|
|
1638
|
+
children: [
|
|
1639
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1640
|
+
cx: "5",
|
|
1641
|
+
cy: "5",
|
|
1642
|
+
r: "1.5"
|
|
1643
|
+
}),
|
|
1644
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1645
|
+
cx: "11",
|
|
1646
|
+
cy: "5",
|
|
1647
|
+
r: "1.5"
|
|
1648
|
+
}),
|
|
1649
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1650
|
+
cx: "5",
|
|
1651
|
+
cy: "10",
|
|
1652
|
+
r: "1.5"
|
|
1653
|
+
}),
|
|
1654
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1655
|
+
cx: "11",
|
|
1656
|
+
cy: "10",
|
|
1657
|
+
r: "1.5"
|
|
1658
|
+
}),
|
|
1659
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1660
|
+
cx: "5",
|
|
1661
|
+
cy: "15",
|
|
1662
|
+
r: "1.5"
|
|
1663
|
+
}),
|
|
1664
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1665
|
+
cx: "11",
|
|
1666
|
+
cy: "15",
|
|
1667
|
+
r: "1.5"
|
|
1668
|
+
})
|
|
1669
|
+
]
|
|
1670
|
+
}),
|
|
1671
|
+
draggable: !locked,
|
|
1672
|
+
"aria-disabled": locked,
|
|
1673
|
+
"aria-label": text.dragPrefix + providerDisplayLabel(provider, locale),
|
|
1674
|
+
title: text.dragHint,
|
|
1675
|
+
onDragStart: (event) => {
|
|
1676
|
+
if (locked || pendingRef.current && pendingRef.current.kind !== kind) {
|
|
1677
|
+
event.preventDefault();
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
event.dataTransfer.effectAllowed = "move";
|
|
1681
|
+
event.dataTransfer.setData(`application/x-dsh-${kind}-provider`, provider.id);
|
|
1682
|
+
const row = event.currentTarget.closest("li");
|
|
1683
|
+
if (row) event.dataTransfer.setDragImage(row, 16, 16);
|
|
1684
|
+
dragRef.current = providerKey(kind, provider.id);
|
|
1685
|
+
requestSeq.current += 1;
|
|
1686
|
+
setDragSource(providerKey(kind, provider.id));
|
|
1687
|
+
setSortAnnouncement("");
|
|
1688
|
+
},
|
|
1689
|
+
onDragEnd: endDrag,
|
|
1690
|
+
onKeyDown: (event) => {
|
|
1691
|
+
if (event.key !== "ArrowUp" && event.key !== "ArrowDown") return;
|
|
1692
|
+
event.preventDefault();
|
|
1693
|
+
move(kind, provider.id, rank + (event.key === "ArrowUp" ? -1 : 1), true);
|
|
1694
|
+
}
|
|
1695
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1696
|
+
className: "web-search-rank-index",
|
|
1697
|
+
children: participating ? rank + 1 : "—"
|
|
1698
|
+
}),
|
|
1699
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1700
|
+
className: "web-search-provider",
|
|
1701
|
+
children: [
|
|
1702
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1703
|
+
className: "web-search-provider-heading",
|
|
1704
|
+
children: [
|
|
1705
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
|
|
1706
|
+
className: "dsh-ui-heading",
|
|
1707
|
+
children: providerDisplayLabel(provider, locale)
|
|
1708
|
+
}),
|
|
1709
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1710
|
+
className: "ws-provider-note",
|
|
1711
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: dormant ? "idle" : provider.configured ? "done" : "warning" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: dormant ? text.dormant : provider.configured ? text.available : text.unavailable })]
|
|
1712
|
+
}),
|
|
1713
|
+
active && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
|
|
1714
|
+
tone: "success",
|
|
1715
|
+
children: text.inUse
|
|
1716
|
+
}),
|
|
1717
|
+
provider.signupUrl && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppLink, {
|
|
1718
|
+
url: provider.signupUrl,
|
|
1719
|
+
label: providerDisplayLabel(provider, locale) + " " + text.signup,
|
|
1720
|
+
children: text.signup
|
|
1721
|
+
})
|
|
1722
|
+
]
|
|
1723
|
+
}),
|
|
1724
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1725
|
+
className: "web-search-provider-info",
|
|
1726
|
+
children: [info.description ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: info.description }) : null, info.pricing || info.pricingUrl ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [info.pricing, info.pricingUrl && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [" ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppLink, {
|
|
1727
|
+
url: info.pricingUrl,
|
|
1728
|
+
label: providerDisplayLabel(provider, locale) + " " + text.pricing,
|
|
1729
|
+
children: provider.id === "ddg" ? text.website : text.pricing
|
|
1730
|
+
})] })] }) : null]
|
|
1731
|
+
}),
|
|
1732
|
+
access.kind === "link" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1733
|
+
className: "dsh-ui-hint",
|
|
1734
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppLink, {
|
|
1735
|
+
url: access.url,
|
|
1736
|
+
label: text.openConfig,
|
|
1737
|
+
children: text.openConfig
|
|
1738
|
+
})
|
|
1739
|
+
}) : access.kind === "owner" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1740
|
+
className: "dsh-ui-hint",
|
|
1741
|
+
children: [text.ownerSettings, access.name]
|
|
1742
|
+
}) : access.kind === "unknown" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1743
|
+
className: "dsh-ui-hint",
|
|
1744
|
+
children: text.unknownOwner
|
|
1745
|
+
}) : null,
|
|
1746
|
+
shared && participating && owned ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", {
|
|
1747
|
+
className: "dsh-ui-meta",
|
|
1748
|
+
children: provider.credentialHint === "与模型设置共用 Key" ? locale === "zh" ? provider.credentialHint : "Shares a key with model settings" : owned && provider.id === "firecrawl" && locale === "en" ? "Search and page reads share a Firecrawl key" : provider.credentialHint ?? text.sharedKey
|
|
1749
|
+
}) : null,
|
|
1750
|
+
showKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1751
|
+
className: "dsh-ui-field",
|
|
1752
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1753
|
+
className: "dsh-ui-label",
|
|
1754
|
+
children: "API Key"
|
|
1755
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
1756
|
+
className: "ws-input",
|
|
1757
|
+
type: "password",
|
|
1758
|
+
autoComplete: "off",
|
|
1759
|
+
spellCheck: false,
|
|
1760
|
+
disabled: busy || reordering || readOnly,
|
|
1761
|
+
value: keys[providerKey(provider.kind, provider.id)] ?? "",
|
|
1762
|
+
placeholder: provider.configured ? text.keyConfigured : text.keyPlaceholder,
|
|
1763
|
+
onChange: (event) => setKeys((current) => ({
|
|
1764
|
+
...current,
|
|
1765
|
+
[providerKey(provider.kind, provider.id)]: event.target.value
|
|
1766
|
+
}))
|
|
1767
|
+
})]
|
|
1768
|
+
}) : null,
|
|
1769
|
+
showKey || needsKey && !shared && provider.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1770
|
+
className: "dsh-ui-actions",
|
|
1771
|
+
children: [showKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1772
|
+
variant: "outline",
|
|
1773
|
+
size: "sm",
|
|
1774
|
+
disabled: busy || reordering || readOnly || !typedKey,
|
|
1775
|
+
onClick: () => void action(async () => {
|
|
1776
|
+
await saveKeys(provider);
|
|
1777
|
+
await load();
|
|
1778
|
+
setNote({ key: "keySaved" });
|
|
1779
|
+
}),
|
|
1780
|
+
children: text.saveKey
|
|
1781
|
+
}) : null, needsKey && !shared && provider.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmButton, {
|
|
1782
|
+
disabled: busy || reordering || readOnly,
|
|
1783
|
+
label: text.deleteKey,
|
|
1784
|
+
confirmLabel: text.confirmDelete,
|
|
1785
|
+
onConfirm: () => void action(() => removeKey(provider))
|
|
1786
|
+
}) : null]
|
|
1787
|
+
}) : null
|
|
1788
|
+
]
|
|
1789
|
+
}),
|
|
1790
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1791
|
+
className: "web-search-rank-move",
|
|
1792
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
1793
|
+
checked: participating,
|
|
1794
|
+
label: providerDisplayLabel(provider, locale),
|
|
1795
|
+
disabled: locked || dragSource !== null,
|
|
1796
|
+
onChange: (next) => {
|
|
1797
|
+
const following = next ? [...order, provider.id] : order.filter((id) => id !== provider.id);
|
|
1798
|
+
runImmediate(on && following.some((id) => saved.providers.some((item) => item.kind === kind && item.id === id && item.configured)), following, droppedDormantIds(saved.settings, following, saved.providers, kind), kind);
|
|
1799
|
+
}
|
|
1800
|
+
})
|
|
1801
|
+
})
|
|
1721
1802
|
]
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
children: [providerDisplayLabel(provider, locale), provider.configured ? "" : " · " + text.unavailable]
|
|
1725
|
-
}, provider.id))]
|
|
1726
|
-
})]
|
|
1803
|
+
}, provider.id);
|
|
1804
|
+
})
|
|
1727
1805
|
}),
|
|
1728
|
-
|
|
1806
|
+
kind === "search" && backends.some(usesBundledPricing) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1729
1807
|
className: "dsh-ui-hint",
|
|
1730
|
-
children:
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1808
|
+
children: pricingCheckedText(locale, PROVIDER_PRICING_CHECKED)
|
|
1809
|
+
}) : null,
|
|
1810
|
+
on && kind === "search" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1811
|
+
className: "dsh-ui-actions",
|
|
1812
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1813
|
+
variant: "outline",
|
|
1814
|
+
size: "md",
|
|
1815
|
+
disabled: busy || reordering,
|
|
1816
|
+
onClick: () => void action(async () => {
|
|
1817
|
+
await saveKeysForKind("search");
|
|
1818
|
+
const result = await call("test");
|
|
1819
|
+
await load();
|
|
1820
|
+
setNote({ sources: result.sources });
|
|
1821
|
+
}),
|
|
1822
|
+
children: text.test
|
|
1734
1823
|
})
|
|
1735
|
-
}) : fetchAccess?.kind === "owner" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1736
|
-
className: "dsh-ui-hint",
|
|
1737
|
-
children: [text.ownerSettings, fetchAccess.name]
|
|
1738
|
-
}) : fetchAccess?.kind === "unknown" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1739
|
-
className: "dsh-ui-hint",
|
|
1740
|
-
children: text.unknownOwner
|
|
1741
1824
|
}) : null
|
|
1742
1825
|
]
|
|
1743
|
-
})
|
|
1744
|
-
})
|
|
1826
|
+
})
|
|
1827
|
+
}, kind);
|
|
1745
1828
|
}),
|
|
1746
1829
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1747
1830
|
role: "tabpanel",
|
|
@@ -1750,11 +1833,11 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1750
1833
|
hidden: tab !== "limits",
|
|
1751
1834
|
tabIndex: 0,
|
|
1752
1835
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("fieldset", {
|
|
1753
|
-
className: "
|
|
1836
|
+
className: "dsh-ui-fields-grid",
|
|
1754
1837
|
disabled: busy || reordering,
|
|
1755
1838
|
"aria-label": text.tabLimits,
|
|
1756
1839
|
children: LIMITS.map(([key, min, max]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1757
|
-
className: "
|
|
1840
|
+
className: "dsh-ui-field",
|
|
1758
1841
|
children: [
|
|
1759
1842
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1760
1843
|
className: "dsh-ui-label",
|
|
@@ -1793,7 +1876,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1793
1876
|
})
|
|
1794
1877
|
}),
|
|
1795
1878
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1796
|
-
className: "dsh-ui-
|
|
1879
|
+
className: "dsh-ui-form-footer dsh-ui-actions-end",
|
|
1797
1880
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1798
1881
|
className: "dsh-ui-meta",
|
|
1799
1882
|
children: limitsDirty || keysDirty ? text.unsaved : text.saveHint
|
|
@@ -1808,74 +1891,13 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1808
1891
|
]
|
|
1809
1892
|
});
|
|
1810
1893
|
}
|
|
1811
|
-
const styles = `${officialUiCss("ws-root")}
|
|
1812
|
-
.ws-root { max-width: 760px; }
|
|
1813
|
-
.ws-card-head { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding-bottom: 8px; }
|
|
1814
|
-
.ws-sr-only {
|
|
1815
|
-
position: absolute;
|
|
1816
|
-
width: 1px;
|
|
1817
|
-
height: 1px;
|
|
1818
|
-
padding: 0;
|
|
1819
|
-
margin: -1px;
|
|
1820
|
-
overflow: hidden;
|
|
1821
|
-
clip-path: inset(50%);
|
|
1822
|
-
white-space: nowrap;
|
|
1823
|
-
border: 0;
|
|
1824
|
-
}
|
|
1825
|
-
.ws-provider-note { display: flex; align-items: center; gap: 6px; }
|
|
1826
|
-
.web-search-provider { display: grid; gap: 8px; min-width: 0; }
|
|
1827
|
-
.web-search-provider-info { display: grid; gap: 3px; font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-secondary); overflow-wrap: anywhere; }
|
|
1828
|
-
.web-search-provider-heading { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; min-height: 24px; }
|
|
1829
|
-
.web-search-settings a { color: var(--dsw-alias-state-business-primary); font-size: 12px; line-height: 18px; text-underline-offset: 3px; }
|
|
1830
|
-
/* The primitive renders a 32px control in an inline-flex wrapper; a settings
|
|
1831
|
-
* field needs it to fill the column it sits in. */
|
|
1832
|
-
.ws-input { width: 100%; }
|
|
1833
|
-
.web-search-rank { margin: 0; padding: 0; list-style: none; }
|
|
1834
|
-
.web-search-rank li {
|
|
1835
|
-
position: relative;
|
|
1836
|
-
display: grid;
|
|
1837
|
-
grid-template-columns: 24px minmax(0, 1fr) auto;
|
|
1838
|
-
gap: 10px;
|
|
1839
|
-
align-items: start;
|
|
1840
|
-
padding: 14px 0;
|
|
1841
|
-
border-top: 0.5px solid var(--dsw-alias-border-l2);
|
|
1842
|
-
}
|
|
1843
|
-
.web-search-settings button.web-search-drag-handle {
|
|
1844
|
-
display: grid;
|
|
1845
|
-
place-items: center;
|
|
1846
|
-
width: 24px;
|
|
1847
|
-
height: 24px;
|
|
1848
|
-
min-height: 24px;
|
|
1849
|
-
padding: 0;
|
|
1850
|
-
cursor: grab;
|
|
1851
|
-
opacity: 0.65;
|
|
1852
|
-
}
|
|
1853
|
-
.web-search-settings button.web-search-drag-handle:hover { opacity: 1; }
|
|
1854
|
-
.web-search-settings button.web-search-drag-handle:active { cursor: grabbing; }
|
|
1855
|
-
.web-search-settings button.web-search-drag-handle[aria-disabled='true'] { cursor: wait; opacity: 0.4; }
|
|
1856
|
-
.web-search-rank li[data-dragging] { opacity: 0.45; }
|
|
1857
|
-
.web-search-rank li[data-drop]::after { content: ''; position: absolute; left: 0; right: 0; height: 2px; background: var(--dsw-alias-state-business-primary); pointer-events: none; }
|
|
1858
|
-
.web-search-rank li[data-drop='before']::after { top: -1px; }
|
|
1859
|
-
.web-search-rank li[data-drop='after']::after { bottom: -1px; }
|
|
1860
|
-
.web-search-rank-index { color: var(--dsw-alias-label-tertiary); font-variant-numeric: tabular-nums; line-height: 24px; }
|
|
1861
|
-
.web-search-rank-move { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; justify-content: flex-end; padding-top: 2px; }
|
|
1862
|
-
.web-search-limits { margin: 0; border: 0; padding: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 20px; }
|
|
1863
|
-
/* A two-column grid, so these fields keep the contract's caption and the
|
|
1864
|
-
* primitive's control and only own their vertical rhythm — a stacked list is
|
|
1865
|
-
* what draws the hairline between fields, and there is nothing to separate. */
|
|
1866
|
-
.ws-limit { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
|
|
1867
|
-
.ws-danger:hover:not(:disabled),
|
|
1868
|
-
.ws-danger[data-armed] { color: var(--dsw-alias-state-error-primary); border-color: var(--dsw-alias-state-error-primary); }
|
|
1869
|
-
.ws-danger:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover-danger); }
|
|
1870
|
-
@media (max-width: 560px) { .web-search-rank-move { flex-direction: column; align-items: flex-end; } .web-search-limits { grid-template-columns: minmax(0, 1fr); } }
|
|
1871
|
-
`;
|
|
1872
1894
|
function apply(ctx) {
|
|
1873
1895
|
const client = ctx;
|
|
1874
1896
|
ctx.effect(() => {
|
|
1875
1897
|
if (typeof document === "undefined") return () => {};
|
|
1876
1898
|
const style = document.createElement("style");
|
|
1877
1899
|
style.setAttribute("data-plugin", "@klarkxy/dsh-web-search-manager");
|
|
1878
|
-
style.textContent =
|
|
1900
|
+
style.textContent = webSearchClientStyles;
|
|
1879
1901
|
style.dataset.dshWebSearch = "";
|
|
1880
1902
|
document.head.appendChild(style);
|
|
1881
1903
|
return () => style.remove();
|