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