@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.js
CHANGED
|
@@ -473,8 +473,8 @@ function createPluginReadGate(now = Date.now) {
|
|
|
473
473
|
//#endregion
|
|
474
474
|
//#region src/contracts.ts
|
|
475
475
|
const WEB_SEARCH_RPC_CHANNEL = "/web-search-manager";
|
|
476
|
-
function
|
|
477
|
-
return
|
|
476
|
+
function providerKey(kind, id) {
|
|
477
|
+
return `${kind}:${id}`;
|
|
478
478
|
}
|
|
479
479
|
function safeConfigurationUrl(value) {
|
|
480
480
|
if (typeof value !== "string" || !value || /[\u0000-\u0020\\]/.test(value)) return;
|
|
@@ -491,6 +491,21 @@ function safeConfigurationUrl(value) {
|
|
|
491
491
|
//#region src/provider-info.ts
|
|
492
492
|
const PROVIDER_PRICING_CHECKED = "2026-09-21";
|
|
493
493
|
const providerInfo = {
|
|
494
|
+
perplexity: {
|
|
495
|
+
description: "通过 Perplexity Search API 获取网页来源,不生成回答。",
|
|
496
|
+
pricing: "按 Search API 请求计费;实际价格与额度以官方账户为准。",
|
|
497
|
+
pricingUrl: "https://docs.perplexity.ai/docs/getting-started/pricing"
|
|
498
|
+
},
|
|
499
|
+
you: {
|
|
500
|
+
description: "通过 You.com 获取网页与新闻结果,不请求额外全文提取。",
|
|
501
|
+
pricing: "按 Web Search API 请求计费;实际价格与额度以官方账户为准。",
|
|
502
|
+
pricingUrl: "https://you.com/docs/administration/billing"
|
|
503
|
+
},
|
|
504
|
+
"firecrawl-fetch": {
|
|
505
|
+
description: "读取网页 Markdown;与 Firecrawl 搜索共用 Key。",
|
|
506
|
+
pricing: "网页读取使用 Firecrawl 积分,与搜索分别计费;实际消耗以官方账户为准。",
|
|
507
|
+
pricingUrl: "https://www.firecrawl.dev/pricing"
|
|
508
|
+
},
|
|
494
509
|
ddg: {
|
|
495
510
|
description: "搜索 DuckDuckGo 公开网页,无需注册或 API Key。",
|
|
496
511
|
pricing: "免费使用,不产生搜索 API 费用。",
|
|
@@ -533,6 +548,18 @@ const providerInfo = {
|
|
|
533
548
|
}
|
|
534
549
|
};
|
|
535
550
|
const englishInfo = {
|
|
551
|
+
perplexity: {
|
|
552
|
+
description: "Get web sources through the Perplexity Search API without generating an answer.",
|
|
553
|
+
pricing: "Charged per Search API request; current prices and credits depend on your official account."
|
|
554
|
+
},
|
|
555
|
+
you: {
|
|
556
|
+
description: "Get web and news results through You.com without requesting extra full-page extraction.",
|
|
557
|
+
pricing: "Charged per Web Search API request; current prices and credits depend on your official account."
|
|
558
|
+
},
|
|
559
|
+
"firecrawl-fetch": {
|
|
560
|
+
description: "Read page Markdown using the same key as Firecrawl search.",
|
|
561
|
+
pricing: "Page reads use Firecrawl credits and are charged separately from search; your official account determines usage."
|
|
562
|
+
},
|
|
536
563
|
ddg: {
|
|
537
564
|
description: "Search public DuckDuckGo pages without registration or an API key.",
|
|
538
565
|
pricing: "Free to use; no search API fees."
|
|
@@ -567,6 +594,9 @@ const englishInfo = {
|
|
|
567
594
|
}
|
|
568
595
|
};
|
|
569
596
|
const bundledDescriptions = {
|
|
597
|
+
perplexity: ["Search API;返回网页来源,不生成回答。", "Search API returning web sources without generating an answer."],
|
|
598
|
+
you: ["网页与新闻搜索;不额外提取全文。", "Web and news search without extra full-page extraction."],
|
|
599
|
+
"firecrawl-fetch": ["读取网页 Markdown;与 Firecrawl 搜索共用 Key。", "Read page Markdown using the same key as Firecrawl search."],
|
|
570
600
|
ddg: ["无需注册。默认后端。", "No registration required. Default backend."],
|
|
571
601
|
"deepseek-official": ["使用已配置的 DeepSeek API Key,可能产生额外搜索费用。", "Uses the configured DeepSeek API key; additional search fees may apply."],
|
|
572
602
|
exa: ["独立 Search API。", "Independent Search API."],
|
|
@@ -586,6 +616,7 @@ function providerDisplayLabel(provider, locale) {
|
|
|
586
616
|
}
|
|
587
617
|
function bundledCatalogId(provider) {
|
|
588
618
|
if (!(typeof provider.configurationOwned === "boolean" ? provider.configurationOwned : !provider.configurationOwner || provider.configurationOwner === "@klarkxy/dsh-web-search-manager")) return;
|
|
619
|
+
if (provider.kind === "fetch" && provider.id === "firecrawl") return "firecrawl-fetch";
|
|
589
620
|
return provider.id === "deepseek-managed" ? "deepseek-official" : provider.id;
|
|
590
621
|
}
|
|
591
622
|
function missingPricing(locale) {
|
|
@@ -594,7 +625,11 @@ function missingPricing(locale) {
|
|
|
594
625
|
function usesBundledPricing(provider) {
|
|
595
626
|
if (provider.pricing?.trim()) return false;
|
|
596
627
|
const catalogId = bundledCatalogId(provider);
|
|
597
|
-
return Boolean(catalogId &&
|
|
628
|
+
return Boolean(catalogId && ![
|
|
629
|
+
"perplexity",
|
|
630
|
+
"you",
|
|
631
|
+
"firecrawl-fetch"
|
|
632
|
+
].includes(catalogId) && Object.hasOwn(providerInfo, catalogId));
|
|
598
633
|
}
|
|
599
634
|
function searchProviderInfo(provider, locale = "zh") {
|
|
600
635
|
const catalogId = bundledCatalogId(provider);
|
|
@@ -691,52 +726,61 @@ function bindStatusRefresh(options) {
|
|
|
691
726
|
};
|
|
692
727
|
}
|
|
693
728
|
function isProviderOn(status, provider) {
|
|
694
|
-
if (provider.kind === "fetch") return status.fetchActive && status
|
|
729
|
+
if (provider.kind === "fetch") return status.fetchActive && selectedBackend(status, "fetch")?.id === provider.id;
|
|
695
730
|
return status.searchActive && selectedSearchBackend(status)?.id === provider.id;
|
|
696
731
|
}
|
|
697
732
|
function dormantSearchPlaceholder(id) {
|
|
733
|
+
return dormantPlaceholder(id, "search");
|
|
734
|
+
}
|
|
735
|
+
function dormantPlaceholder(id, kind) {
|
|
698
736
|
return {
|
|
699
737
|
id,
|
|
700
738
|
label: id,
|
|
701
739
|
description: "",
|
|
702
740
|
billing: "unknown",
|
|
703
|
-
kind
|
|
741
|
+
kind,
|
|
704
742
|
configured: false,
|
|
705
743
|
calls: 0,
|
|
706
744
|
failures: 0,
|
|
707
745
|
configurationOwned: false
|
|
708
746
|
};
|
|
709
747
|
}
|
|
748
|
+
function isRegistered(status, id, kind) {
|
|
749
|
+
return status.providers.some((provider) => provider.kind === kind && provider.id === id);
|
|
750
|
+
}
|
|
710
751
|
function isRegisteredSearch(status, id) {
|
|
711
|
-
return status
|
|
752
|
+
return isRegistered(status, id, "search");
|
|
712
753
|
}
|
|
713
|
-
function
|
|
714
|
-
|
|
754
|
+
function providerOrder(settings, kind) {
|
|
755
|
+
return kind === "search" ? settings.searchOrder : settings.fetchOrder;
|
|
756
|
+
}
|
|
757
|
+
function catalogBackends(status, kind) {
|
|
758
|
+
const byId = new Map(status.providers.filter((provider) => provider.kind === kind).map((provider) => [provider.id, provider]));
|
|
715
759
|
const rows = [];
|
|
716
760
|
const seen = /* @__PURE__ */ new Set();
|
|
717
|
-
for (const id of status.settings
|
|
761
|
+
for (const id of providerOrder(status.settings, kind)) {
|
|
718
762
|
if (!id || seen.has(id)) continue;
|
|
719
763
|
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);
|
|
764
|
+
rows.push(byId.get(id) ?? dormantPlaceholder(id, kind));
|
|
725
765
|
}
|
|
766
|
+
for (const provider of byId.values()) if (!seen.has(provider.id)) rows.push(provider);
|
|
726
767
|
return rows;
|
|
727
768
|
}
|
|
769
|
+
function catalogSearchBackends(status) {
|
|
770
|
+
return catalogBackends(status, "search");
|
|
771
|
+
}
|
|
728
772
|
function searchBackends(status) {
|
|
729
|
-
|
|
730
|
-
|
|
773
|
+
return catalogSearchBackends(status).filter((provider) => status.settings.searchOrder.includes(provider.id));
|
|
774
|
+
}
|
|
775
|
+
function selectedBackend(status, kind) {
|
|
776
|
+
return providerOrder(status.settings, kind).flatMap((id) => status.providers.find((provider) => provider.kind === kind && provider.id === id) ?? []).find((provider) => provider.configured);
|
|
731
777
|
}
|
|
732
778
|
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);
|
|
779
|
+
return selectedBackend(status, "search");
|
|
736
780
|
}
|
|
737
|
-
function droppedDormantIds(settings, order, providers) {
|
|
738
|
-
const known = new Set(providers.filter((provider) => provider.kind ===
|
|
739
|
-
return [...new Set(settings.
|
|
781
|
+
function droppedDormantIds(settings, order, providers, kind = "search") {
|
|
782
|
+
const known = new Set(providers.filter((provider) => provider.kind === kind).map((provider) => provider.id));
|
|
783
|
+
return [...new Set(providerOrder(settings, kind).filter((id) => id && !known.has(id) && !order.includes(id)))];
|
|
740
784
|
}
|
|
741
785
|
function configurationAccess(provider) {
|
|
742
786
|
if (ownsProviderConfiguration(provider)) return { kind: "owned" };
|
|
@@ -817,8 +861,9 @@ function ConfirmButton(props) {
|
|
|
817
861
|
const copy = {
|
|
818
862
|
zh: {
|
|
819
863
|
tabsLabel: "网络搜索设置",
|
|
820
|
-
|
|
821
|
-
|
|
864
|
+
tabSearch: "web-search",
|
|
865
|
+
tabFetch: "web-fetch",
|
|
866
|
+
tabLimits: "设置",
|
|
822
867
|
loading: "正在读取设置…",
|
|
823
868
|
loadFailed: "读取设置失败:",
|
|
824
869
|
reconnect: "重新连接",
|
|
@@ -859,13 +904,16 @@ const copy = {
|
|
|
859
904
|
maxFetchChars: "正文字符上限",
|
|
860
905
|
fetchTitle: "网页读取",
|
|
861
906
|
fetchProvider: "读取服务",
|
|
907
|
+
fetchBackends: "读取后端",
|
|
908
|
+
needFetchBackend: "需选择并配置读取服务。",
|
|
862
909
|
ownerSettings: "配置入口:",
|
|
863
910
|
unknownOwner: "请在服务所属插件中配置"
|
|
864
911
|
},
|
|
865
912
|
en: {
|
|
866
913
|
tabsLabel: "Web search settings",
|
|
867
|
-
|
|
868
|
-
|
|
914
|
+
tabSearch: "web-search",
|
|
915
|
+
tabFetch: "web-fetch",
|
|
916
|
+
tabLimits: "Settings",
|
|
869
917
|
loading: "Loading settings…",
|
|
870
918
|
loadFailed: "Could not load settings: ",
|
|
871
919
|
reconnect: "Reconnect",
|
|
@@ -906,6 +954,8 @@ const copy = {
|
|
|
906
954
|
maxFetchChars: "Max text chars",
|
|
907
955
|
fetchTitle: "Read web pages",
|
|
908
956
|
fetchProvider: "Fetch service",
|
|
957
|
+
fetchBackends: "Fetch backends",
|
|
958
|
+
needFetchBackend: "Choose and configure a fetch service.",
|
|
909
959
|
ownerSettings: "Configure in: ",
|
|
910
960
|
unknownOwner: "Configure this service in its plugin"
|
|
911
961
|
}
|
|
@@ -922,7 +972,7 @@ function uiMessage(message, locale, providers = []) {
|
|
|
922
972
|
if ("key" in message) return copy[locale][message.key];
|
|
923
973
|
if ("sources" in message) return testOkText(locale, message.sources);
|
|
924
974
|
if ("moved" in message) {
|
|
925
|
-
const provider = providers.find((item) => item.id === message.moved);
|
|
975
|
+
const provider = providers.find((item) => item.id === message.moved && item.kind === message.kind);
|
|
926
976
|
return movedText(locale, provider ? providerDisplayLabel(provider, locale) : message.moved, message.position);
|
|
927
977
|
}
|
|
928
978
|
return message.invalid.map((key) => {
|
|
@@ -975,15 +1025,15 @@ function parseLimit(raw, min, max) {
|
|
|
975
1025
|
* Settings written by an immediate action (a switch or a reorder). It keeps the
|
|
976
1026
|
* saved limits and never includes typed-but-unsaved keys or limit drafts.
|
|
977
1027
|
*/
|
|
978
|
-
function immediateUpdate(settings, on, order, providers, remove = []) {
|
|
1028
|
+
function immediateUpdate(settings, on, order, providers, remove = [], kind = "search") {
|
|
979
1029
|
let searchOrder = order;
|
|
980
1030
|
if (providers) {
|
|
981
|
-
const known = new Set(providers.filter((provider) => provider.kind ===
|
|
1031
|
+
const known = new Set(providers.filter((provider) => provider.kind === kind).map((provider) => provider.id));
|
|
982
1032
|
const removed = new Set(remove);
|
|
983
1033
|
const following = order.filter((id) => !removed.has(id));
|
|
984
1034
|
if (following.some((id) => !known.has(id))) searchOrder = following;
|
|
985
1035
|
else {
|
|
986
|
-
searchOrder = settings.
|
|
1036
|
+
searchOrder = providerOrder(settings, kind).flatMap((id) => {
|
|
987
1037
|
if (removed.has(id)) return [];
|
|
988
1038
|
return known.has(id) ? following.splice(0, 1) : [id];
|
|
989
1039
|
});
|
|
@@ -992,8 +1042,13 @@ function immediateUpdate(settings, on, order, providers, remove = []) {
|
|
|
992
1042
|
}
|
|
993
1043
|
return {
|
|
994
1044
|
...editable(settings),
|
|
995
|
-
|
|
996
|
-
|
|
1045
|
+
...kind === "search" ? {
|
|
1046
|
+
searchOrder,
|
|
1047
|
+
searchEnabled: on
|
|
1048
|
+
} : {
|
|
1049
|
+
fetchOrder: searchOrder,
|
|
1050
|
+
fetchEnabled: on
|
|
1051
|
+
}
|
|
997
1052
|
};
|
|
998
1053
|
}
|
|
999
1054
|
function limitTexts(settings) {
|
|
@@ -1015,7 +1070,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1015
1070
|
const locale = useHostLocale(client);
|
|
1016
1071
|
const text = copy[locale];
|
|
1017
1072
|
const tabsId = (0, react.useId)();
|
|
1018
|
-
const [tab, setTab] = (0, react.useState)("
|
|
1073
|
+
const [tab, setTab] = (0, react.useState)("search");
|
|
1019
1074
|
const [status, setStatus] = (0, react.useState)();
|
|
1020
1075
|
const [limitDraft, setLimitDraft] = (0, react.useState)();
|
|
1021
1076
|
const [invalid, setInvalid] = (0, react.useState)({});
|
|
@@ -1032,6 +1087,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1032
1087
|
const statusRef = (0, react.useRef)(status);
|
|
1033
1088
|
statusRef.current = status;
|
|
1034
1089
|
const pendingRef = (0, react.useRef)(null);
|
|
1090
|
+
const dragRef = (0, react.useRef)(null);
|
|
1035
1091
|
const timerRef = (0, react.useRef)(void 0);
|
|
1036
1092
|
const handleRefs = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
1037
1093
|
const refocusRef = (0, react.useRef)(null);
|
|
@@ -1067,7 +1123,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1067
1123
|
mounted: mountedRef.current,
|
|
1068
1124
|
hidden: isPageHidden(hiddenRef.current),
|
|
1069
1125
|
busy: busyRef.current,
|
|
1070
|
-
reordering: pendingRef.current !== null,
|
|
1126
|
+
reordering: pendingRef.current !== null || dragRef.current !== null,
|
|
1071
1127
|
reading: readingRef.current
|
|
1072
1128
|
});
|
|
1073
1129
|
}
|
|
@@ -1104,7 +1160,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1104
1160
|
requestId: mine,
|
|
1105
1161
|
latestRequestId: requestSeq.current,
|
|
1106
1162
|
busy: busyRef.current,
|
|
1107
|
-
reordering: pendingRef.current !== null,
|
|
1163
|
+
reordering: pendingRef.current !== null || dragRef.current !== null,
|
|
1108
1164
|
hidden: isPageHidden(hiddenRef.current),
|
|
1109
1165
|
currentRevision: statusRef.current?.settings.revision,
|
|
1110
1166
|
incomingRevision: next.settings.revision
|
|
@@ -1154,7 +1210,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1154
1210
|
const current = statusRef.current;
|
|
1155
1211
|
const next = pendingRef.current;
|
|
1156
1212
|
if (current && next) call("update", {
|
|
1157
|
-
settings: immediateUpdate(current.settings, current.settings.searchEnabled, next, current.providers),
|
|
1213
|
+
settings: immediateUpdate(current.settings, next.kind === "search" ? current.settings.searchEnabled : current.settings.fetchEnabled, next.order, current.providers, [], next.kind),
|
|
1158
1214
|
expectedRevision: current.settings.revision
|
|
1159
1215
|
}).catch(() => {});
|
|
1160
1216
|
};
|
|
@@ -1172,6 +1228,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1172
1228
|
if (handle && document.activeElement !== handle) handle.focus();
|
|
1173
1229
|
});
|
|
1174
1230
|
async function action(run) {
|
|
1231
|
+
if (busyRef.current) return;
|
|
1175
1232
|
readGate.reset();
|
|
1176
1233
|
requestSeq.current += 1;
|
|
1177
1234
|
busyRef.current = true;
|
|
@@ -1189,18 +1246,17 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1189
1246
|
}
|
|
1190
1247
|
}
|
|
1191
1248
|
/** Switches and reorders: settings only, applied at once. */
|
|
1192
|
-
function runImmediate(on, order, remove = []) {
|
|
1249
|
+
function runImmediate(on, order, remove = [], kind = "search") {
|
|
1193
1250
|
clearTimeout(timerRef.current);
|
|
1194
1251
|
timerRef.current = void 0;
|
|
1195
1252
|
pendingRef.current = null;
|
|
1196
1253
|
action(async () => {
|
|
1197
1254
|
const current = statusRef.current;
|
|
1198
1255
|
if (!current) return;
|
|
1199
|
-
|
|
1200
|
-
settings: immediateUpdate(current.settings, on, order, current.providers, remove),
|
|
1256
|
+
adopt(await call("update", {
|
|
1257
|
+
settings: immediateUpdate(current.settings, on, order, current.providers, remove, kind),
|
|
1201
1258
|
expectedRevision: current.settings.revision
|
|
1202
|
-
});
|
|
1203
|
-
setStatus(next);
|
|
1259
|
+
}));
|
|
1204
1260
|
await load();
|
|
1205
1261
|
}).then(() => {
|
|
1206
1262
|
if (pendingRef.current === null) setPendingOrder(null);
|
|
@@ -1210,24 +1266,27 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1210
1266
|
async function saveKeys(only) {
|
|
1211
1267
|
if (!status) return;
|
|
1212
1268
|
for (const provider of status.providers) {
|
|
1213
|
-
if (
|
|
1214
|
-
const snapshot = keys[provider.id] ?? "";
|
|
1269
|
+
if (only && providerKey(provider.kind, provider.id) !== providerKey(only.kind, only.id)) continue;
|
|
1270
|
+
const snapshot = keys[providerKey(provider.kind, provider.id)] ?? "";
|
|
1215
1271
|
const typed = snapshot.trim();
|
|
1216
1272
|
if (!ownsProviderConfiguration(provider) || !provider.credentialRef || !typed) continue;
|
|
1217
1273
|
if (writable[provider.credentialRef] === false) throw new UiError("readOnly");
|
|
1218
1274
|
unwrap(await client.remote.credentials.set(provider.credentialRef, typed));
|
|
1219
|
-
setKeys((current) => current[provider.id] === snapshot ? {
|
|
1275
|
+
setKeys((current) => current[providerKey(provider.kind, provider.id)] === snapshot ? {
|
|
1220
1276
|
...current,
|
|
1221
|
-
[provider.id]: ""
|
|
1277
|
+
[providerKey(provider.kind, provider.id)]: ""
|
|
1222
1278
|
} : current);
|
|
1223
1279
|
}
|
|
1224
1280
|
}
|
|
1281
|
+
async function saveKeysForKind(kind) {
|
|
1282
|
+
for (const provider of statusRef.current?.providers ?? []) if (provider.kind === kind) await saveKeys(provider);
|
|
1283
|
+
}
|
|
1225
1284
|
async function removeKey(provider) {
|
|
1226
1285
|
if (!ownsProviderConfiguration(provider) || !provider.credentialRef || provider.credentialShared || !status) return;
|
|
1227
1286
|
unwrap(await client.remote.credentials.unset(provider.credentialRef));
|
|
1228
1287
|
setKeys((current) => ({
|
|
1229
1288
|
...current,
|
|
1230
|
-
[provider.id]: ""
|
|
1289
|
+
[providerKey(provider.kind, provider.id)]: ""
|
|
1231
1290
|
}));
|
|
1232
1291
|
await load();
|
|
1233
1292
|
setNote({ key: "keyDeleted" });
|
|
@@ -1256,28 +1315,30 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1256
1315
|
}) : null]
|
|
1257
1316
|
});
|
|
1258
1317
|
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
1318
|
const savedLimits = limitTexts(saved.settings);
|
|
1267
1319
|
const limitsDirty = LIMITS.some(([key]) => limitDraft[key] !== savedLimits[key]);
|
|
1268
1320
|
const keysDirty = Object.values(keys).some((value) => value.trim());
|
|
1269
|
-
const reordering = pendingOrder !== null;
|
|
1270
|
-
function move(id, nextIndex, keyboard = false) {
|
|
1321
|
+
const reordering = pendingOrder !== null || dragSource !== null;
|
|
1322
|
+
function move(kind, id, nextIndex, keyboard = false) {
|
|
1323
|
+
if (pendingRef.current && pendingRef.current.kind !== kind) return;
|
|
1324
|
+
const order = pendingRef.current?.order ?? providerOrder(statusRef.current.settings, kind);
|
|
1271
1325
|
const index = order.indexOf(id);
|
|
1272
1326
|
if (busy || index < 0 || nextIndex < 0 || nextIndex >= order.length || index === nextIndex) return;
|
|
1273
1327
|
const next = [...order];
|
|
1274
1328
|
const [item] = next.splice(index, 1);
|
|
1275
1329
|
next.splice(nextIndex, 0, item);
|
|
1276
|
-
pendingRef.current =
|
|
1277
|
-
|
|
1278
|
-
|
|
1330
|
+
pendingRef.current = {
|
|
1331
|
+
kind,
|
|
1332
|
+
order: next
|
|
1333
|
+
};
|
|
1334
|
+
setPendingOrder({
|
|
1335
|
+
kind,
|
|
1336
|
+
order: next
|
|
1337
|
+
});
|
|
1338
|
+
if (keyboard) refocusRef.current = providerKey(kind, id);
|
|
1279
1339
|
setSortAnnouncement({
|
|
1280
1340
|
moved: id,
|
|
1341
|
+
kind,
|
|
1281
1342
|
position: nextIndex + 1
|
|
1282
1343
|
});
|
|
1283
1344
|
clearTimeout(timerRef.current);
|
|
@@ -1285,10 +1346,11 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1285
1346
|
timerRef.current = void 0;
|
|
1286
1347
|
const batched = pendingRef.current;
|
|
1287
1348
|
const current = statusRef.current;
|
|
1288
|
-
if (batched && current) runImmediate(current.settings.searchEnabled, batched);
|
|
1349
|
+
if (batched && current) runImmediate(batched.kind === "search" ? current.settings.searchEnabled : current.settings.fetchEnabled, batched.order, [], batched.kind);
|
|
1289
1350
|
}, keyboard ? REORDER_DEBOUNCE_MS : 0);
|
|
1290
1351
|
}
|
|
1291
1352
|
function endDrag() {
|
|
1353
|
+
dragRef.current = null;
|
|
1292
1354
|
setDragSource(null);
|
|
1293
1355
|
setDropTarget(null);
|
|
1294
1356
|
}
|
|
@@ -1331,17 +1393,26 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1331
1393
|
value: tab,
|
|
1332
1394
|
onChange: setTab,
|
|
1333
1395
|
label: text.tabsLabel,
|
|
1334
|
-
items: [
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1396
|
+
items: [
|
|
1397
|
+
{
|
|
1398
|
+
value: "search",
|
|
1399
|
+
label: text.tabSearch,
|
|
1400
|
+
id: tabsId + "-search-tab",
|
|
1401
|
+
panelId: tabsId + "-search-panel"
|
|
1402
|
+
},
|
|
1403
|
+
{
|
|
1404
|
+
value: "fetch",
|
|
1405
|
+
label: text.tabFetch,
|
|
1406
|
+
id: tabsId + "-fetch-tab",
|
|
1407
|
+
panelId: tabsId + "-fetch-panel"
|
|
1408
|
+
},
|
|
1409
|
+
{
|
|
1410
|
+
value: "limits",
|
|
1411
|
+
label: text.tabLimits,
|
|
1412
|
+
id: tabsId + "-limits-tab",
|
|
1413
|
+
panelId: tabsId + "-limits-panel"
|
|
1414
|
+
}
|
|
1415
|
+
]
|
|
1345
1416
|
}),
|
|
1346
1417
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1347
1418
|
className: "ws-sr-only",
|
|
@@ -1380,374 +1451,294 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1380
1451
|
className: "dsh-ui-banner dsh-ui-banner--danger",
|
|
1381
1452
|
children: text.storageFailed
|
|
1382
1453
|
}),
|
|
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,
|
|
1454
|
+
["search", "fetch"].map((kind) => {
|
|
1455
|
+
const order = pendingOrder?.kind === kind ? pendingOrder.order : providerOrder(saved.settings, kind);
|
|
1456
|
+
const catalog = catalogBackends(saved, kind);
|
|
1457
|
+
const backends = [...order.flatMap((id) => catalog.find((provider) => provider.id === id) ?? []), ...catalog.filter((provider) => !order.includes(provider.id))];
|
|
1458
|
+
const selected = selectedBackend(saved, kind);
|
|
1459
|
+
const on = kind === "search" ? saved.settings.searchEnabled : saved.settings.fetchEnabled;
|
|
1460
|
+
const canEnable = order.some((id) => saved.providers.some((provider) => provider.kind === kind && provider.id === id && provider.configured));
|
|
1461
|
+
const sortingOther = pendingOrder !== null && pendingOrder.kind !== kind;
|
|
1462
|
+
const locked = busy || sortingOther || Boolean(dragSource && !dragSource.startsWith(kind + ":"));
|
|
1463
|
+
const title = kind === "search" ? text.toolTitle : text.fetchTitle;
|
|
1464
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1465
|
+
role: "tabpanel",
|
|
1466
|
+
id: tabsId + "-" + kind + "-panel",
|
|
1467
|
+
"aria-labelledby": tabsId + "-" + kind + "-tab",
|
|
1468
|
+
hidden: tab !== kind,
|
|
1469
|
+
tabIndex: 0,
|
|
1470
|
+
children: backends.length === 0 ? kind === "search" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1471
|
+
className: "dsh-ui-empty",
|
|
1472
|
+
children: text.empty
|
|
1473
|
+
}) : null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
1474
|
+
className: "web-search-card dsh-ui-card dsh-ui-card--flat",
|
|
1475
|
+
"data-testid": kind === "search" ? "web-search-tool" : "web-search-fetch",
|
|
1476
|
+
"data-on": on ? "true" : "false",
|
|
1664
1477
|
children: [
|
|
1665
1478
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
1666
1479
|
className: "ws-card-head",
|
|
1667
1480
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1668
1481
|
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
|
-
]
|
|
1482
|
+
children: title
|
|
1483
|
+
}), !canEnable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1484
|
+
className: "dsh-ui-hint",
|
|
1485
|
+
children: kind === "search" ? text.needBackend : text.needFetchBackend
|
|
1680
1486
|
})] }), /* @__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
|
-
})
|
|
1487
|
+
checked: on,
|
|
1488
|
+
label: title,
|
|
1489
|
+
disabled: locked || dragSource !== null || !on && !canEnable,
|
|
1490
|
+
onChange: (next) => runImmediate(next, order, [], kind)
|
|
1695
1491
|
})]
|
|
1696
1492
|
}),
|
|
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
|
-
|
|
1493
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
|
|
1494
|
+
className: "web-search-rank",
|
|
1495
|
+
"aria-label": kind === "search" ? text.backends : text.fetchBackends,
|
|
1496
|
+
children: backends.map((provider) => {
|
|
1497
|
+
const participating = order.includes(provider.id);
|
|
1498
|
+
const rank = order.indexOf(provider.id);
|
|
1499
|
+
const dormant = !isRegistered(saved, provider.id, kind);
|
|
1500
|
+
const active = !dormant && (kind === "search" ? saved.searchActive : saved.fetchActive) && selected?.id === provider.id;
|
|
1501
|
+
const info = kind === "search" || ownsProviderConfiguration(provider) && provider.id === "firecrawl" ? searchProviderInfo(provider, locale) : {
|
|
1502
|
+
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,
|
|
1503
|
+
pricing: provider.pricing ?? "",
|
|
1504
|
+
pricingUrl: provider.pricingUrl
|
|
1505
|
+
};
|
|
1506
|
+
const owned = ownsProviderConfiguration(provider);
|
|
1507
|
+
const access = configurationAccess(provider);
|
|
1508
|
+
const needsKey = owned && Boolean(provider.credentialRef);
|
|
1509
|
+
const shared = Boolean(provider.credentialShared);
|
|
1510
|
+
const showKey = !dormant && needsKey && participating && (!shared || !provider.configured);
|
|
1511
|
+
const canSort = participating && order.length > 1;
|
|
1512
|
+
const readOnly = writable[provider.credentialRef ?? ""] === false;
|
|
1513
|
+
const typedKey = (keys[providerKey(provider.kind, provider.id)] ?? "").trim();
|
|
1514
|
+
const availability = dormant ? "dormant" : provider.configured ? "available" : "unavailable";
|
|
1515
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
1516
|
+
className: active ? "is-active" : void 0,
|
|
1517
|
+
"data-testid": `web-${kind}-rank-${provider.id}`,
|
|
1518
|
+
"data-availability": availability,
|
|
1519
|
+
"data-in-use": active || void 0,
|
|
1520
|
+
"data-dragging": dragSource === providerKey(kind, provider.id) || void 0,
|
|
1521
|
+
"data-drop": dropTarget === providerKey(kind, provider.id) && dragSource ? order.indexOf(dragSource.slice(kind.length + 1)) < rank ? "after" : "before" : void 0,
|
|
1522
|
+
onDragOver: (event) => {
|
|
1523
|
+
if (locked || !canSort || !dragSource?.startsWith(kind + ":") || dragSource === providerKey(kind, provider.id)) return;
|
|
1524
|
+
event.preventDefault();
|
|
1525
|
+
event.dataTransfer.dropEffect = "move";
|
|
1526
|
+
setDropTarget(providerKey(kind, provider.id));
|
|
1527
|
+
},
|
|
1528
|
+
onDragLeave: (event) => {
|
|
1529
|
+
if (!event.currentTarget.contains(event.relatedTarget)) setDropTarget(null);
|
|
1530
|
+
},
|
|
1531
|
+
onDrop: (event) => {
|
|
1532
|
+
if (locked || !canSort || !dragSource?.startsWith(kind + ":")) return;
|
|
1533
|
+
event.preventDefault();
|
|
1534
|
+
move(kind, dragSource.slice(kind.length + 1), rank);
|
|
1535
|
+
endDrag();
|
|
1536
|
+
},
|
|
1723
1537
|
children: [
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1538
|
+
canSort ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1539
|
+
variant: "ghost",
|
|
1540
|
+
size: "sm",
|
|
1541
|
+
className: "web-search-drag-handle",
|
|
1542
|
+
ref: (element) => {
|
|
1543
|
+
if (element) handleRefs.current.set(providerKey(kind, provider.id), element);
|
|
1544
|
+
else handleRefs.current.delete(providerKey(kind, provider.id));
|
|
1545
|
+
},
|
|
1546
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
1547
|
+
width: "16",
|
|
1548
|
+
height: "20",
|
|
1549
|
+
viewBox: "0 0 16 20",
|
|
1550
|
+
fill: "currentColor",
|
|
1551
|
+
"aria-hidden": "true",
|
|
1552
|
+
children: [
|
|
1553
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1554
|
+
cx: "5",
|
|
1555
|
+
cy: "5",
|
|
1556
|
+
r: "1.5"
|
|
1557
|
+
}),
|
|
1558
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1559
|
+
cx: "11",
|
|
1560
|
+
cy: "5",
|
|
1561
|
+
r: "1.5"
|
|
1562
|
+
}),
|
|
1563
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1564
|
+
cx: "5",
|
|
1565
|
+
cy: "10",
|
|
1566
|
+
r: "1.5"
|
|
1567
|
+
}),
|
|
1568
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1569
|
+
cx: "11",
|
|
1570
|
+
cy: "10",
|
|
1571
|
+
r: "1.5"
|
|
1572
|
+
}),
|
|
1573
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1574
|
+
cx: "5",
|
|
1575
|
+
cy: "15",
|
|
1576
|
+
r: "1.5"
|
|
1577
|
+
}),
|
|
1578
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1579
|
+
cx: "11",
|
|
1580
|
+
cy: "15",
|
|
1581
|
+
r: "1.5"
|
|
1582
|
+
})
|
|
1583
|
+
]
|
|
1584
|
+
}),
|
|
1585
|
+
draggable: !locked,
|
|
1586
|
+
"aria-disabled": locked,
|
|
1587
|
+
"aria-label": text.dragPrefix + providerDisplayLabel(provider, locale),
|
|
1588
|
+
title: text.dragHint,
|
|
1589
|
+
onDragStart: (event) => {
|
|
1590
|
+
if (locked || pendingRef.current && pendingRef.current.kind !== kind) {
|
|
1591
|
+
event.preventDefault();
|
|
1592
|
+
return;
|
|
1593
|
+
}
|
|
1594
|
+
event.dataTransfer.effectAllowed = "move";
|
|
1595
|
+
event.dataTransfer.setData(`application/x-dsh-${kind}-provider`, provider.id);
|
|
1596
|
+
const row = event.currentTarget.closest("li");
|
|
1597
|
+
if (row) event.dataTransfer.setDragImage(row, 16, 16);
|
|
1598
|
+
dragRef.current = providerKey(kind, provider.id);
|
|
1599
|
+
requestSeq.current += 1;
|
|
1600
|
+
setDragSource(providerKey(kind, provider.id));
|
|
1601
|
+
setSortAnnouncement("");
|
|
1602
|
+
},
|
|
1603
|
+
onDragEnd: endDrag,
|
|
1604
|
+
onKeyDown: (event) => {
|
|
1605
|
+
if (event.key !== "ArrowUp" && event.key !== "ArrowDown") return;
|
|
1606
|
+
event.preventDefault();
|
|
1607
|
+
move(kind, provider.id, rank + (event.key === "ArrowUp" ? -1 : 1), true);
|
|
1608
|
+
}
|
|
1609
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1610
|
+
className: "web-search-rank-index",
|
|
1611
|
+
children: participating ? rank + 1 : "—"
|
|
1612
|
+
}),
|
|
1613
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1614
|
+
className: "web-search-provider",
|
|
1615
|
+
children: [
|
|
1616
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1617
|
+
className: "web-search-provider-heading",
|
|
1618
|
+
children: [
|
|
1619
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
|
|
1620
|
+
className: "dsh-ui-heading",
|
|
1621
|
+
children: providerDisplayLabel(provider, locale)
|
|
1622
|
+
}),
|
|
1623
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1624
|
+
className: "ws-provider-note",
|
|
1625
|
+
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 })]
|
|
1626
|
+
}),
|
|
1627
|
+
active && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
|
|
1628
|
+
tone: "success",
|
|
1629
|
+
children: text.inUse
|
|
1630
|
+
}),
|
|
1631
|
+
provider.signupUrl && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppLink, {
|
|
1632
|
+
url: provider.signupUrl,
|
|
1633
|
+
label: providerDisplayLabel(provider, locale) + " " + text.signup,
|
|
1634
|
+
children: text.signup
|
|
1635
|
+
})
|
|
1636
|
+
]
|
|
1637
|
+
}),
|
|
1638
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1639
|
+
className: "web-search-provider-info",
|
|
1640
|
+
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, {
|
|
1641
|
+
url: info.pricingUrl,
|
|
1642
|
+
label: providerDisplayLabel(provider, locale) + " " + text.pricing,
|
|
1643
|
+
children: provider.id === "ddg" ? text.website : text.pricing
|
|
1644
|
+
})] })] }) : null]
|
|
1645
|
+
}),
|
|
1646
|
+
access.kind === "link" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1647
|
+
className: "dsh-ui-hint",
|
|
1648
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppLink, {
|
|
1649
|
+
url: access.url,
|
|
1650
|
+
label: text.openConfig,
|
|
1651
|
+
children: text.openConfig
|
|
1652
|
+
})
|
|
1653
|
+
}) : access.kind === "owner" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1654
|
+
className: "dsh-ui-hint",
|
|
1655
|
+
children: [text.ownerSettings, access.name]
|
|
1656
|
+
}) : access.kind === "unknown" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1657
|
+
className: "dsh-ui-hint",
|
|
1658
|
+
children: text.unknownOwner
|
|
1659
|
+
}) : null,
|
|
1660
|
+
shared && participating && owned ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", {
|
|
1661
|
+
className: "dsh-ui-meta",
|
|
1662
|
+
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
|
|
1663
|
+
}) : null,
|
|
1664
|
+
showKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1665
|
+
className: "dsh-ui-field",
|
|
1666
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1667
|
+
className: "dsh-ui-label",
|
|
1668
|
+
children: "API Key"
|
|
1669
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
1670
|
+
className: "ws-input",
|
|
1671
|
+
type: "password",
|
|
1672
|
+
autoComplete: "off",
|
|
1673
|
+
spellCheck: false,
|
|
1674
|
+
disabled: busy || reordering || readOnly,
|
|
1675
|
+
value: keys[providerKey(provider.kind, provider.id)] ?? "",
|
|
1676
|
+
placeholder: provider.configured ? text.keyConfigured : text.keyPlaceholder,
|
|
1677
|
+
onChange: (event) => setKeys((current) => ({
|
|
1678
|
+
...current,
|
|
1679
|
+
[providerKey(provider.kind, provider.id)]: event.target.value
|
|
1680
|
+
}))
|
|
1681
|
+
})]
|
|
1682
|
+
}) : null,
|
|
1683
|
+
showKey || needsKey && !shared && provider.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1684
|
+
className: "dsh-ui-actions",
|
|
1685
|
+
children: [showKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1686
|
+
variant: "outline",
|
|
1687
|
+
size: "sm",
|
|
1688
|
+
disabled: busy || reordering || readOnly || !typedKey,
|
|
1689
|
+
onClick: () => void action(async () => {
|
|
1690
|
+
await saveKeys(provider);
|
|
1691
|
+
await load();
|
|
1692
|
+
setNote({ key: "keySaved" });
|
|
1693
|
+
}),
|
|
1694
|
+
children: text.saveKey
|
|
1695
|
+
}) : null, needsKey && !shared && provider.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmButton, {
|
|
1696
|
+
disabled: busy || reordering || readOnly,
|
|
1697
|
+
label: text.deleteKey,
|
|
1698
|
+
confirmLabel: text.confirmDelete,
|
|
1699
|
+
onConfirm: () => void action(() => removeKey(provider))
|
|
1700
|
+
}) : null]
|
|
1701
|
+
}) : null
|
|
1702
|
+
]
|
|
1703
|
+
}),
|
|
1704
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1705
|
+
className: "web-search-rank-move",
|
|
1706
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
1707
|
+
checked: participating,
|
|
1708
|
+
label: providerDisplayLabel(provider, locale),
|
|
1709
|
+
disabled: locked || dragSource !== null,
|
|
1710
|
+
onChange: (next) => {
|
|
1711
|
+
const following = next ? [...order, provider.id] : order.filter((id) => id !== provider.id);
|
|
1712
|
+
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);
|
|
1713
|
+
}
|
|
1714
|
+
})
|
|
1715
|
+
})
|
|
1727
1716
|
]
|
|
1728
|
-
}
|
|
1729
|
-
|
|
1730
|
-
children: [providerDisplayLabel(provider, locale), provider.configured ? "" : " · " + text.unavailable]
|
|
1731
|
-
}, provider.id))]
|
|
1732
|
-
})]
|
|
1717
|
+
}, provider.id);
|
|
1718
|
+
})
|
|
1733
1719
|
}),
|
|
1734
|
-
|
|
1720
|
+
kind === "search" && backends.some(usesBundledPricing) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1735
1721
|
className: "dsh-ui-hint",
|
|
1736
|
-
children:
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1722
|
+
children: pricingCheckedText(locale, PROVIDER_PRICING_CHECKED)
|
|
1723
|
+
}) : null,
|
|
1724
|
+
on && kind === "search" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1725
|
+
className: "dsh-ui-actions",
|
|
1726
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1727
|
+
variant: "outline",
|
|
1728
|
+
size: "md",
|
|
1729
|
+
disabled: busy || reordering,
|
|
1730
|
+
onClick: () => void action(async () => {
|
|
1731
|
+
await saveKeysForKind("search");
|
|
1732
|
+
const result = await call("test");
|
|
1733
|
+
await load();
|
|
1734
|
+
setNote({ sources: result.sources });
|
|
1735
|
+
}),
|
|
1736
|
+
children: text.test
|
|
1740
1737
|
})
|
|
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
1738
|
}) : null
|
|
1748
1739
|
]
|
|
1749
|
-
})
|
|
1750
|
-
})
|
|
1740
|
+
})
|
|
1741
|
+
}, kind);
|
|
1751
1742
|
}),
|
|
1752
1743
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1753
1744
|
role: "tabpanel",
|