@klarkxy/dsh-web-search-manager 0.2.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -8
- package/docs/README.zh-CN.md +13 -7
- package/lib/client.inner.cjs +414 -423
- package/lib/client.inner.cjs.map +1 -1
- package/lib/client.js +414 -423
- 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
|
@@ -467,8 +467,8 @@ function createPluginReadGate(now = Date.now) {
|
|
|
467
467
|
//#endregion
|
|
468
468
|
//#region src/contracts.ts
|
|
469
469
|
const WEB_SEARCH_RPC_CHANNEL = "/web-search-manager";
|
|
470
|
-
function
|
|
471
|
-
return
|
|
470
|
+
function providerKey(kind, id) {
|
|
471
|
+
return `${kind}:${id}`;
|
|
472
472
|
}
|
|
473
473
|
function safeConfigurationUrl(value) {
|
|
474
474
|
if (typeof value !== "string" || !value || /[\u0000-\u0020\\]/.test(value)) return;
|
|
@@ -485,6 +485,21 @@ function safeConfigurationUrl(value) {
|
|
|
485
485
|
//#region src/provider-info.ts
|
|
486
486
|
const PROVIDER_PRICING_CHECKED = "2026-09-21";
|
|
487
487
|
const providerInfo = {
|
|
488
|
+
perplexity: {
|
|
489
|
+
description: "通过 Perplexity Search API 获取网页来源,不生成回答。",
|
|
490
|
+
pricing: "按 Search API 请求计费;实际价格与额度以官方账户为准。",
|
|
491
|
+
pricingUrl: "https://docs.perplexity.ai/docs/getting-started/pricing"
|
|
492
|
+
},
|
|
493
|
+
you: {
|
|
494
|
+
description: "通过 You.com 获取网页与新闻结果,不请求额外全文提取。",
|
|
495
|
+
pricing: "按 Web Search API 请求计费;实际价格与额度以官方账户为准。",
|
|
496
|
+
pricingUrl: "https://you.com/docs/administration/billing"
|
|
497
|
+
},
|
|
498
|
+
"firecrawl-fetch": {
|
|
499
|
+
description: "读取网页 Markdown;与 Firecrawl 搜索共用 Key。",
|
|
500
|
+
pricing: "网页读取使用 Firecrawl 积分,与搜索分别计费;实际消耗以官方账户为准。",
|
|
501
|
+
pricingUrl: "https://www.firecrawl.dev/pricing"
|
|
502
|
+
},
|
|
488
503
|
ddg: {
|
|
489
504
|
description: "搜索 DuckDuckGo 公开网页,无需注册或 API Key。",
|
|
490
505
|
pricing: "免费使用,不产生搜索 API 费用。",
|
|
@@ -527,6 +542,18 @@ const providerInfo = {
|
|
|
527
542
|
}
|
|
528
543
|
};
|
|
529
544
|
const englishInfo = {
|
|
545
|
+
perplexity: {
|
|
546
|
+
description: "Get web sources through the Perplexity Search API without generating an answer.",
|
|
547
|
+
pricing: "Charged per Search API request; current prices and credits depend on your official account."
|
|
548
|
+
},
|
|
549
|
+
you: {
|
|
550
|
+
description: "Get web and news results through You.com without requesting extra full-page extraction.",
|
|
551
|
+
pricing: "Charged per Web Search API request; current prices and credits depend on your official account."
|
|
552
|
+
},
|
|
553
|
+
"firecrawl-fetch": {
|
|
554
|
+
description: "Read page Markdown using the same key as Firecrawl search.",
|
|
555
|
+
pricing: "Page reads use Firecrawl credits and are charged separately from search; your official account determines usage."
|
|
556
|
+
},
|
|
530
557
|
ddg: {
|
|
531
558
|
description: "Search public DuckDuckGo pages without registration or an API key.",
|
|
532
559
|
pricing: "Free to use; no search API fees."
|
|
@@ -561,6 +588,9 @@ const englishInfo = {
|
|
|
561
588
|
}
|
|
562
589
|
};
|
|
563
590
|
const bundledDescriptions = {
|
|
591
|
+
perplexity: ["Search API;返回网页来源,不生成回答。", "Search API returning web sources without generating an answer."],
|
|
592
|
+
you: ["网页与新闻搜索;不额外提取全文。", "Web and news search without extra full-page extraction."],
|
|
593
|
+
"firecrawl-fetch": ["读取网页 Markdown;与 Firecrawl 搜索共用 Key。", "Read page Markdown using the same key as Firecrawl search."],
|
|
564
594
|
ddg: ["无需注册。默认后端。", "No registration required. Default backend."],
|
|
565
595
|
"deepseek-official": ["使用已配置的 DeepSeek API Key,可能产生额外搜索费用。", "Uses the configured DeepSeek API key; additional search fees may apply."],
|
|
566
596
|
exa: ["独立 Search API。", "Independent Search API."],
|
|
@@ -580,6 +610,7 @@ function providerDisplayLabel(provider, locale) {
|
|
|
580
610
|
}
|
|
581
611
|
function bundledCatalogId(provider) {
|
|
582
612
|
if (!(typeof provider.configurationOwned === "boolean" ? provider.configurationOwned : !provider.configurationOwner || provider.configurationOwner === "@klarkxy/dsh-web-search-manager")) return;
|
|
613
|
+
if (provider.kind === "fetch" && provider.id === "firecrawl") return "firecrawl-fetch";
|
|
583
614
|
return provider.id === "deepseek-managed" ? "deepseek-official" : provider.id;
|
|
584
615
|
}
|
|
585
616
|
function missingPricing(locale) {
|
|
@@ -588,7 +619,11 @@ function missingPricing(locale) {
|
|
|
588
619
|
function usesBundledPricing(provider) {
|
|
589
620
|
if (provider.pricing?.trim()) return false;
|
|
590
621
|
const catalogId = bundledCatalogId(provider);
|
|
591
|
-
return Boolean(catalogId &&
|
|
622
|
+
return Boolean(catalogId && ![
|
|
623
|
+
"perplexity",
|
|
624
|
+
"you",
|
|
625
|
+
"firecrawl-fetch"
|
|
626
|
+
].includes(catalogId) && Object.hasOwn(providerInfo, catalogId));
|
|
592
627
|
}
|
|
593
628
|
function searchProviderInfo(provider, locale = "zh") {
|
|
594
629
|
const catalogId = bundledCatalogId(provider);
|
|
@@ -685,52 +720,61 @@ function bindStatusRefresh(options) {
|
|
|
685
720
|
};
|
|
686
721
|
}
|
|
687
722
|
function isProviderOn(status, provider) {
|
|
688
|
-
if (provider.kind === "fetch") return status.fetchActive && status
|
|
723
|
+
if (provider.kind === "fetch") return status.fetchActive && selectedBackend(status, "fetch")?.id === provider.id;
|
|
689
724
|
return status.searchActive && selectedSearchBackend(status)?.id === provider.id;
|
|
690
725
|
}
|
|
691
726
|
function dormantSearchPlaceholder(id) {
|
|
727
|
+
return dormantPlaceholder(id, "search");
|
|
728
|
+
}
|
|
729
|
+
function dormantPlaceholder(id, kind) {
|
|
692
730
|
return {
|
|
693
731
|
id,
|
|
694
732
|
label: id,
|
|
695
733
|
description: "",
|
|
696
734
|
billing: "unknown",
|
|
697
|
-
kind
|
|
735
|
+
kind,
|
|
698
736
|
configured: false,
|
|
699
737
|
calls: 0,
|
|
700
738
|
failures: 0,
|
|
701
739
|
configurationOwned: false
|
|
702
740
|
};
|
|
703
741
|
}
|
|
742
|
+
function isRegistered(status, id, kind) {
|
|
743
|
+
return status.providers.some((provider) => provider.kind === kind && provider.id === id);
|
|
744
|
+
}
|
|
704
745
|
function isRegisteredSearch(status, id) {
|
|
705
|
-
return status
|
|
746
|
+
return isRegistered(status, id, "search");
|
|
706
747
|
}
|
|
707
|
-
function
|
|
708
|
-
|
|
748
|
+
function providerOrder(settings, kind) {
|
|
749
|
+
return kind === "search" ? settings.searchOrder : settings.fetchOrder;
|
|
750
|
+
}
|
|
751
|
+
function catalogBackends(status, kind) {
|
|
752
|
+
const byId = new Map(status.providers.filter((provider) => provider.kind === kind).map((provider) => [provider.id, provider]));
|
|
709
753
|
const rows = [];
|
|
710
754
|
const seen = /* @__PURE__ */ new Set();
|
|
711
|
-
for (const id of status.settings
|
|
755
|
+
for (const id of providerOrder(status.settings, kind)) {
|
|
712
756
|
if (!id || seen.has(id)) continue;
|
|
713
757
|
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);
|
|
758
|
+
rows.push(byId.get(id) ?? dormantPlaceholder(id, kind));
|
|
719
759
|
}
|
|
760
|
+
for (const provider of byId.values()) if (!seen.has(provider.id)) rows.push(provider);
|
|
720
761
|
return rows;
|
|
721
762
|
}
|
|
763
|
+
function catalogSearchBackends(status) {
|
|
764
|
+
return catalogBackends(status, "search");
|
|
765
|
+
}
|
|
722
766
|
function searchBackends(status) {
|
|
723
|
-
|
|
724
|
-
|
|
767
|
+
return catalogSearchBackends(status).filter((provider) => status.settings.searchOrder.includes(provider.id));
|
|
768
|
+
}
|
|
769
|
+
function selectedBackend(status, kind) {
|
|
770
|
+
return providerOrder(status.settings, kind).flatMap((id) => status.providers.find((provider) => provider.kind === kind && provider.id === id) ?? []).find((provider) => provider.configured);
|
|
725
771
|
}
|
|
726
772
|
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);
|
|
773
|
+
return selectedBackend(status, "search");
|
|
730
774
|
}
|
|
731
|
-
function droppedDormantIds(settings, order, providers) {
|
|
732
|
-
const known = new Set(providers.filter((provider) => provider.kind ===
|
|
733
|
-
return [...new Set(settings.
|
|
775
|
+
function droppedDormantIds(settings, order, providers, kind = "search") {
|
|
776
|
+
const known = new Set(providers.filter((provider) => provider.kind === kind).map((provider) => provider.id));
|
|
777
|
+
return [...new Set(providerOrder(settings, kind).filter((id) => id && !known.has(id) && !order.includes(id)))];
|
|
734
778
|
}
|
|
735
779
|
function configurationAccess(provider) {
|
|
736
780
|
if (ownsProviderConfiguration(provider)) return { kind: "owned" };
|
|
@@ -811,8 +855,9 @@ function ConfirmButton(props) {
|
|
|
811
855
|
const copy = {
|
|
812
856
|
zh: {
|
|
813
857
|
tabsLabel: "网络搜索设置",
|
|
814
|
-
|
|
815
|
-
|
|
858
|
+
tabSearch: "web-search",
|
|
859
|
+
tabFetch: "web-fetch",
|
|
860
|
+
tabLimits: "设置",
|
|
816
861
|
loading: "正在读取设置…",
|
|
817
862
|
loadFailed: "读取设置失败:",
|
|
818
863
|
reconnect: "重新连接",
|
|
@@ -853,13 +898,16 @@ const copy = {
|
|
|
853
898
|
maxFetchChars: "正文字符上限",
|
|
854
899
|
fetchTitle: "网页读取",
|
|
855
900
|
fetchProvider: "读取服务",
|
|
901
|
+
fetchBackends: "读取后端",
|
|
902
|
+
needFetchBackend: "需选择并配置读取服务。",
|
|
856
903
|
ownerSettings: "配置入口:",
|
|
857
904
|
unknownOwner: "请在服务所属插件中配置"
|
|
858
905
|
},
|
|
859
906
|
en: {
|
|
860
907
|
tabsLabel: "Web search settings",
|
|
861
|
-
|
|
862
|
-
|
|
908
|
+
tabSearch: "web-search",
|
|
909
|
+
tabFetch: "web-fetch",
|
|
910
|
+
tabLimits: "Settings",
|
|
863
911
|
loading: "Loading settings…",
|
|
864
912
|
loadFailed: "Could not load settings: ",
|
|
865
913
|
reconnect: "Reconnect",
|
|
@@ -900,6 +948,8 @@ const copy = {
|
|
|
900
948
|
maxFetchChars: "Max text chars",
|
|
901
949
|
fetchTitle: "Read web pages",
|
|
902
950
|
fetchProvider: "Fetch service",
|
|
951
|
+
fetchBackends: "Fetch backends",
|
|
952
|
+
needFetchBackend: "Choose and configure a fetch service.",
|
|
903
953
|
ownerSettings: "Configure in: ",
|
|
904
954
|
unknownOwner: "Configure this service in its plugin"
|
|
905
955
|
}
|
|
@@ -916,7 +966,7 @@ function uiMessage(message, locale, providers = []) {
|
|
|
916
966
|
if ("key" in message) return copy[locale][message.key];
|
|
917
967
|
if ("sources" in message) return testOkText(locale, message.sources);
|
|
918
968
|
if ("moved" in message) {
|
|
919
|
-
const provider = providers.find((item) => item.id === message.moved);
|
|
969
|
+
const provider = providers.find((item) => item.id === message.moved && item.kind === message.kind);
|
|
920
970
|
return movedText(locale, provider ? providerDisplayLabel(provider, locale) : message.moved, message.position);
|
|
921
971
|
}
|
|
922
972
|
return message.invalid.map((key) => {
|
|
@@ -969,15 +1019,15 @@ function parseLimit(raw, min, max) {
|
|
|
969
1019
|
* Settings written by an immediate action (a switch or a reorder). It keeps the
|
|
970
1020
|
* saved limits and never includes typed-but-unsaved keys or limit drafts.
|
|
971
1021
|
*/
|
|
972
|
-
function immediateUpdate(settings, on, order, providers, remove = []) {
|
|
1022
|
+
function immediateUpdate(settings, on, order, providers, remove = [], kind = "search") {
|
|
973
1023
|
let searchOrder = order;
|
|
974
1024
|
if (providers) {
|
|
975
|
-
const known = new Set(providers.filter((provider) => provider.kind ===
|
|
1025
|
+
const known = new Set(providers.filter((provider) => provider.kind === kind).map((provider) => provider.id));
|
|
976
1026
|
const removed = new Set(remove);
|
|
977
1027
|
const following = order.filter((id) => !removed.has(id));
|
|
978
1028
|
if (following.some((id) => !known.has(id))) searchOrder = following;
|
|
979
1029
|
else {
|
|
980
|
-
searchOrder = settings.
|
|
1030
|
+
searchOrder = providerOrder(settings, kind).flatMap((id) => {
|
|
981
1031
|
if (removed.has(id)) return [];
|
|
982
1032
|
return known.has(id) ? following.splice(0, 1) : [id];
|
|
983
1033
|
});
|
|
@@ -986,8 +1036,13 @@ function immediateUpdate(settings, on, order, providers, remove = []) {
|
|
|
986
1036
|
}
|
|
987
1037
|
return {
|
|
988
1038
|
...editable(settings),
|
|
989
|
-
|
|
990
|
-
|
|
1039
|
+
...kind === "search" ? {
|
|
1040
|
+
searchOrder,
|
|
1041
|
+
searchEnabled: on
|
|
1042
|
+
} : {
|
|
1043
|
+
fetchOrder: searchOrder,
|
|
1044
|
+
fetchEnabled: on
|
|
1045
|
+
}
|
|
991
1046
|
};
|
|
992
1047
|
}
|
|
993
1048
|
function limitTexts(settings) {
|
|
@@ -1009,7 +1064,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1009
1064
|
const locale = useHostLocale(client);
|
|
1010
1065
|
const text = copy[locale];
|
|
1011
1066
|
const tabsId = (0, react.useId)();
|
|
1012
|
-
const [tab, setTab] = (0, react.useState)("
|
|
1067
|
+
const [tab, setTab] = (0, react.useState)("search");
|
|
1013
1068
|
const [status, setStatus] = (0, react.useState)();
|
|
1014
1069
|
const [limitDraft, setLimitDraft] = (0, react.useState)();
|
|
1015
1070
|
const [invalid, setInvalid] = (0, react.useState)({});
|
|
@@ -1026,6 +1081,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1026
1081
|
const statusRef = (0, react.useRef)(status);
|
|
1027
1082
|
statusRef.current = status;
|
|
1028
1083
|
const pendingRef = (0, react.useRef)(null);
|
|
1084
|
+
const dragRef = (0, react.useRef)(null);
|
|
1029
1085
|
const timerRef = (0, react.useRef)(void 0);
|
|
1030
1086
|
const handleRefs = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
1031
1087
|
const refocusRef = (0, react.useRef)(null);
|
|
@@ -1061,7 +1117,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1061
1117
|
mounted: mountedRef.current,
|
|
1062
1118
|
hidden: isPageHidden(hiddenRef.current),
|
|
1063
1119
|
busy: busyRef.current,
|
|
1064
|
-
reordering: pendingRef.current !== null,
|
|
1120
|
+
reordering: pendingRef.current !== null || dragRef.current !== null,
|
|
1065
1121
|
reading: readingRef.current
|
|
1066
1122
|
});
|
|
1067
1123
|
}
|
|
@@ -1098,7 +1154,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1098
1154
|
requestId: mine,
|
|
1099
1155
|
latestRequestId: requestSeq.current,
|
|
1100
1156
|
busy: busyRef.current,
|
|
1101
|
-
reordering: pendingRef.current !== null,
|
|
1157
|
+
reordering: pendingRef.current !== null || dragRef.current !== null,
|
|
1102
1158
|
hidden: isPageHidden(hiddenRef.current),
|
|
1103
1159
|
currentRevision: statusRef.current?.settings.revision,
|
|
1104
1160
|
incomingRevision: next.settings.revision
|
|
@@ -1148,7 +1204,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1148
1204
|
const current = statusRef.current;
|
|
1149
1205
|
const next = pendingRef.current;
|
|
1150
1206
|
if (current && next) call("update", {
|
|
1151
|
-
settings: immediateUpdate(current.settings, current.settings.searchEnabled, next, current.providers),
|
|
1207
|
+
settings: immediateUpdate(current.settings, next.kind === "search" ? current.settings.searchEnabled : current.settings.fetchEnabled, next.order, current.providers, [], next.kind),
|
|
1152
1208
|
expectedRevision: current.settings.revision
|
|
1153
1209
|
}).catch(() => {});
|
|
1154
1210
|
};
|
|
@@ -1166,6 +1222,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1166
1222
|
if (handle && document.activeElement !== handle) handle.focus();
|
|
1167
1223
|
});
|
|
1168
1224
|
async function action(run) {
|
|
1225
|
+
if (busyRef.current) return;
|
|
1169
1226
|
readGate.reset();
|
|
1170
1227
|
requestSeq.current += 1;
|
|
1171
1228
|
busyRef.current = true;
|
|
@@ -1183,18 +1240,17 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1183
1240
|
}
|
|
1184
1241
|
}
|
|
1185
1242
|
/** Switches and reorders: settings only, applied at once. */
|
|
1186
|
-
function runImmediate(on, order, remove = []) {
|
|
1243
|
+
function runImmediate(on, order, remove = [], kind = "search") {
|
|
1187
1244
|
clearTimeout(timerRef.current);
|
|
1188
1245
|
timerRef.current = void 0;
|
|
1189
1246
|
pendingRef.current = null;
|
|
1190
1247
|
action(async () => {
|
|
1191
1248
|
const current = statusRef.current;
|
|
1192
1249
|
if (!current) return;
|
|
1193
|
-
|
|
1194
|
-
settings: immediateUpdate(current.settings, on, order, current.providers, remove),
|
|
1250
|
+
adopt(await call("update", {
|
|
1251
|
+
settings: immediateUpdate(current.settings, on, order, current.providers, remove, kind),
|
|
1195
1252
|
expectedRevision: current.settings.revision
|
|
1196
|
-
});
|
|
1197
|
-
setStatus(next);
|
|
1253
|
+
}));
|
|
1198
1254
|
await load();
|
|
1199
1255
|
}).then(() => {
|
|
1200
1256
|
if (pendingRef.current === null) setPendingOrder(null);
|
|
@@ -1204,24 +1260,27 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1204
1260
|
async function saveKeys(only) {
|
|
1205
1261
|
if (!status) return;
|
|
1206
1262
|
for (const provider of status.providers) {
|
|
1207
|
-
if (
|
|
1208
|
-
const snapshot = keys[provider.id] ?? "";
|
|
1263
|
+
if (only && providerKey(provider.kind, provider.id) !== providerKey(only.kind, only.id)) continue;
|
|
1264
|
+
const snapshot = keys[providerKey(provider.kind, provider.id)] ?? "";
|
|
1209
1265
|
const typed = snapshot.trim();
|
|
1210
1266
|
if (!ownsProviderConfiguration(provider) || !provider.credentialRef || !typed) continue;
|
|
1211
1267
|
if (writable[provider.credentialRef] === false) throw new UiError("readOnly");
|
|
1212
1268
|
unwrap(await client.remote.credentials.set(provider.credentialRef, typed));
|
|
1213
|
-
setKeys((current) => current[provider.id] === snapshot ? {
|
|
1269
|
+
setKeys((current) => current[providerKey(provider.kind, provider.id)] === snapshot ? {
|
|
1214
1270
|
...current,
|
|
1215
|
-
[provider.id]: ""
|
|
1271
|
+
[providerKey(provider.kind, provider.id)]: ""
|
|
1216
1272
|
} : current);
|
|
1217
1273
|
}
|
|
1218
1274
|
}
|
|
1275
|
+
async function saveKeysForKind(kind) {
|
|
1276
|
+
for (const provider of statusRef.current?.providers ?? []) if (provider.kind === kind) await saveKeys(provider);
|
|
1277
|
+
}
|
|
1219
1278
|
async function removeKey(provider) {
|
|
1220
1279
|
if (!ownsProviderConfiguration(provider) || !provider.credentialRef || provider.credentialShared || !status) return;
|
|
1221
1280
|
unwrap(await client.remote.credentials.unset(provider.credentialRef));
|
|
1222
1281
|
setKeys((current) => ({
|
|
1223
1282
|
...current,
|
|
1224
|
-
[provider.id]: ""
|
|
1283
|
+
[providerKey(provider.kind, provider.id)]: ""
|
|
1225
1284
|
}));
|
|
1226
1285
|
await load();
|
|
1227
1286
|
setNote({ key: "keyDeleted" });
|
|
@@ -1250,28 +1309,30 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1250
1309
|
}) : null]
|
|
1251
1310
|
});
|
|
1252
1311
|
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
1312
|
const savedLimits = limitTexts(saved.settings);
|
|
1261
1313
|
const limitsDirty = LIMITS.some(([key]) => limitDraft[key] !== savedLimits[key]);
|
|
1262
1314
|
const keysDirty = Object.values(keys).some((value) => value.trim());
|
|
1263
|
-
const reordering = pendingOrder !== null;
|
|
1264
|
-
function move(id, nextIndex, keyboard = false) {
|
|
1315
|
+
const reordering = pendingOrder !== null || dragSource !== null;
|
|
1316
|
+
function move(kind, id, nextIndex, keyboard = false) {
|
|
1317
|
+
if (pendingRef.current && pendingRef.current.kind !== kind) return;
|
|
1318
|
+
const order = pendingRef.current?.order ?? providerOrder(statusRef.current.settings, kind);
|
|
1265
1319
|
const index = order.indexOf(id);
|
|
1266
1320
|
if (busy || index < 0 || nextIndex < 0 || nextIndex >= order.length || index === nextIndex) return;
|
|
1267
1321
|
const next = [...order];
|
|
1268
1322
|
const [item] = next.splice(index, 1);
|
|
1269
1323
|
next.splice(nextIndex, 0, item);
|
|
1270
|
-
pendingRef.current =
|
|
1271
|
-
|
|
1272
|
-
|
|
1324
|
+
pendingRef.current = {
|
|
1325
|
+
kind,
|
|
1326
|
+
order: next
|
|
1327
|
+
};
|
|
1328
|
+
setPendingOrder({
|
|
1329
|
+
kind,
|
|
1330
|
+
order: next
|
|
1331
|
+
});
|
|
1332
|
+
if (keyboard) refocusRef.current = providerKey(kind, id);
|
|
1273
1333
|
setSortAnnouncement({
|
|
1274
1334
|
moved: id,
|
|
1335
|
+
kind,
|
|
1275
1336
|
position: nextIndex + 1
|
|
1276
1337
|
});
|
|
1277
1338
|
clearTimeout(timerRef.current);
|
|
@@ -1279,10 +1340,11 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1279
1340
|
timerRef.current = void 0;
|
|
1280
1341
|
const batched = pendingRef.current;
|
|
1281
1342
|
const current = statusRef.current;
|
|
1282
|
-
if (batched && current) runImmediate(current.settings.searchEnabled, batched);
|
|
1343
|
+
if (batched && current) runImmediate(batched.kind === "search" ? current.settings.searchEnabled : current.settings.fetchEnabled, batched.order, [], batched.kind);
|
|
1283
1344
|
}, keyboard ? REORDER_DEBOUNCE_MS : 0);
|
|
1284
1345
|
}
|
|
1285
1346
|
function endDrag() {
|
|
1347
|
+
dragRef.current = null;
|
|
1286
1348
|
setDragSource(null);
|
|
1287
1349
|
setDropTarget(null);
|
|
1288
1350
|
}
|
|
@@ -1325,17 +1387,26 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1325
1387
|
value: tab,
|
|
1326
1388
|
onChange: setTab,
|
|
1327
1389
|
label: text.tabsLabel,
|
|
1328
|
-
items: [
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1390
|
+
items: [
|
|
1391
|
+
{
|
|
1392
|
+
value: "search",
|
|
1393
|
+
label: text.tabSearch,
|
|
1394
|
+
id: tabsId + "-search-tab",
|
|
1395
|
+
panelId: tabsId + "-search-panel"
|
|
1396
|
+
},
|
|
1397
|
+
{
|
|
1398
|
+
value: "fetch",
|
|
1399
|
+
label: text.tabFetch,
|
|
1400
|
+
id: tabsId + "-fetch-tab",
|
|
1401
|
+
panelId: tabsId + "-fetch-panel"
|
|
1402
|
+
},
|
|
1403
|
+
{
|
|
1404
|
+
value: "limits",
|
|
1405
|
+
label: text.tabLimits,
|
|
1406
|
+
id: tabsId + "-limits-tab",
|
|
1407
|
+
panelId: tabsId + "-limits-panel"
|
|
1408
|
+
}
|
|
1409
|
+
]
|
|
1339
1410
|
}),
|
|
1340
1411
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1341
1412
|
className: "ws-sr-only",
|
|
@@ -1374,374 +1445,294 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1374
1445
|
className: "dsh-ui-banner dsh-ui-banner--danger",
|
|
1375
1446
|
children: text.storageFailed
|
|
1376
1447
|
}),
|
|
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,
|
|
1448
|
+
["search", "fetch"].map((kind) => {
|
|
1449
|
+
const order = pendingOrder?.kind === kind ? pendingOrder.order : providerOrder(saved.settings, kind);
|
|
1450
|
+
const catalog = catalogBackends(saved, kind);
|
|
1451
|
+
const backends = [...order.flatMap((id) => catalog.find((provider) => provider.id === id) ?? []), ...catalog.filter((provider) => !order.includes(provider.id))];
|
|
1452
|
+
const selected = selectedBackend(saved, kind);
|
|
1453
|
+
const on = kind === "search" ? saved.settings.searchEnabled : saved.settings.fetchEnabled;
|
|
1454
|
+
const canEnable = order.some((id) => saved.providers.some((provider) => provider.kind === kind && provider.id === id && provider.configured));
|
|
1455
|
+
const sortingOther = pendingOrder !== null && pendingOrder.kind !== kind;
|
|
1456
|
+
const locked = busy || sortingOther || Boolean(dragSource && !dragSource.startsWith(kind + ":"));
|
|
1457
|
+
const title = kind === "search" ? text.toolTitle : text.fetchTitle;
|
|
1458
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1459
|
+
role: "tabpanel",
|
|
1460
|
+
id: tabsId + "-" + kind + "-panel",
|
|
1461
|
+
"aria-labelledby": tabsId + "-" + kind + "-tab",
|
|
1462
|
+
hidden: tab !== kind,
|
|
1463
|
+
tabIndex: 0,
|
|
1464
|
+
children: backends.length === 0 ? kind === "search" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1465
|
+
className: "dsh-ui-empty",
|
|
1466
|
+
children: text.empty
|
|
1467
|
+
}) : null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
1468
|
+
className: "web-search-card dsh-ui-card dsh-ui-card--flat",
|
|
1469
|
+
"data-testid": kind === "search" ? "web-search-tool" : "web-search-fetch",
|
|
1470
|
+
"data-on": on ? "true" : "false",
|
|
1658
1471
|
children: [
|
|
1659
1472
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
1660
1473
|
className: "ws-card-head",
|
|
1661
1474
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1662
1475
|
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
|
-
]
|
|
1476
|
+
children: title
|
|
1477
|
+
}), !canEnable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1478
|
+
className: "dsh-ui-hint",
|
|
1479
|
+
children: kind === "search" ? text.needBackend : text.needFetchBackend
|
|
1674
1480
|
})] }), /* @__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
|
-
})
|
|
1481
|
+
checked: on,
|
|
1482
|
+
label: title,
|
|
1483
|
+
disabled: locked || dragSource !== null || !on && !canEnable,
|
|
1484
|
+
onChange: (next) => runImmediate(next, order, [], kind)
|
|
1689
1485
|
})]
|
|
1690
1486
|
}),
|
|
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
|
-
|
|
1487
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
|
|
1488
|
+
className: "web-search-rank",
|
|
1489
|
+
"aria-label": kind === "search" ? text.backends : text.fetchBackends,
|
|
1490
|
+
children: backends.map((provider) => {
|
|
1491
|
+
const participating = order.includes(provider.id);
|
|
1492
|
+
const rank = order.indexOf(provider.id);
|
|
1493
|
+
const dormant = !isRegistered(saved, provider.id, kind);
|
|
1494
|
+
const active = !dormant && (kind === "search" ? saved.searchActive : saved.fetchActive) && selected?.id === provider.id;
|
|
1495
|
+
const info = kind === "search" || ownsProviderConfiguration(provider) && provider.id === "firecrawl" ? searchProviderInfo(provider, locale) : {
|
|
1496
|
+
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,
|
|
1497
|
+
pricing: provider.pricing ?? "",
|
|
1498
|
+
pricingUrl: provider.pricingUrl
|
|
1499
|
+
};
|
|
1500
|
+
const owned = ownsProviderConfiguration(provider);
|
|
1501
|
+
const access = configurationAccess(provider);
|
|
1502
|
+
const needsKey = owned && Boolean(provider.credentialRef);
|
|
1503
|
+
const shared = Boolean(provider.credentialShared);
|
|
1504
|
+
const showKey = !dormant && needsKey && participating && (!shared || !provider.configured);
|
|
1505
|
+
const canSort = participating && order.length > 1;
|
|
1506
|
+
const readOnly = writable[provider.credentialRef ?? ""] === false;
|
|
1507
|
+
const typedKey = (keys[providerKey(provider.kind, provider.id)] ?? "").trim();
|
|
1508
|
+
const availability = dormant ? "dormant" : provider.configured ? "available" : "unavailable";
|
|
1509
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
1510
|
+
className: active ? "is-active" : void 0,
|
|
1511
|
+
"data-testid": `web-${kind}-rank-${provider.id}`,
|
|
1512
|
+
"data-availability": availability,
|
|
1513
|
+
"data-in-use": active || void 0,
|
|
1514
|
+
"data-dragging": dragSource === providerKey(kind, provider.id) || void 0,
|
|
1515
|
+
"data-drop": dropTarget === providerKey(kind, provider.id) && dragSource ? order.indexOf(dragSource.slice(kind.length + 1)) < rank ? "after" : "before" : void 0,
|
|
1516
|
+
onDragOver: (event) => {
|
|
1517
|
+
if (locked || !canSort || !dragSource?.startsWith(kind + ":") || dragSource === providerKey(kind, provider.id)) return;
|
|
1518
|
+
event.preventDefault();
|
|
1519
|
+
event.dataTransfer.dropEffect = "move";
|
|
1520
|
+
setDropTarget(providerKey(kind, provider.id));
|
|
1521
|
+
},
|
|
1522
|
+
onDragLeave: (event) => {
|
|
1523
|
+
if (!event.currentTarget.contains(event.relatedTarget)) setDropTarget(null);
|
|
1524
|
+
},
|
|
1525
|
+
onDrop: (event) => {
|
|
1526
|
+
if (locked || !canSort || !dragSource?.startsWith(kind + ":")) return;
|
|
1527
|
+
event.preventDefault();
|
|
1528
|
+
move(kind, dragSource.slice(kind.length + 1), rank);
|
|
1529
|
+
endDrag();
|
|
1530
|
+
},
|
|
1717
1531
|
children: [
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1532
|
+
canSort ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1533
|
+
variant: "ghost",
|
|
1534
|
+
size: "sm",
|
|
1535
|
+
className: "web-search-drag-handle",
|
|
1536
|
+
ref: (element) => {
|
|
1537
|
+
if (element) handleRefs.current.set(providerKey(kind, provider.id), element);
|
|
1538
|
+
else handleRefs.current.delete(providerKey(kind, provider.id));
|
|
1539
|
+
},
|
|
1540
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
1541
|
+
width: "16",
|
|
1542
|
+
height: "20",
|
|
1543
|
+
viewBox: "0 0 16 20",
|
|
1544
|
+
fill: "currentColor",
|
|
1545
|
+
"aria-hidden": "true",
|
|
1546
|
+
children: [
|
|
1547
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1548
|
+
cx: "5",
|
|
1549
|
+
cy: "5",
|
|
1550
|
+
r: "1.5"
|
|
1551
|
+
}),
|
|
1552
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1553
|
+
cx: "11",
|
|
1554
|
+
cy: "5",
|
|
1555
|
+
r: "1.5"
|
|
1556
|
+
}),
|
|
1557
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1558
|
+
cx: "5",
|
|
1559
|
+
cy: "10",
|
|
1560
|
+
r: "1.5"
|
|
1561
|
+
}),
|
|
1562
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1563
|
+
cx: "11",
|
|
1564
|
+
cy: "10",
|
|
1565
|
+
r: "1.5"
|
|
1566
|
+
}),
|
|
1567
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1568
|
+
cx: "5",
|
|
1569
|
+
cy: "15",
|
|
1570
|
+
r: "1.5"
|
|
1571
|
+
}),
|
|
1572
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1573
|
+
cx: "11",
|
|
1574
|
+
cy: "15",
|
|
1575
|
+
r: "1.5"
|
|
1576
|
+
})
|
|
1577
|
+
]
|
|
1578
|
+
}),
|
|
1579
|
+
draggable: !locked,
|
|
1580
|
+
"aria-disabled": locked,
|
|
1581
|
+
"aria-label": text.dragPrefix + providerDisplayLabel(provider, locale),
|
|
1582
|
+
title: text.dragHint,
|
|
1583
|
+
onDragStart: (event) => {
|
|
1584
|
+
if (locked || pendingRef.current && pendingRef.current.kind !== kind) {
|
|
1585
|
+
event.preventDefault();
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
event.dataTransfer.effectAllowed = "move";
|
|
1589
|
+
event.dataTransfer.setData(`application/x-dsh-${kind}-provider`, provider.id);
|
|
1590
|
+
const row = event.currentTarget.closest("li");
|
|
1591
|
+
if (row) event.dataTransfer.setDragImage(row, 16, 16);
|
|
1592
|
+
dragRef.current = providerKey(kind, provider.id);
|
|
1593
|
+
requestSeq.current += 1;
|
|
1594
|
+
setDragSource(providerKey(kind, provider.id));
|
|
1595
|
+
setSortAnnouncement("");
|
|
1596
|
+
},
|
|
1597
|
+
onDragEnd: endDrag,
|
|
1598
|
+
onKeyDown: (event) => {
|
|
1599
|
+
if (event.key !== "ArrowUp" && event.key !== "ArrowDown") return;
|
|
1600
|
+
event.preventDefault();
|
|
1601
|
+
move(kind, provider.id, rank + (event.key === "ArrowUp" ? -1 : 1), true);
|
|
1602
|
+
}
|
|
1603
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1604
|
+
className: "web-search-rank-index",
|
|
1605
|
+
children: participating ? rank + 1 : "—"
|
|
1606
|
+
}),
|
|
1607
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1608
|
+
className: "web-search-provider",
|
|
1609
|
+
children: [
|
|
1610
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1611
|
+
className: "web-search-provider-heading",
|
|
1612
|
+
children: [
|
|
1613
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
|
|
1614
|
+
className: "dsh-ui-heading",
|
|
1615
|
+
children: providerDisplayLabel(provider, locale)
|
|
1616
|
+
}),
|
|
1617
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1618
|
+
className: "ws-provider-note",
|
|
1619
|
+
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 })]
|
|
1620
|
+
}),
|
|
1621
|
+
active && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
|
|
1622
|
+
tone: "success",
|
|
1623
|
+
children: text.inUse
|
|
1624
|
+
}),
|
|
1625
|
+
provider.signupUrl && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppLink, {
|
|
1626
|
+
url: provider.signupUrl,
|
|
1627
|
+
label: providerDisplayLabel(provider, locale) + " " + text.signup,
|
|
1628
|
+
children: text.signup
|
|
1629
|
+
})
|
|
1630
|
+
]
|
|
1631
|
+
}),
|
|
1632
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1633
|
+
className: "web-search-provider-info",
|
|
1634
|
+
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, {
|
|
1635
|
+
url: info.pricingUrl,
|
|
1636
|
+
label: providerDisplayLabel(provider, locale) + " " + text.pricing,
|
|
1637
|
+
children: provider.id === "ddg" ? text.website : text.pricing
|
|
1638
|
+
})] })] }) : null]
|
|
1639
|
+
}),
|
|
1640
|
+
access.kind === "link" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1641
|
+
className: "dsh-ui-hint",
|
|
1642
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppLink, {
|
|
1643
|
+
url: access.url,
|
|
1644
|
+
label: text.openConfig,
|
|
1645
|
+
children: text.openConfig
|
|
1646
|
+
})
|
|
1647
|
+
}) : access.kind === "owner" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1648
|
+
className: "dsh-ui-hint",
|
|
1649
|
+
children: [text.ownerSettings, access.name]
|
|
1650
|
+
}) : access.kind === "unknown" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1651
|
+
className: "dsh-ui-hint",
|
|
1652
|
+
children: text.unknownOwner
|
|
1653
|
+
}) : null,
|
|
1654
|
+
shared && participating && owned ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", {
|
|
1655
|
+
className: "dsh-ui-meta",
|
|
1656
|
+
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
|
|
1657
|
+
}) : null,
|
|
1658
|
+
showKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1659
|
+
className: "dsh-ui-field",
|
|
1660
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1661
|
+
className: "dsh-ui-label",
|
|
1662
|
+
children: "API Key"
|
|
1663
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
1664
|
+
className: "ws-input",
|
|
1665
|
+
type: "password",
|
|
1666
|
+
autoComplete: "off",
|
|
1667
|
+
spellCheck: false,
|
|
1668
|
+
disabled: busy || reordering || readOnly,
|
|
1669
|
+
value: keys[providerKey(provider.kind, provider.id)] ?? "",
|
|
1670
|
+
placeholder: provider.configured ? text.keyConfigured : text.keyPlaceholder,
|
|
1671
|
+
onChange: (event) => setKeys((current) => ({
|
|
1672
|
+
...current,
|
|
1673
|
+
[providerKey(provider.kind, provider.id)]: event.target.value
|
|
1674
|
+
}))
|
|
1675
|
+
})]
|
|
1676
|
+
}) : null,
|
|
1677
|
+
showKey || needsKey && !shared && provider.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1678
|
+
className: "dsh-ui-actions",
|
|
1679
|
+
children: [showKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1680
|
+
variant: "outline",
|
|
1681
|
+
size: "sm",
|
|
1682
|
+
disabled: busy || reordering || readOnly || !typedKey,
|
|
1683
|
+
onClick: () => void action(async () => {
|
|
1684
|
+
await saveKeys(provider);
|
|
1685
|
+
await load();
|
|
1686
|
+
setNote({ key: "keySaved" });
|
|
1687
|
+
}),
|
|
1688
|
+
children: text.saveKey
|
|
1689
|
+
}) : null, needsKey && !shared && provider.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmButton, {
|
|
1690
|
+
disabled: busy || reordering || readOnly,
|
|
1691
|
+
label: text.deleteKey,
|
|
1692
|
+
confirmLabel: text.confirmDelete,
|
|
1693
|
+
onConfirm: () => void action(() => removeKey(provider))
|
|
1694
|
+
}) : null]
|
|
1695
|
+
}) : null
|
|
1696
|
+
]
|
|
1697
|
+
}),
|
|
1698
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1699
|
+
className: "web-search-rank-move",
|
|
1700
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
1701
|
+
checked: participating,
|
|
1702
|
+
label: providerDisplayLabel(provider, locale),
|
|
1703
|
+
disabled: locked || dragSource !== null,
|
|
1704
|
+
onChange: (next) => {
|
|
1705
|
+
const following = next ? [...order, provider.id] : order.filter((id) => id !== provider.id);
|
|
1706
|
+
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);
|
|
1707
|
+
}
|
|
1708
|
+
})
|
|
1709
|
+
})
|
|
1721
1710
|
]
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
children: [providerDisplayLabel(provider, locale), provider.configured ? "" : " · " + text.unavailable]
|
|
1725
|
-
}, provider.id))]
|
|
1726
|
-
})]
|
|
1711
|
+
}, provider.id);
|
|
1712
|
+
})
|
|
1727
1713
|
}),
|
|
1728
|
-
|
|
1714
|
+
kind === "search" && backends.some(usesBundledPricing) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1729
1715
|
className: "dsh-ui-hint",
|
|
1730
|
-
children:
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1716
|
+
children: pricingCheckedText(locale, PROVIDER_PRICING_CHECKED)
|
|
1717
|
+
}) : null,
|
|
1718
|
+
on && kind === "search" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1719
|
+
className: "dsh-ui-actions",
|
|
1720
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1721
|
+
variant: "outline",
|
|
1722
|
+
size: "md",
|
|
1723
|
+
disabled: busy || reordering,
|
|
1724
|
+
onClick: () => void action(async () => {
|
|
1725
|
+
await saveKeysForKind("search");
|
|
1726
|
+
const result = await call("test");
|
|
1727
|
+
await load();
|
|
1728
|
+
setNote({ sources: result.sources });
|
|
1729
|
+
}),
|
|
1730
|
+
children: text.test
|
|
1734
1731
|
})
|
|
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
1732
|
}) : null
|
|
1742
1733
|
]
|
|
1743
|
-
})
|
|
1744
|
-
})
|
|
1734
|
+
})
|
|
1735
|
+
}, kind);
|
|
1745
1736
|
}),
|
|
1746
1737
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1747
1738
|
role: "tabpanel",
|