@klarkxy/dsh-web-search-manager 0.1.11 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -10
- package/cordis.patch.yml +1 -8
- package/docs/README.zh-CN.md +31 -10
- package/lib/client.inner.cjs +649 -104
- package/lib/client.inner.cjs.map +1 -1
- package/lib/client.js +650 -266
- package/lib/contracts.d.ts +15 -20
- package/lib/contracts.js +13 -1
- package/lib/contracts.js.map +1 -1
- package/lib/index.d.ts +19 -3
- package/lib/index.js +506 -389
- package/lib/index.js.map +1 -1
- package/package.json +23 -22
package/lib/client.inner.cjs
CHANGED
|
@@ -2,6 +2,47 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
2
2
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
3
3
|
let react = require("react");
|
|
4
4
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
5
|
+
//#region src/client-error.ts
|
|
6
|
+
/** Local adapter diagnostics only; third-party descriptions remain verbatim. */
|
|
7
|
+
const localErrors = {
|
|
8
|
+
"网络搜索设置格式无效。": "Invalid web search settings format.",
|
|
9
|
+
"未知操作。": "Unknown operation.",
|
|
10
|
+
"网络设置操作失败,请刷新后重试。": "The network settings operation failed. Refresh and try again.",
|
|
11
|
+
"查询须为 1 至 4000 字符。": "The query must contain 1 to 4,000 characters.",
|
|
12
|
+
"网络搜索插件已停止。": "The web search plugin has stopped.",
|
|
13
|
+
"请在设置 → 网络搜索中配置凭据并启用。": "Configure credentials and enable web search in Settings → Web search.",
|
|
14
|
+
"网络访问设置已改变,请重新发起请求。": "Network access settings changed. Start the request again.",
|
|
15
|
+
"网络请求已取消。": "The network request was cancelled.",
|
|
16
|
+
"网络请求已取消或超时。": "The network request was cancelled or timed out.",
|
|
17
|
+
"设置已被其他页面修改,请刷新后重试。": "Settings changed in another page. Refresh and try again.",
|
|
18
|
+
"供应商不支持自定义地址。": "The provider does not support a custom address.",
|
|
19
|
+
"供应商地址须为无凭据、无查询参数的 HTTPS 地址。": "The provider address must use HTTPS without credentials or query parameters.",
|
|
20
|
+
"需选择已安装的供应商并填写有效凭据。": "Choose an installed provider and enter valid credentials.",
|
|
21
|
+
"保存失败,本次运行已暂停网络访问以防意外联网。": "Saving failed. Network access is paused for this run to prevent unintended requests.",
|
|
22
|
+
"供应商未提供错误描述。": "The provider did not supply an error description.",
|
|
23
|
+
"供应商请求失败。": "Provider request failed.",
|
|
24
|
+
"搜索凭据已删除或不可用,请重新配置。": "Search credentials were deleted or are unavailable. Configure them again.",
|
|
25
|
+
"供应商配置不可用。": "Provider configuration is unavailable.",
|
|
26
|
+
"查询无效。": "Invalid query.",
|
|
27
|
+
"结果数须为 1 至 20。": "The result count must be from 1 to 20.",
|
|
28
|
+
"搜索已取消。": "Search cancelled.",
|
|
29
|
+
"Tavily 搜索已取消。": "Tavily search cancelled.",
|
|
30
|
+
"响应格式无效。": "Invalid response shape",
|
|
31
|
+
"响应缺失。": "Missing response",
|
|
32
|
+
"响应过大。": "Response too large",
|
|
33
|
+
"地址无效。": "Invalid endpoint"
|
|
34
|
+
};
|
|
35
|
+
function localErrorText(message, locale) {
|
|
36
|
+
const prefix = message.match(/^(\[(?:search|fetch):[^\]]+\] )(.*)$/s);
|
|
37
|
+
if (prefix) return prefix[1] + localErrorText(prefix[2], locale);
|
|
38
|
+
if (locale === "zh") return Object.entries(localErrors).find(([, english]) => english === message)?.[0] ?? message;
|
|
39
|
+
if (localErrors[message]) return localErrors[message];
|
|
40
|
+
const http = message.match(/^供应商请求失败(HTTP (\d{3}))。$/);
|
|
41
|
+
if (http) return `Provider request failed (HTTP ${http[1]}).`;
|
|
42
|
+
const config = message.match(/^(brave|bocha|firecrawl|serper|Tavily) 配置不可用。$/);
|
|
43
|
+
if (config) return `${config[1]} configuration is unavailable.`;
|
|
44
|
+
return message;
|
|
45
|
+
}
|
|
5
46
|
Object.values({
|
|
6
47
|
/** Surfaces, from the page up to the topmost nested card. */
|
|
7
48
|
surface: [
|
|
@@ -299,7 +340,7 @@ ${scope} {
|
|
|
299
340
|
border: 0.5px solid var(--dsw-alias-border-l3);
|
|
300
341
|
border-radius: var(--dsw-radius-md);
|
|
301
342
|
background: var(--dsw-alias-bg-layer-1);
|
|
302
|
-
font-family: var(--ds-font-family-code
|
|
343
|
+
font-family: var(--ds-font-family-code);
|
|
303
344
|
font-size: 12px;
|
|
304
345
|
line-height: 20px;
|
|
305
346
|
color: var(--dsw-alias-label-secondary);
|
|
@@ -375,6 +416,49 @@ ${scope} {
|
|
|
375
416
|
`.replace(/^& (\.dsh-ui-[a-z0-9-]+)(?![+>~])/gm, (_match, className) => `&${className}, & ${className}`);
|
|
376
417
|
}
|
|
377
418
|
//#endregion
|
|
419
|
+
//#region ../dsh-plugin-kit/src/client-utils.ts
|
|
420
|
+
/** Fixed pause for failing background reads; manual work is never replayed. */
|
|
421
|
+
function createPluginReadGate(now = Date.now) {
|
|
422
|
+
let pausedUntil = 0;
|
|
423
|
+
let epoch = 0;
|
|
424
|
+
return {
|
|
425
|
+
canRead: () => now() >= pausedUntil,
|
|
426
|
+
reset: () => {
|
|
427
|
+
epoch++;
|
|
428
|
+
pausedUntil = 0;
|
|
429
|
+
},
|
|
430
|
+
async run(read) {
|
|
431
|
+
const current = epoch;
|
|
432
|
+
try {
|
|
433
|
+
const value = await read();
|
|
434
|
+
if (current === epoch) pausedUntil = 0;
|
|
435
|
+
return value;
|
|
436
|
+
} catch (error) {
|
|
437
|
+
const message = error instanceof Error ? error.message : "";
|
|
438
|
+
if (current === epoch && (/(?:^|\s)transport failure for .+: HTTP (?:404|405|503)\b/.test(message) || error instanceof TypeError && /^(?:Failed to fetch|fetch failed|NetworkError when attempting to fetch resource\.?)$/i.test(message))) pausedUntil = now() + 3e4;
|
|
439
|
+
throw error;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
//#endregion
|
|
445
|
+
//#region src/contracts.ts
|
|
446
|
+
const WEB_SEARCH_RPC_CHANNEL = "/web-search-manager";
|
|
447
|
+
function pickActiveSearch(order, configured) {
|
|
448
|
+
return order.find(configured) ?? "";
|
|
449
|
+
}
|
|
450
|
+
function safeConfigurationUrl(value) {
|
|
451
|
+
if (typeof value !== "string" || !value || /[\u0000-\u0020\\]/.test(value)) return;
|
|
452
|
+
try {
|
|
453
|
+
const url = new URL(value, "https://dsh.invalid");
|
|
454
|
+
if (url.username || url.password) return;
|
|
455
|
+
if (value.startsWith("/") && !value.startsWith("//") || value.startsWith("#")) return url.origin === "https://dsh.invalid" ? value : void 0;
|
|
456
|
+
return /^https:\/\//i.test(value) && url.protocol === "https:" ? value : void 0;
|
|
457
|
+
} catch {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
//#endregion
|
|
378
462
|
//#region src/provider-info.ts
|
|
379
463
|
const PROVIDER_PRICING_CHECKED = "2026-09-21";
|
|
380
464
|
const providerInfo = {
|
|
@@ -419,22 +503,94 @@ const providerInfo = {
|
|
|
419
503
|
pricingUrl: "https://docs.tavily.com/documentation/api-credits"
|
|
420
504
|
}
|
|
421
505
|
};
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
506
|
+
const englishInfo = {
|
|
507
|
+
ddg: {
|
|
508
|
+
description: "Search public DuckDuckGo pages without registration or an API key.",
|
|
509
|
+
pricing: "Free to use; no search API fees."
|
|
510
|
+
},
|
|
511
|
+
bocha: {
|
|
512
|
+
description: "Search Chinese web content, returning titles, links and summaries.",
|
|
513
|
+
pricing: "Charged per request: listed price ¥0.036/request; trial credits depend on official offers."
|
|
514
|
+
},
|
|
515
|
+
brave: {
|
|
516
|
+
description: "Search web pages, news and more with Brave’s independent index.",
|
|
517
|
+
pricing: "$5 in monthly credits (about 1,000 requests); then $5/1,000 requests. A payment card is required to activate."
|
|
518
|
+
},
|
|
519
|
+
"deepseek-official": {
|
|
520
|
+
description: "Call built-in search through a DeepSeek model using the model API key.",
|
|
521
|
+
pricing: "Default Flash token pricing per million: input ¥1–2 (cache hits ¥0.02–0.04), output ¥4–8, varying by peak/off-peak hours. Platform terms govern promotional balance and tool fees."
|
|
522
|
+
},
|
|
523
|
+
exa: {
|
|
524
|
+
description: "Web search for AI with relevant pages and content snippets.",
|
|
525
|
+
pricing: "$20 on signup, then $10 monthly credits; $7/1,000 requests (up to 10 results each), plus $1/1,000 additional results beyond 10."
|
|
526
|
+
},
|
|
527
|
+
firecrawl: {
|
|
528
|
+
description: "Search the web and extract page content.",
|
|
529
|
+
pricing: "1,000 free credits/month; 2 credits per 10 search results. Hobby includes 5,000 credits/month at $16/month billed annually; extra credits cost $5/1,000."
|
|
530
|
+
},
|
|
531
|
+
serper: {
|
|
532
|
+
description: "Get Google web search results.",
|
|
533
|
+
pricing: "2,500 one-time signup requests; $50/50,000 requests ($1/1,000). Paid credits expire after 6 months."
|
|
534
|
+
},
|
|
535
|
+
tavily: {
|
|
536
|
+
description: "Web search for AI, currently using basic search.",
|
|
537
|
+
pricing: "1,000 free credits/month; basic search uses 1 credit/request. Pay as you go: $0.008/credit ($8/1,000 requests)."
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
const bundledDescriptions = {
|
|
541
|
+
ddg: ["无需注册。默认后端。", "No registration required. Default backend."],
|
|
542
|
+
"deepseek-official": ["使用已配置的 DeepSeek API Key,可能产生额外搜索费用。", "Uses the configured DeepSeek API key; additional search fees may apply."],
|
|
543
|
+
exa: ["独立 Search API。", "Independent Search API."],
|
|
544
|
+
brave: ["Brave Search API。", "Brave Search API."],
|
|
545
|
+
bocha: ["中文网页搜索 API。", "Chinese web search API."],
|
|
546
|
+
serper: ["Google 结果的 Search API。", "Search API for Google results."],
|
|
547
|
+
firecrawl: ["网页搜索与提取 API。", "Web search and extraction API."],
|
|
548
|
+
tavily: ["Search API;固定 basic 检索,不自动升级搜索深度。", "Search API using basic search; search depth is never upgraded automatically."]
|
|
549
|
+
};
|
|
550
|
+
/** Translate only manager-owned labels; externally supplied metadata stays verbatim. */
|
|
551
|
+
function providerDisplayLabel(provider, locale) {
|
|
552
|
+
if (!bundledCatalogId(provider)) return provider.label;
|
|
553
|
+
if (provider.label === "DeepSeek 搜索") return locale === "zh" ? provider.label : "DeepSeek search";
|
|
554
|
+
if (provider.label === "博查") return locale === "zh" ? provider.label : "Bocha";
|
|
555
|
+
if (provider.label === "网页读取") return locale === "zh" ? provider.label : "Read web pages";
|
|
556
|
+
return provider.label;
|
|
429
557
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
function resolveSearchOrder(knownIds, settings, _keyed) {
|
|
434
|
-
return [...new Set(settings.searchOrder.filter((id) => knownIds.includes(id)))];
|
|
558
|
+
function bundledCatalogId(provider) {
|
|
559
|
+
if (!(typeof provider.configurationOwned === "boolean" ? provider.configurationOwned : !provider.configurationOwner || provider.configurationOwner === "@klarkxy/dsh-web-search-manager")) return;
|
|
560
|
+
return provider.id === "deepseek-managed" ? "deepseek-official" : provider.id;
|
|
435
561
|
}
|
|
436
|
-
function
|
|
437
|
-
return
|
|
562
|
+
function missingPricing(locale) {
|
|
563
|
+
return locale === "zh" ? "未提供费用信息" : "No pricing information provided";
|
|
564
|
+
}
|
|
565
|
+
function usesBundledPricing(provider) {
|
|
566
|
+
if (provider.pricing?.trim()) return false;
|
|
567
|
+
const catalogId = bundledCatalogId(provider);
|
|
568
|
+
return Boolean(catalogId && Object.hasOwn(providerInfo, catalogId));
|
|
569
|
+
}
|
|
570
|
+
function searchProviderInfo(provider, locale = "zh") {
|
|
571
|
+
const catalogId = bundledCatalogId(provider);
|
|
572
|
+
const base = catalogId && Object.hasOwn(providerInfo, catalogId) ? providerInfo[catalogId] : void 0;
|
|
573
|
+
const known = base && locale === "en" ? {
|
|
574
|
+
...base,
|
|
575
|
+
...englishInfo[catalogId]
|
|
576
|
+
} : base;
|
|
577
|
+
const ownedCopy = catalogId ? bundledDescriptions[catalogId] : void 0;
|
|
578
|
+
const description = ownedCopy && provider.description === ownedCopy[0] ? ownedCopy[locale === "zh" ? 0 : 1] : base && provider.description === base.description ? known.description : provider.description.trim() ? provider.description : known?.description ?? provider.description;
|
|
579
|
+
if (provider.pricing?.trim()) return {
|
|
580
|
+
description,
|
|
581
|
+
pricing: provider.pricing,
|
|
582
|
+
pricingUrl: provider.pricingUrl ?? known?.pricingUrl ?? provider.signupUrl
|
|
583
|
+
};
|
|
584
|
+
if (known) return {
|
|
585
|
+
description,
|
|
586
|
+
pricing: known.pricing,
|
|
587
|
+
pricingUrl: provider.pricingUrl ?? known.pricingUrl ?? provider.signupUrl
|
|
588
|
+
};
|
|
589
|
+
return {
|
|
590
|
+
description,
|
|
591
|
+
pricing: missingPricing(locale),
|
|
592
|
+
pricingUrl: provider.pricingUrl ?? provider.signupUrl
|
|
593
|
+
};
|
|
438
594
|
}
|
|
439
595
|
//#endregion
|
|
440
596
|
//#region src/client.tsx
|
|
@@ -454,32 +610,129 @@ function editable(settings) {
|
|
|
454
610
|
const { revision: _revision, ...rest } = settings;
|
|
455
611
|
return rest;
|
|
456
612
|
}
|
|
613
|
+
const STATUS_REFRESH_MS = 3e3;
|
|
614
|
+
function isPageHidden(hidden) {
|
|
615
|
+
if (hidden) return true;
|
|
616
|
+
return typeof document !== "undefined" && document.visibilityState === "hidden";
|
|
617
|
+
}
|
|
618
|
+
function canStartStatusRead(input) {
|
|
619
|
+
if (!input.mounted) return false;
|
|
620
|
+
if (input.source === "poll") return !input.hidden && !input.busy && !input.reordering && !input.reading;
|
|
621
|
+
return true;
|
|
622
|
+
}
|
|
623
|
+
function isExternalConfigurationUrl(url) {
|
|
624
|
+
return /^https:\/\//i.test(url);
|
|
625
|
+
}
|
|
626
|
+
function shouldApplyPolledStatus(input) {
|
|
627
|
+
if (input.hidden || input.busy || input.reordering) return false;
|
|
628
|
+
if (input.requestId !== input.latestRequestId) return false;
|
|
629
|
+
if (input.currentRevision !== void 0 && input.incomingRevision < input.currentRevision) return false;
|
|
630
|
+
return true;
|
|
631
|
+
}
|
|
632
|
+
function bindStatusRefresh(options) {
|
|
633
|
+
const intervalMs = options.intervalMs ?? 3e3;
|
|
634
|
+
let inFlight = false;
|
|
635
|
+
let stopped = false;
|
|
636
|
+
const run = async () => {
|
|
637
|
+
if (stopped || inFlight || options.isHidden()) return;
|
|
638
|
+
inFlight = true;
|
|
639
|
+
try {
|
|
640
|
+
await options.tick();
|
|
641
|
+
} finally {
|
|
642
|
+
inFlight = false;
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
const timer = (options.setIntervalFn ?? setInterval)(() => {
|
|
646
|
+
run();
|
|
647
|
+
}, intervalMs);
|
|
648
|
+
const onVisible = () => {
|
|
649
|
+
if (!options.isHidden()) run();
|
|
650
|
+
};
|
|
651
|
+
const add = options.addListener ?? ((type, listener) => {
|
|
652
|
+
if (typeof document !== "undefined") document.addEventListener(type, listener);
|
|
653
|
+
});
|
|
654
|
+
const remove = options.removeListener ?? ((type, listener) => {
|
|
655
|
+
if (typeof document !== "undefined") document.removeEventListener(type, listener);
|
|
656
|
+
});
|
|
657
|
+
add("visibilitychange", onVisible);
|
|
658
|
+
return () => {
|
|
659
|
+
stopped = true;
|
|
660
|
+
(options.clearIntervalFn ?? clearInterval)(timer);
|
|
661
|
+
remove("visibilitychange", onVisible);
|
|
662
|
+
};
|
|
663
|
+
}
|
|
457
664
|
function isProviderOn(status, provider) {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
return enabled && selected === provider.id;
|
|
665
|
+
if (provider.kind === "fetch") return status.fetchActive && status.settings.fetchProvider === provider.id && provider.configured;
|
|
666
|
+
return status.searchActive && selectedSearchBackend(status)?.id === provider.id;
|
|
461
667
|
}
|
|
462
|
-
function
|
|
463
|
-
|
|
464
|
-
|
|
668
|
+
function dormantSearchPlaceholder(id) {
|
|
669
|
+
return {
|
|
670
|
+
id,
|
|
671
|
+
label: id,
|
|
672
|
+
description: "",
|
|
673
|
+
billing: "unknown",
|
|
674
|
+
kind: "search",
|
|
675
|
+
configured: false,
|
|
676
|
+
calls: 0,
|
|
677
|
+
failures: 0,
|
|
678
|
+
configurationOwned: false
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
function isRegisteredSearch(status, id) {
|
|
682
|
+
return status.providers.some((provider) => provider.kind === "search" && provider.id === id);
|
|
465
683
|
}
|
|
466
684
|
function catalogSearchBackends(status) {
|
|
467
|
-
const
|
|
468
|
-
const
|
|
469
|
-
|
|
685
|
+
const byId = new Map(status.providers.filter((provider) => provider.kind === "search").map((provider) => [provider.id, provider]));
|
|
686
|
+
const rows = [];
|
|
687
|
+
const seen = /* @__PURE__ */ new Set();
|
|
688
|
+
for (const id of status.settings.searchOrder) {
|
|
689
|
+
if (!id || seen.has(id)) continue;
|
|
690
|
+
seen.add(id);
|
|
691
|
+
rows.push(byId.get(id) ?? dormantSearchPlaceholder(id));
|
|
692
|
+
}
|
|
693
|
+
for (const provider of status.providers) {
|
|
694
|
+
if (provider.kind !== "search" || seen.has(provider.id)) continue;
|
|
695
|
+
rows.push(provider);
|
|
696
|
+
}
|
|
697
|
+
return rows;
|
|
698
|
+
}
|
|
699
|
+
function searchBackends(status) {
|
|
700
|
+
const participating = new Set(status.settings.searchOrder);
|
|
701
|
+
return catalogSearchBackends(status).filter((provider) => participating.has(provider.id));
|
|
470
702
|
}
|
|
471
703
|
function selectedSearchBackend(status) {
|
|
472
|
-
const backends = searchBackends(status);
|
|
704
|
+
const backends = searchBackends(status).filter((provider) => isRegisteredSearch(status, provider.id));
|
|
473
705
|
const id = pickActiveSearch(backends.map((provider) => provider.id), (id) => Boolean(backends.find((provider) => provider.id === id)?.configured));
|
|
474
706
|
return backends.find((provider) => provider.id === id);
|
|
475
707
|
}
|
|
476
|
-
function
|
|
708
|
+
function droppedDormantIds(settings, order, providers) {
|
|
709
|
+
const known = new Set(providers.filter((provider) => provider.kind === "search").map((provider) => provider.id));
|
|
710
|
+
return [...new Set(settings.searchOrder.filter((id) => id && !known.has(id) && !order.includes(id)))];
|
|
711
|
+
}
|
|
712
|
+
function configurationAccess(provider) {
|
|
713
|
+
if (ownsProviderConfiguration(provider)) return { kind: "owned" };
|
|
714
|
+
const url = safeConfigurationUrl(provider.configurationUrl);
|
|
715
|
+
if (url) return {
|
|
716
|
+
kind: "link",
|
|
717
|
+
url
|
|
718
|
+
};
|
|
719
|
+
if (provider.configurationOwner?.trim()) return {
|
|
720
|
+
kind: "owner",
|
|
721
|
+
name: provider.configurationOwner
|
|
722
|
+
};
|
|
723
|
+
return { kind: "unknown" };
|
|
724
|
+
}
|
|
725
|
+
function AppLink({ url, label, children }) {
|
|
726
|
+
const external = isExternalConfigurationUrl(url);
|
|
477
727
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
478
728
|
href: url,
|
|
479
|
-
|
|
480
|
-
|
|
729
|
+
...external ? {
|
|
730
|
+
target: "_blank",
|
|
731
|
+
rel: "noreferrer noopener"
|
|
732
|
+
} : {},
|
|
481
733
|
"aria-label": label,
|
|
482
734
|
onClick: (event) => {
|
|
735
|
+
if (!external) return;
|
|
483
736
|
const bridge = globalThis.dshWindow;
|
|
484
737
|
if (bridge?.openExternal) {
|
|
485
738
|
event.preventDefault();
|
|
@@ -495,9 +748,13 @@ function canEnableSearch(order, providers, keys, writable) {
|
|
|
495
748
|
if (!provider) return false;
|
|
496
749
|
if (provider.configured) return true;
|
|
497
750
|
const typed = (keys[provider.id] ?? "").trim();
|
|
498
|
-
return Boolean(provider.credentialRef && typed && writable[provider.credentialRef] !== false);
|
|
751
|
+
return Boolean(ownsProviderConfiguration(provider) && provider.credentialRef && typed && writable[provider.credentialRef] !== false);
|
|
499
752
|
});
|
|
500
753
|
}
|
|
754
|
+
function ownsProviderConfiguration(provider) {
|
|
755
|
+
if (typeof provider.configurationOwned === "boolean") return provider.configurationOwned;
|
|
756
|
+
return !provider.configurationOwner || provider.configurationOwner === "@klarkxy/dsh-web-search-manager";
|
|
757
|
+
}
|
|
501
758
|
function nextSearchEnabled(currentlyEnabled, nextOrder, providers, keys, writable) {
|
|
502
759
|
return currentlyEnabled && canEnableSearch(nextOrder, providers, keys, writable);
|
|
503
760
|
}
|
|
@@ -549,11 +806,14 @@ const copy = {
|
|
|
549
806
|
dragPrefix: "拖动排序 ",
|
|
550
807
|
dragHint: "拖动排序,或用上下方向键",
|
|
551
808
|
inUse: "当前使用",
|
|
809
|
+
available: "可用",
|
|
810
|
+
unavailable: "暂不可用",
|
|
811
|
+
dormant: "未加载,保留选择",
|
|
552
812
|
signup: "去注册",
|
|
553
813
|
pricing: "费用说明",
|
|
554
814
|
website: "官网",
|
|
815
|
+
openConfig: "打开配置",
|
|
555
816
|
needsKey: "待配置 Key",
|
|
556
|
-
unavailable: "暂不可用",
|
|
557
817
|
sharedKey: "与其他设置共用凭据",
|
|
558
818
|
keyConfigured: "已配置,留空不替换",
|
|
559
819
|
keyPlaceholder: "输入 API Key",
|
|
@@ -567,7 +827,11 @@ const copy = {
|
|
|
567
827
|
maxResults: "每条查询结果上限",
|
|
568
828
|
maxQueries: "每次调用查询上限",
|
|
569
829
|
timeoutMs: "请求超时(毫秒)",
|
|
570
|
-
maxFetchChars: "正文字符上限"
|
|
830
|
+
maxFetchChars: "正文字符上限",
|
|
831
|
+
fetchTitle: "网页读取",
|
|
832
|
+
fetchProvider: "读取服务",
|
|
833
|
+
ownerSettings: "配置入口:",
|
|
834
|
+
unknownOwner: "请在服务所属插件中配置"
|
|
571
835
|
},
|
|
572
836
|
en: {
|
|
573
837
|
tabsLabel: "Web search settings",
|
|
@@ -589,11 +853,14 @@ const copy = {
|
|
|
589
853
|
dragPrefix: "Reorder ",
|
|
590
854
|
dragHint: "Drag to reorder, or use arrow keys",
|
|
591
855
|
inUse: "In use",
|
|
856
|
+
available: "Available",
|
|
857
|
+
unavailable: "Unavailable",
|
|
858
|
+
dormant: "Not loaded; selection kept",
|
|
592
859
|
signup: "Sign up",
|
|
593
860
|
pricing: "Pricing",
|
|
594
861
|
website: "Website",
|
|
862
|
+
openConfig: "Open configuration",
|
|
595
863
|
needsKey: "Key required",
|
|
596
|
-
unavailable: "Unavailable",
|
|
597
864
|
sharedKey: "Shares a credential with other settings",
|
|
598
865
|
keyConfigured: "Configured; empty keeps it",
|
|
599
866
|
keyPlaceholder: "Enter API key",
|
|
@@ -607,14 +874,38 @@ const copy = {
|
|
|
607
874
|
maxResults: "Max results/query",
|
|
608
875
|
maxQueries: "Max queries/call",
|
|
609
876
|
timeoutMs: "Request timeout (ms)",
|
|
610
|
-
maxFetchChars: "Max text chars"
|
|
877
|
+
maxFetchChars: "Max text chars",
|
|
878
|
+
fetchTitle: "Read web pages",
|
|
879
|
+
fetchProvider: "Fetch service",
|
|
880
|
+
ownerSettings: "Configure in: ",
|
|
881
|
+
unknownOwner: "Configure this service in its plugin"
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
var UiError = class extends Error {
|
|
885
|
+
key;
|
|
886
|
+
constructor(key) {
|
|
887
|
+
super(key);
|
|
888
|
+
this.key = key;
|
|
611
889
|
}
|
|
612
890
|
};
|
|
891
|
+
function uiMessage(message, locale, providers = []) {
|
|
892
|
+
if (typeof message === "string") return localErrorText(message, locale);
|
|
893
|
+
if ("key" in message) return copy[locale][message.key];
|
|
894
|
+
if ("sources" in message) return testOkText(locale, message.sources);
|
|
895
|
+
if ("moved" in message) {
|
|
896
|
+
const provider = providers.find((item) => item.id === message.moved);
|
|
897
|
+
return movedText(locale, provider ? providerDisplayLabel(provider, locale) : message.moved, message.position);
|
|
898
|
+
}
|
|
899
|
+
return message.invalid.map((key) => {
|
|
900
|
+
const [, min, max] = LIMITS.find((item) => item[0] === key);
|
|
901
|
+
return limitInvalidText(locale, copy[locale][key], min, max);
|
|
902
|
+
}).join(" ");
|
|
903
|
+
}
|
|
613
904
|
function movedText(locale, label, position) {
|
|
614
905
|
return locale === "zh" ? `${label} 已移至第 ${position} 位` : `${label} moved to position ${position}`;
|
|
615
906
|
}
|
|
616
907
|
function pricingCheckedText(locale, date) {
|
|
617
|
-
return locale === "zh" ?
|
|
908
|
+
return locale === "zh" ? `内置适配器价格核对于 ${date},额度与费用以供应商账户为准。` : `Bundled adapter prices checked on ${date}; your provider account is authoritative.`;
|
|
618
909
|
}
|
|
619
910
|
function testOkText(locale, sources) {
|
|
620
911
|
return locale === "zh" ? `连接正常,返回 ${sources} 条来源。` : `Connection OK: ${sources} sources returned.`;
|
|
@@ -655,13 +946,25 @@ function parseLimit(raw, min, max) {
|
|
|
655
946
|
* Settings written by an immediate action (a switch or a reorder). It keeps the
|
|
656
947
|
* saved limits and never includes typed-but-unsaved keys or limit drafts.
|
|
657
948
|
*/
|
|
658
|
-
function immediateUpdate(settings, on, order) {
|
|
949
|
+
function immediateUpdate(settings, on, order, providers, remove = []) {
|
|
950
|
+
let searchOrder = order;
|
|
951
|
+
if (providers) {
|
|
952
|
+
const known = new Set(providers.filter((provider) => provider.kind === "search").map((provider) => provider.id));
|
|
953
|
+
const removed = new Set(remove);
|
|
954
|
+
const following = order.filter((id) => !removed.has(id));
|
|
955
|
+
if (following.some((id) => !known.has(id))) searchOrder = following;
|
|
956
|
+
else {
|
|
957
|
+
searchOrder = settings.searchOrder.flatMap((id) => {
|
|
958
|
+
if (removed.has(id)) return [];
|
|
959
|
+
return known.has(id) ? following.splice(0, 1) : [id];
|
|
960
|
+
});
|
|
961
|
+
searchOrder.push(...following);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
659
964
|
return {
|
|
660
965
|
...editable(settings),
|
|
661
|
-
searchOrder
|
|
662
|
-
searchEnabled: on
|
|
663
|
-
fetchProvider: "http",
|
|
664
|
-
fetchEnabled: on
|
|
966
|
+
searchOrder,
|
|
967
|
+
searchEnabled: on
|
|
665
968
|
};
|
|
666
969
|
}
|
|
667
970
|
function limitTexts(settings) {
|
|
@@ -679,7 +982,7 @@ function useHostLocale(client) {
|
|
|
679
982
|
}
|
|
680
983
|
/** Keyboard reorders are batched: persist once the arrows stop for this long. */
|
|
681
984
|
const REORDER_DEBOUNCE_MS = 500;
|
|
682
|
-
function NetworkSearchSettings({ client }) {
|
|
985
|
+
function NetworkSearchSettings({ client, hidden }) {
|
|
683
986
|
const locale = useHostLocale(client);
|
|
684
987
|
const text = copy[locale];
|
|
685
988
|
const tabsId = (0, react.useId)();
|
|
@@ -703,6 +1006,17 @@ function NetworkSearchSettings({ client }) {
|
|
|
703
1006
|
const timerRef = (0, react.useRef)(void 0);
|
|
704
1007
|
const handleRefs = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
705
1008
|
const refocusRef = (0, react.useRef)(null);
|
|
1009
|
+
const busyRef = (0, react.useRef)(false);
|
|
1010
|
+
const hiddenRef = (0, react.useRef)(hidden);
|
|
1011
|
+
hiddenRef.current = hidden;
|
|
1012
|
+
const requestSeq = (0, react.useRef)(0);
|
|
1013
|
+
const mountedRef = (0, react.useRef)(true);
|
|
1014
|
+
const readingRef = (0, react.useRef)(false);
|
|
1015
|
+
const readingRequest = (0, react.useRef)(0);
|
|
1016
|
+
const statusRead = (0, react.useRef)(Promise.resolve());
|
|
1017
|
+
const readGateRef = (0, react.useRef)(void 0);
|
|
1018
|
+
if (!readGateRef.current) readGateRef.current = createPluginReadGate();
|
|
1019
|
+
const readGate = readGateRef.current;
|
|
706
1020
|
async function call(endpoint, payload = {}) {
|
|
707
1021
|
return unwrap(await client.connection.rpc.call(WEB_SEARCH_RPC_CHANNEL, endpoint, payload));
|
|
708
1022
|
}
|
|
@@ -718,30 +1032,109 @@ function NetworkSearchSettings({ client }) {
|
|
|
718
1032
|
});
|
|
719
1033
|
if (resetDraft) setInvalid({});
|
|
720
1034
|
}
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
1035
|
+
function pollBlocked() {
|
|
1036
|
+
return !canStartStatusRead({
|
|
1037
|
+
source: "poll",
|
|
1038
|
+
mounted: mountedRef.current,
|
|
1039
|
+
hidden: isPageHidden(hiddenRef.current),
|
|
1040
|
+
busy: busyRef.current,
|
|
1041
|
+
reordering: pendingRef.current !== null,
|
|
1042
|
+
reading: readingRef.current
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
async function describeOwned(next, mine) {
|
|
1046
|
+
const refs = next.providers.flatMap((provider) => ownsProviderConfiguration(provider) && provider.credentialRef ? [provider.credentialRef] : []);
|
|
1047
|
+
if (!refs.length) return;
|
|
1048
|
+
const credentials = unwrap(await readGate.run(() => client.remote.credentials.describe(refs)));
|
|
1049
|
+
if (!mountedRef.current || mine !== requestSeq.current) return;
|
|
1050
|
+
setWritable(Object.fromEntries(Object.entries(credentials).map(([ref, value]) => [ref, value.writable])));
|
|
1051
|
+
}
|
|
1052
|
+
async function load(resetDraft = false, source = "user") {
|
|
1053
|
+
if (source === "poll" && !readGate.canRead()) return;
|
|
1054
|
+
if (source === "user") {
|
|
1055
|
+
readGate.reset();
|
|
1056
|
+
requestSeq.current += 1;
|
|
1057
|
+
}
|
|
1058
|
+
while (readingRef.current) {
|
|
1059
|
+
if (source === "poll" || !mountedRef.current) return;
|
|
1060
|
+
await statusRead.current;
|
|
1061
|
+
}
|
|
1062
|
+
if (!mountedRef.current) return;
|
|
1063
|
+
if (source === "poll" && pollBlocked()) return;
|
|
1064
|
+
const mine = ++requestSeq.current;
|
|
1065
|
+
readingRef.current = true;
|
|
1066
|
+
readingRequest.current = mine;
|
|
1067
|
+
let release = () => {};
|
|
1068
|
+
statusRead.current = new Promise((resolve) => {
|
|
1069
|
+
release = resolve;
|
|
1070
|
+
});
|
|
1071
|
+
try {
|
|
1072
|
+
const next = unwrap(await readGate.run(() => client.connection.rpc.call(WEB_SEARCH_RPC_CHANNEL, "status", {})));
|
|
1073
|
+
if (!mountedRef.current || mine !== requestSeq.current) return next;
|
|
1074
|
+
if (source === "poll" && !shouldApplyPolledStatus({
|
|
1075
|
+
requestId: mine,
|
|
1076
|
+
latestRequestId: requestSeq.current,
|
|
1077
|
+
busy: busyRef.current,
|
|
1078
|
+
reordering: pendingRef.current !== null,
|
|
1079
|
+
hidden: isPageHidden(hiddenRef.current),
|
|
1080
|
+
currentRevision: statusRef.current?.settings.revision,
|
|
1081
|
+
incomingRevision: next.settings.revision
|
|
1082
|
+
})) return next;
|
|
1083
|
+
adopt(next, resetDraft);
|
|
1084
|
+
setLoadError("");
|
|
1085
|
+
await describeOwned(next, mine);
|
|
1086
|
+
return next;
|
|
1087
|
+
} catch (cause) {
|
|
1088
|
+
if (!mountedRef.current || mine !== requestSeq.current) return;
|
|
1089
|
+
setLoadError(cause instanceof Error && cause.message ? cause.message : { key: "failed" });
|
|
1090
|
+
if (source === "poll") return;
|
|
1091
|
+
throw cause;
|
|
1092
|
+
} finally {
|
|
1093
|
+
if (readingRequest.current === mine) readingRef.current = false;
|
|
1094
|
+
release();
|
|
729
1095
|
}
|
|
730
|
-
return next;
|
|
731
1096
|
}
|
|
732
1097
|
(0, react.useEffect)(() => {
|
|
733
|
-
|
|
1098
|
+
mountedRef.current = true;
|
|
1099
|
+
readGate.reset();
|
|
1100
|
+
load().catch((cause) => setLoadError(cause instanceof Error && cause.message ? cause.message : { key: "failed" }));
|
|
1101
|
+
const bind = () => bindStatusRefresh({
|
|
1102
|
+
isHidden: () => isPageHidden(hiddenRef.current),
|
|
1103
|
+
tick: async () => {
|
|
1104
|
+
await load(false, "poll").catch(() => {});
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
let stop = bind();
|
|
1108
|
+
const stopConnection = client.connection.generation?.subscribe(() => {
|
|
1109
|
+
readGate.reset();
|
|
1110
|
+
requestSeq.current += 1;
|
|
1111
|
+
readingRef.current = false;
|
|
1112
|
+
statusRead.current = Promise.resolve();
|
|
1113
|
+
stop();
|
|
1114
|
+
stop = bind();
|
|
1115
|
+
load(false, "poll");
|
|
1116
|
+
});
|
|
1117
|
+
return () => {
|
|
1118
|
+
mountedRef.current = false;
|
|
1119
|
+
readGate.reset();
|
|
1120
|
+
requestSeq.current += 1;
|
|
1121
|
+
stop();
|
|
1122
|
+
stopConnection?.();
|
|
1123
|
+
if (timerRef.current === void 0) return;
|
|
1124
|
+
clearTimeout(timerRef.current);
|
|
1125
|
+
const current = statusRef.current;
|
|
1126
|
+
const next = pendingRef.current;
|
|
1127
|
+
if (current && next) call("update", {
|
|
1128
|
+
settings: immediateUpdate(current.settings, current.settings.searchEnabled, next, current.providers),
|
|
1129
|
+
expectedRevision: current.settings.revision
|
|
1130
|
+
}).catch(() => {});
|
|
1131
|
+
};
|
|
734
1132
|
}, [client]);
|
|
735
|
-
(0, react.useEffect)(() =>
|
|
736
|
-
if (
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
if (current && next) call("update", {
|
|
741
|
-
settings: immediateUpdate(current.settings, current.settings.searchEnabled, next),
|
|
742
|
-
expectedRevision: current.settings.revision
|
|
743
|
-
}).catch(() => {});
|
|
744
|
-
}, []);
|
|
1133
|
+
(0, react.useEffect)(() => {
|
|
1134
|
+
if (hidden) return;
|
|
1135
|
+
if (!statusRef.current) return;
|
|
1136
|
+
load(false, "poll").catch(() => {});
|
|
1137
|
+
}, [hidden]);
|
|
745
1138
|
(0, react.useLayoutEffect)(() => {
|
|
746
1139
|
const id = refocusRef.current;
|
|
747
1140
|
if (!id) return;
|
|
@@ -750,20 +1143,24 @@ function NetworkSearchSettings({ client }) {
|
|
|
750
1143
|
if (handle && document.activeElement !== handle) handle.focus();
|
|
751
1144
|
});
|
|
752
1145
|
async function action(run) {
|
|
1146
|
+
readGate.reset();
|
|
1147
|
+
requestSeq.current += 1;
|
|
1148
|
+
busyRef.current = true;
|
|
753
1149
|
setBusy(true);
|
|
754
1150
|
setNote("");
|
|
755
1151
|
setError("");
|
|
756
1152
|
try {
|
|
757
1153
|
await run();
|
|
758
1154
|
} catch (cause) {
|
|
759
|
-
setError(cause instanceof Error && cause.message ? cause.message :
|
|
1155
|
+
setError(cause instanceof UiError ? { key: cause.key } : cause instanceof Error && cause.message ? cause.message : { key: "failed" });
|
|
760
1156
|
await load().catch(() => {});
|
|
761
1157
|
} finally {
|
|
1158
|
+
busyRef.current = false;
|
|
762
1159
|
setBusy(false);
|
|
763
1160
|
}
|
|
764
1161
|
}
|
|
765
1162
|
/** Switches and reorders: settings only, applied at once. */
|
|
766
|
-
function runImmediate(on, order) {
|
|
1163
|
+
function runImmediate(on, order, remove = []) {
|
|
767
1164
|
clearTimeout(timerRef.current);
|
|
768
1165
|
timerRef.current = void 0;
|
|
769
1166
|
pendingRef.current = null;
|
|
@@ -771,7 +1168,7 @@ function NetworkSearchSettings({ client }) {
|
|
|
771
1168
|
const current = statusRef.current;
|
|
772
1169
|
if (!current) return;
|
|
773
1170
|
const next = await call("update", {
|
|
774
|
-
settings: immediateUpdate(current.settings, on, order),
|
|
1171
|
+
settings: immediateUpdate(current.settings, on, order, current.providers, remove),
|
|
775
1172
|
expectedRevision: current.settings.revision
|
|
776
1173
|
});
|
|
777
1174
|
setStatus(next);
|
|
@@ -787,8 +1184,8 @@ function NetworkSearchSettings({ client }) {
|
|
|
787
1184
|
if (provider.kind !== "search" || only && provider.id !== only.id) continue;
|
|
788
1185
|
const snapshot = keys[provider.id] ?? "";
|
|
789
1186
|
const typed = snapshot.trim();
|
|
790
|
-
if (!provider.credentialRef || !typed) continue;
|
|
791
|
-
if (writable[provider.credentialRef] === false) throw new
|
|
1187
|
+
if (!ownsProviderConfiguration(provider) || !provider.credentialRef || !typed) continue;
|
|
1188
|
+
if (writable[provider.credentialRef] === false) throw new UiError("readOnly");
|
|
792
1189
|
unwrap(await client.remote.credentials.set(provider.credentialRef, typed));
|
|
793
1190
|
setKeys((current) => current[provider.id] === snapshot ? {
|
|
794
1191
|
...current,
|
|
@@ -797,21 +1194,21 @@ function NetworkSearchSettings({ client }) {
|
|
|
797
1194
|
}
|
|
798
1195
|
}
|
|
799
1196
|
async function removeKey(provider) {
|
|
800
|
-
if (!provider.credentialRef || provider.credentialShared || !status) return;
|
|
1197
|
+
if (!ownsProviderConfiguration(provider) || !provider.credentialRef || provider.credentialShared || !status) return;
|
|
801
1198
|
unwrap(await client.remote.credentials.unset(provider.credentialRef));
|
|
802
1199
|
setKeys((current) => ({
|
|
803
1200
|
...current,
|
|
804
1201
|
[provider.id]: ""
|
|
805
1202
|
}));
|
|
806
1203
|
await load();
|
|
807
|
-
setNote(
|
|
1204
|
+
setNote({ key: "keyDeleted" });
|
|
808
1205
|
}
|
|
809
1206
|
if (!status || !limitDraft) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
810
1207
|
className: "web-search-settings ws-root dsh-ui-panel",
|
|
811
1208
|
children: [loadError ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
812
1209
|
role: "alert",
|
|
813
1210
|
className: "dsh-ui-error",
|
|
814
|
-
children: [text.loadFailed, loadError]
|
|
1211
|
+
children: [text.loadFailed, uiMessage(loadError, locale, status?.providers)]
|
|
815
1212
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
816
1213
|
role: "status",
|
|
817
1214
|
className: "dsh-ui-hint",
|
|
@@ -850,7 +1247,10 @@ function NetworkSearchSettings({ client }) {
|
|
|
850
1247
|
pendingRef.current = next;
|
|
851
1248
|
setPendingOrder(next);
|
|
852
1249
|
if (keyboard) refocusRef.current = id;
|
|
853
|
-
setSortAnnouncement(
|
|
1250
|
+
setSortAnnouncement({
|
|
1251
|
+
moved: id,
|
|
1252
|
+
position: nextIndex + 1
|
|
1253
|
+
});
|
|
854
1254
|
clearTimeout(timerRef.current);
|
|
855
1255
|
timerRef.current = setTimeout(() => {
|
|
856
1256
|
timerRef.current = void 0;
|
|
@@ -871,13 +1271,13 @@ function NetworkSearchSettings({ client }) {
|
|
|
871
1271
|
const value = parseLimit(limitDraft[key], min, max);
|
|
872
1272
|
if (value === void 0) {
|
|
873
1273
|
nextInvalid[key] = true;
|
|
874
|
-
messages.push(
|
|
1274
|
+
messages.push(key);
|
|
875
1275
|
} else parsed[key] = value;
|
|
876
1276
|
}
|
|
877
1277
|
setInvalid(nextInvalid);
|
|
878
1278
|
if (messages.length) {
|
|
879
1279
|
setNote("");
|
|
880
|
-
setError(messages
|
|
1280
|
+
setError({ invalid: messages });
|
|
881
1281
|
return;
|
|
882
1282
|
}
|
|
883
1283
|
action(async () => {
|
|
@@ -886,14 +1286,12 @@ function NetworkSearchSettings({ client }) {
|
|
|
886
1286
|
adopt(await call("update", {
|
|
887
1287
|
settings: {
|
|
888
1288
|
...editable(current.settings),
|
|
889
|
-
...parsed
|
|
890
|
-
fetchProvider: "http",
|
|
891
|
-
fetchEnabled: current.settings.searchEnabled
|
|
1289
|
+
...parsed
|
|
892
1290
|
},
|
|
893
1291
|
expectedRevision: current.settings.revision
|
|
894
1292
|
}), true);
|
|
895
1293
|
await load();
|
|
896
|
-
setNote(
|
|
1294
|
+
setNote({ key: "saved" });
|
|
897
1295
|
});
|
|
898
1296
|
}
|
|
899
1297
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
@@ -920,30 +1318,46 @@ function NetworkSearchSettings({ client }) {
|
|
|
920
1318
|
className: "ws-sr-only",
|
|
921
1319
|
role: "status",
|
|
922
1320
|
"aria-live": "polite",
|
|
923
|
-
children: sortAnnouncement
|
|
1321
|
+
children: uiMessage(sortAnnouncement, locale, saved.providers)
|
|
924
1322
|
}),
|
|
925
1323
|
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
926
1324
|
role: "alert",
|
|
927
1325
|
className: "dsh-ui-error",
|
|
928
|
-
children: error
|
|
1326
|
+
children: uiMessage(error, locale, saved.providers)
|
|
929
1327
|
}),
|
|
1328
|
+
loadError ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1329
|
+
className: "dsh-ui-stack",
|
|
1330
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1331
|
+
role: "alert",
|
|
1332
|
+
className: "dsh-ui-error",
|
|
1333
|
+
children: [text.loadFailed, uiMessage(loadError, locale, status?.providers)]
|
|
1334
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1335
|
+
variant: "outline",
|
|
1336
|
+
size: "sm",
|
|
1337
|
+
disabled: busy,
|
|
1338
|
+
onClick: () => void action(async () => {
|
|
1339
|
+
await load();
|
|
1340
|
+
}),
|
|
1341
|
+
children: text.reconnect
|
|
1342
|
+
})]
|
|
1343
|
+
}) : null,
|
|
930
1344
|
note && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
931
1345
|
role: "status",
|
|
932
1346
|
className: "dsh-ui-notice",
|
|
933
|
-
children: note
|
|
1347
|
+
children: uiMessage(note, locale, saved.providers)
|
|
934
1348
|
}),
|
|
935
1349
|
saved.storageFailed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
936
1350
|
role: "alert",
|
|
937
1351
|
className: "dsh-ui-banner dsh-ui-banner--danger",
|
|
938
1352
|
children: text.storageFailed
|
|
939
1353
|
}),
|
|
940
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1354
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
941
1355
|
role: "tabpanel",
|
|
942
1356
|
id: tabsId + "-providers-panel",
|
|
943
1357
|
"aria-labelledby": tabsId + "-providers-tab",
|
|
944
1358
|
hidden: tab !== "providers",
|
|
945
1359
|
tabIndex: 0,
|
|
946
|
-
children: backends.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1360
|
+
children: [backends.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
947
1361
|
className: "dsh-ui-empty",
|
|
948
1362
|
children: text.empty
|
|
949
1363
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
@@ -972,17 +1386,23 @@ function NetworkSearchSettings({ client }) {
|
|
|
972
1386
|
children: backends.map((provider) => {
|
|
973
1387
|
const participating = order.includes(provider.id);
|
|
974
1388
|
const rank = order.indexOf(provider.id);
|
|
975
|
-
const
|
|
976
|
-
const
|
|
977
|
-
const
|
|
1389
|
+
const dormant = !isRegisteredSearch(saved, provider.id);
|
|
1390
|
+
const active = !dormant && saved.searchActive && selected?.id === provider.id;
|
|
1391
|
+
const info = searchProviderInfo(provider, locale);
|
|
1392
|
+
const owned = ownsProviderConfiguration(provider);
|
|
1393
|
+
const access = configurationAccess(provider);
|
|
1394
|
+
const needsKey = owned && Boolean(provider.credentialRef);
|
|
978
1395
|
const shared = Boolean(provider.credentialShared);
|
|
979
|
-
const showKey = needsKey && participating && (!shared || !provider.configured);
|
|
1396
|
+
const showKey = !dormant && needsKey && participating && (!shared || !provider.configured);
|
|
980
1397
|
const canSort = participating && order.length > 1;
|
|
981
1398
|
const readOnly = writable[provider.credentialRef ?? ""] === false;
|
|
982
1399
|
const typedKey = (keys[provider.id] ?? "").trim();
|
|
1400
|
+
const availability = dormant ? "dormant" : provider.configured ? "available" : "unavailable";
|
|
983
1401
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
984
1402
|
className: active ? "is-active" : void 0,
|
|
985
1403
|
"data-testid": `web-search-rank-${provider.id}`,
|
|
1404
|
+
"data-availability": availability,
|
|
1405
|
+
"data-in-use": active || void 0,
|
|
986
1406
|
"data-dragging": dragSource === provider.id || void 0,
|
|
987
1407
|
"data-drop": dropTarget === provider.id && dragSource ? order.indexOf(dragSource) < rank ? "after" : "before" : void 0,
|
|
988
1408
|
onDragOver: (event) => {
|
|
@@ -1050,7 +1470,7 @@ function NetworkSearchSettings({ client }) {
|
|
|
1050
1470
|
}),
|
|
1051
1471
|
draggable: !busy,
|
|
1052
1472
|
"aria-disabled": busy,
|
|
1053
|
-
"aria-label": text.dragPrefix + provider
|
|
1473
|
+
"aria-label": text.dragPrefix + providerDisplayLabel(provider, locale),
|
|
1054
1474
|
title: text.dragHint,
|
|
1055
1475
|
onDragStart: (event) => {
|
|
1056
1476
|
if (busy) {
|
|
@@ -1082,35 +1502,49 @@ function NetworkSearchSettings({ client }) {
|
|
|
1082
1502
|
children: [
|
|
1083
1503
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
|
|
1084
1504
|
className: "dsh-ui-heading",
|
|
1085
|
-
children: provider
|
|
1505
|
+
children: providerDisplayLabel(provider, locale)
|
|
1506
|
+
}),
|
|
1507
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1508
|
+
className: "ws-provider-note",
|
|
1509
|
+
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 })]
|
|
1086
1510
|
}),
|
|
1087
1511
|
active && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
|
|
1088
1512
|
tone: "success",
|
|
1089
1513
|
children: text.inUse
|
|
1090
1514
|
}),
|
|
1091
|
-
provider.signupUrl && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1515
|
+
provider.signupUrl && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppLink, {
|
|
1092
1516
|
url: provider.signupUrl,
|
|
1093
|
-
label: provider
|
|
1517
|
+
label: providerDisplayLabel(provider, locale) + " " + text.signup,
|
|
1094
1518
|
children: text.signup
|
|
1095
1519
|
})
|
|
1096
1520
|
]
|
|
1097
1521
|
}),
|
|
1098
1522
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1099
1523
|
className: "web-search-provider-info",
|
|
1100
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: info.description }), /* @__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)(
|
|
1524
|
+
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, {
|
|
1101
1525
|
url: info.pricingUrl,
|
|
1102
|
-
label: provider
|
|
1526
|
+
label: providerDisplayLabel(provider, locale) + " " + text.pricing,
|
|
1103
1527
|
children: provider.id === "ddg" ? text.website : text.pricing
|
|
1104
1528
|
})] })] })]
|
|
1105
1529
|
}),
|
|
1106
|
-
|
|
1107
|
-
className: "
|
|
1108
|
-
children:
|
|
1530
|
+
access.kind === "link" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1531
|
+
className: "dsh-ui-hint",
|
|
1532
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppLink, {
|
|
1533
|
+
url: access.url,
|
|
1534
|
+
label: text.openConfig,
|
|
1535
|
+
children: text.openConfig
|
|
1536
|
+
})
|
|
1537
|
+
}) : access.kind === "owner" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1538
|
+
className: "dsh-ui-hint",
|
|
1539
|
+
children: [text.ownerSettings, access.name]
|
|
1540
|
+
}) : access.kind === "unknown" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1541
|
+
className: "dsh-ui-hint",
|
|
1542
|
+
children: text.unknownOwner
|
|
1109
1543
|
}) : null,
|
|
1110
|
-
shared && participating && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", {
|
|
1544
|
+
shared && participating && owned ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", {
|
|
1111
1545
|
className: "dsh-ui-meta",
|
|
1112
|
-
children: provider.credentialHint ?? text.sharedKey
|
|
1113
|
-
}),
|
|
1546
|
+
children: provider.credentialHint === "与模型设置共用 Key" ? locale === "zh" ? provider.credentialHint : "Shares a key with model settings" : provider.credentialHint ?? text.sharedKey
|
|
1547
|
+
}) : null,
|
|
1114
1548
|
showKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1115
1549
|
className: "dsh-ui-field",
|
|
1116
1550
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -1139,7 +1573,7 @@ function NetworkSearchSettings({ client }) {
|
|
|
1139
1573
|
onClick: () => void action(async () => {
|
|
1140
1574
|
await saveKeys(provider);
|
|
1141
1575
|
await load();
|
|
1142
|
-
setNote(
|
|
1576
|
+
setNote({ key: "keySaved" });
|
|
1143
1577
|
}),
|
|
1144
1578
|
children: text.saveKey
|
|
1145
1579
|
}) : null, needsKey && !shared && provider.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmButton, {
|
|
@@ -1155,11 +1589,11 @@ function NetworkSearchSettings({ client }) {
|
|
|
1155
1589
|
className: "web-search-rank-move",
|
|
1156
1590
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
1157
1591
|
checked: participating,
|
|
1158
|
-
label: provider
|
|
1592
|
+
label: providerDisplayLabel(provider, locale),
|
|
1159
1593
|
disabled: busy,
|
|
1160
1594
|
onChange: (next) => {
|
|
1161
1595
|
const following = next ? [...order, provider.id] : order.filter((id) => id !== provider.id);
|
|
1162
|
-
runImmediate(nextSearchEnabled(saved.settings.searchEnabled, following, saved.providers, {}, writable), following);
|
|
1596
|
+
runImmediate(nextSearchEnabled(saved.settings.searchEnabled, following, saved.providers, {}, writable), following, droppedDormantIds(saved.settings, following, saved.providers));
|
|
1163
1597
|
}
|
|
1164
1598
|
})
|
|
1165
1599
|
})
|
|
@@ -1167,10 +1601,10 @@ function NetworkSearchSettings({ client }) {
|
|
|
1167
1601
|
}, provider.id);
|
|
1168
1602
|
})
|
|
1169
1603
|
}),
|
|
1170
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1604
|
+
backends.some(usesBundledPricing) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1171
1605
|
className: "dsh-ui-hint",
|
|
1172
1606
|
children: pricingCheckedText(locale, PROVIDER_PRICING_CHECKED)
|
|
1173
|
-
}),
|
|
1607
|
+
}) : null,
|
|
1174
1608
|
on ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1175
1609
|
className: "dsh-ui-actions",
|
|
1176
1610
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
@@ -1181,13 +1615,110 @@ function NetworkSearchSettings({ client }) {
|
|
|
1181
1615
|
await saveKeys();
|
|
1182
1616
|
const result = await call("test");
|
|
1183
1617
|
await load();
|
|
1184
|
-
setNote(
|
|
1618
|
+
setNote({ sources: result.sources });
|
|
1185
1619
|
}),
|
|
1186
1620
|
children: text.test
|
|
1187
1621
|
})
|
|
1188
1622
|
}) : null
|
|
1189
1623
|
]
|
|
1190
|
-
})
|
|
1624
|
+
}), (saved.providers.some((provider) => provider.kind === "fetch") || saved.settings.fetchProvider) && (() => {
|
|
1625
|
+
const fetchId = saved.settings.fetchProvider;
|
|
1626
|
+
const fetchSelected = saved.providers.find((provider) => provider.kind === "fetch" && provider.id === fetchId);
|
|
1627
|
+
const fetchDormant = Boolean(fetchId) && !fetchSelected;
|
|
1628
|
+
const fetchActive = Boolean(fetchSelected && saved.fetchActive && fetchSelected.configured);
|
|
1629
|
+
const fetchAccess = fetchSelected ? configurationAccess(fetchSelected) : fetchDormant ? { kind: "unknown" } : void 0;
|
|
1630
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
1631
|
+
className: "dsh-ui-card dsh-ui-card--flat",
|
|
1632
|
+
"data-testid": "web-search-fetch",
|
|
1633
|
+
"data-availability": fetchDormant ? "dormant" : fetchSelected?.configured ? "available" : "unavailable",
|
|
1634
|
+
"data-in-use": fetchActive || void 0,
|
|
1635
|
+
children: [
|
|
1636
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
1637
|
+
className: "ws-card-head",
|
|
1638
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1639
|
+
className: "dsh-ui-title",
|
|
1640
|
+
children: text.fetchTitle
|
|
1641
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1642
|
+
className: "ws-provider-note",
|
|
1643
|
+
children: [
|
|
1644
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: fetchDormant ? "idle" : fetchSelected?.configured ? "done" : "warning" }),
|
|
1645
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: fetchDormant ? text.dormant : fetchSelected?.configured ? text.available : text.unavailable }),
|
|
1646
|
+
fetchActive ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
|
|
1647
|
+
tone: "success",
|
|
1648
|
+
children: text.inUse
|
|
1649
|
+
}) : null
|
|
1650
|
+
]
|
|
1651
|
+
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
1652
|
+
checked: saved.settings.fetchEnabled,
|
|
1653
|
+
label: text.fetchTitle,
|
|
1654
|
+
disabled: busy || reordering,
|
|
1655
|
+
onChange: (next) => void action(async () => {
|
|
1656
|
+
const current = statusRef.current;
|
|
1657
|
+
adopt(await call("update", {
|
|
1658
|
+
settings: {
|
|
1659
|
+
...editable(current.settings),
|
|
1660
|
+
fetchEnabled: next
|
|
1661
|
+
},
|
|
1662
|
+
expectedRevision: current.settings.revision
|
|
1663
|
+
}));
|
|
1664
|
+
await load();
|
|
1665
|
+
})
|
|
1666
|
+
})]
|
|
1667
|
+
}),
|
|
1668
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1669
|
+
className: "dsh-ui-field",
|
|
1670
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1671
|
+
className: "dsh-ui-label",
|
|
1672
|
+
children: text.fetchProvider
|
|
1673
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1674
|
+
className: "dsh-ui-select",
|
|
1675
|
+
"aria-label": text.fetchProvider,
|
|
1676
|
+
value: fetchId,
|
|
1677
|
+
disabled: busy || reordering,
|
|
1678
|
+
onChange: (event) => {
|
|
1679
|
+
const id = event.target.value;
|
|
1680
|
+
action(async () => {
|
|
1681
|
+
const current = statusRef.current;
|
|
1682
|
+
adopt(await call("update", {
|
|
1683
|
+
settings: {
|
|
1684
|
+
...editable(current.settings),
|
|
1685
|
+
fetchProvider: id
|
|
1686
|
+
},
|
|
1687
|
+
expectedRevision: current.settings.revision
|
|
1688
|
+
}));
|
|
1689
|
+
await load();
|
|
1690
|
+
});
|
|
1691
|
+
},
|
|
1692
|
+
children: [fetchDormant && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
1693
|
+
value: fetchId,
|
|
1694
|
+
children: [
|
|
1695
|
+
fetchId,
|
|
1696
|
+
" · ",
|
|
1697
|
+
text.dormant
|
|
1698
|
+
]
|
|
1699
|
+
}), saved.providers.filter((provider) => provider.kind === "fetch").map((provider) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
1700
|
+
value: provider.id,
|
|
1701
|
+
children: [providerDisplayLabel(provider, locale), provider.configured ? "" : " · " + text.unavailable]
|
|
1702
|
+
}, provider.id))]
|
|
1703
|
+
})]
|
|
1704
|
+
}),
|
|
1705
|
+
fetchAccess?.kind === "link" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1706
|
+
className: "dsh-ui-hint",
|
|
1707
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppLink, {
|
|
1708
|
+
url: fetchAccess.url,
|
|
1709
|
+
label: text.openConfig,
|
|
1710
|
+
children: text.openConfig
|
|
1711
|
+
})
|
|
1712
|
+
}) : fetchAccess?.kind === "owner" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1713
|
+
className: "dsh-ui-hint",
|
|
1714
|
+
children: [text.ownerSettings, fetchAccess.name]
|
|
1715
|
+
}) : fetchAccess?.kind === "unknown" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1716
|
+
className: "dsh-ui-hint",
|
|
1717
|
+
children: text.unknownOwner
|
|
1718
|
+
}) : null
|
|
1719
|
+
]
|
|
1720
|
+
});
|
|
1721
|
+
})()]
|
|
1191
1722
|
}),
|
|
1192
1723
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1193
1724
|
role: "tabpanel",
|
|
@@ -1329,20 +1860,34 @@ function apply(ctx) {
|
|
|
1329
1860
|
ctx.effect(() => client.slots.inject("plugins.bundle.config", () => client.slots.register({
|
|
1330
1861
|
name: "plugins.bundle.config",
|
|
1331
1862
|
key: "@klarkxy/dsh-web-search-manager"
|
|
1332
|
-
}, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NetworkSearchSettings, {
|
|
1863
|
+
}, (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NetworkSearchSettings, {
|
|
1864
|
+
client,
|
|
1865
|
+
hidden: props?.hidden
|
|
1866
|
+
}))), "web-search-manager.settings");
|
|
1333
1867
|
}
|
|
1334
1868
|
//#endregion
|
|
1335
1869
|
exports.NetworkSearchSettings = NetworkSearchSettings;
|
|
1870
|
+
exports.STATUS_REFRESH_MS = STATUS_REFRESH_MS;
|
|
1336
1871
|
exports.apply = apply;
|
|
1872
|
+
exports.bindStatusRefresh = bindStatusRefresh;
|
|
1337
1873
|
exports.canEnableSearch = canEnableSearch;
|
|
1874
|
+
exports.canStartStatusRead = canStartStatusRead;
|
|
1338
1875
|
exports.catalogSearchBackends = catalogSearchBackends;
|
|
1876
|
+
exports.configurationAccess = configurationAccess;
|
|
1877
|
+
exports.dormantSearchPlaceholder = dormantSearchPlaceholder;
|
|
1878
|
+
exports.droppedDormantIds = droppedDormantIds;
|
|
1339
1879
|
exports.immediateUpdate = immediateUpdate;
|
|
1340
1880
|
exports.inject = inject;
|
|
1881
|
+
exports.isExternalConfigurationUrl = isExternalConfigurationUrl;
|
|
1882
|
+
exports.isPageHidden = isPageHidden;
|
|
1341
1883
|
exports.isProviderOn = isProviderOn;
|
|
1884
|
+
exports.isRegisteredSearch = isRegisteredSearch;
|
|
1342
1885
|
exports.name = name;
|
|
1343
1886
|
exports.nextSearchEnabled = nextSearchEnabled;
|
|
1887
|
+
exports.ownsProviderConfiguration = ownsProviderConfiguration;
|
|
1344
1888
|
exports.parseLimit = parseLimit;
|
|
1345
1889
|
exports.searchBackends = searchBackends;
|
|
1346
1890
|
exports.selectedSearchBackend = selectedSearchBackend;
|
|
1891
|
+
exports.shouldApplyPolledStatus = shouldApplyPolledStatus;
|
|
1347
1892
|
|
|
1348
1893
|
//# sourceMappingURL=client.inner.cjs.map
|