@klarkxy/dsh-web-search-manager 0.2.0 → 0.2.2
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 +8 -30
- package/docs/README.zh-CN.md +8 -30
- package/lib/client.inner.cjs +147 -26
- package/lib/client.inner.cjs.map +1 -1
- package/lib/client.js +147 -26
- package/lib/contracts.d.ts +3 -25
- package/lib/contracts.js.map +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.js +19 -59
- package/lib/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
# @klarkxy/dsh-web-search-manager
|
|
1
|
+
# @klarkxy/dsh-web-search-manager
|
|
2
|
+
|
|
3
|
+
<!-- release-state-note -->
|
|
2
4
|
|
|
3
5
|
Choose which service answers the agent's web searches, set the limits every request runs under, and let the agent read public pages — all from one settings page.
|
|
4
6
|
|
|
5
7
|
[简体中文](docs/README.zh-CN.md)
|
|
6
8
|
|
|
7
|
-
Requires Node.js ≥22 and DSH `0.2.0-rc.2
|
|
9
|
+
Requires Node.js ≥22 (26 verified) and DSH `0.2.0-rc.2` or later. No repository build is needed.
|
|
8
10
|
|
|
9
11
|
## Install
|
|
10
12
|
|
|
@@ -35,7 +37,7 @@ Built-in search backends:
|
|
|
35
37
|
- **博查**: a Chinese web search API; requires `DSH_EDITOR_WEB_BOCHA_API_KEY`.
|
|
36
38
|
- **Serper**: a Google results API; requires `DSH_EDITOR_WEB_SERPER_API_KEY`.
|
|
37
39
|
- **Firecrawl**: a web search and extraction API; requires `DSH_EDITOR_WEB_FIRECRAWL_API_KEY`.
|
|
38
|
-
- **Tavily**: a Search API fixed to basic search depth, never auto-upgraded; requires `DSH_EDITOR_WEB_TAVILY_API_KEY`. The adapter is built into this plugin
|
|
40
|
+
- **Tavily**: a Search API fixed to basic search depth, never auto-upgraded; requires `DSH_EDITOR_WEB_TAVILY_API_KEY`. The adapter is built into this plugin.
|
|
39
41
|
|
|
40
42
|
## Read public pages
|
|
41
43
|
|
|
@@ -57,35 +59,11 @@ For agent access, add this entry to the plugin list in the `agent.cordis.yml` yo
|
|
|
57
59
|
|
|
58
60
|
Once that entry is enabled, the agent receives the official `web_search` and `web_fetch` tools from `@deepseek-ai/dsh-tool-web`, mounted only while search and fetching are enabled.
|
|
59
61
|
|
|
60
|
-
##
|
|
62
|
+
## Provider integration
|
|
61
63
|
|
|
62
64
|
Provider plugins call `ctx.web.registerSearchProvider(provider)` or `ctx.web.registerFetchProvider(provider)` directly, with no dependency on `webSearchManager`. Keep `available()` local, forward cancellation signals, and resolve configuration and credentials in the provider. Optional `dshWebManagement` display metadata on the provider can include `label`, `description`, `billing`, `configurationOwner`, `credentialHint`, `pricing`, an HTTPS `pricingUrl`, and an explicit `configurationUrl`. `configurationUrl` is the only settings link: HTTPS, a same-host `/…` path, or a `#…` hash. Do not expect this page to invent a route from `id` or `configurationOwner`. `credentialHint` is display text only. Providers without metadata are discovered under their id. Description and pricing from the provider win over any bundled notes with the same id; bundled adapters may still fall back to their known price list. Native services without pricing metadata show that none was provided. Metadata never contains credential values. The old manager factory methods remain as deprecated compatibility adapters; new plugins use native registration.
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
const provider = {
|
|
66
|
-
id: 'my-search',
|
|
67
|
-
dshWebManagement: {
|
|
68
|
-
id: 'my-search', label: 'My search', description: 'Custom search service',
|
|
69
|
-
billing: 'request', configurationOwner: '@example/my-plugin',
|
|
70
|
-
configurationUrl: '/plugins/my-plugin',
|
|
71
|
-
},
|
|
72
|
-
available: () => locallyConfigured(),
|
|
73
|
-
search: (request, signal) => searchWithOwnConfiguration(request, signal),
|
|
74
|
-
}
|
|
75
|
-
ctx.web.registerSearchProvider(provider)
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
DSH 0.2.0-rc.2 has no public registry enumeration or dynamic selection API. This package isolates a compatibility bridge in `web-registry.ts`: it observes the actual host Maps, manages availability and temporarily takes over selection, then restores provider instances and prior config/environment selection on unload. Unsupported host structures fail explicitly instead of omitting providers. Providers can announce local availability changes through the repository extension event `web/provider-availability-updated` (arguments: `search`/`fetch`, id); this is not an official DSH event. Status refreshes and execution also recheck `available()`. After saving migrated adapter ids, downgrading the manager requires reselecting the old routes; credential references and legacy endpoint keys remain stored.
|
|
79
|
-
|
|
80
|
-
From the repository root:
|
|
81
|
-
|
|
82
|
-
```sh
|
|
83
|
-
pnpm --filter @klarkxy/dsh-web-search-manager typecheck
|
|
84
|
-
pnpm --filter @klarkxy/dsh-web-search-manager test
|
|
85
|
-
pnpm --filter @klarkxy/dsh-web-search-manager build
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
Tests use mock credentials and responses and incur no search charges.
|
|
66
|
+
DSH has no public registry enumeration or dynamic selection API. This package isolates a compatibility bridge: it observes the actual host Maps, manages availability and temporarily takes over selection, then restores provider instances and prior config/environment selection on unload. Unsupported host structures fail explicitly instead of omitting providers. Providers can announce local availability changes through the extension event `web/provider-availability-updated` (arguments: `search`/`fetch`, id); this is not an official DSH event. Status refreshes and execution also recheck `available()`. After saving migrated adapter ids, downgrading the manager requires reselecting the old routes; credential references and legacy endpoint keys remain stored.
|
|
89
67
|
|
|
90
68
|
## Boundaries and limits
|
|
91
69
|
|
|
@@ -98,4 +76,4 @@ Tests use mock credentials and responses and incur no search charges.
|
|
|
98
76
|
- While the manager is attached, saved manager selection applies; unloading restores the host’s original config/environment selection.
|
|
99
77
|
- If saving the configuration fails, managed network access pauses until the settings are saved successfully.
|
|
100
78
|
|
|
101
|
-
[
|
|
79
|
+
[License](https://github.com/klarkxy/dsh-plugins/blob/main/plugins/dsh-web-search-manager/LICENSE)
|
package/docs/README.zh-CN.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
# @klarkxy/dsh-web-search-manager
|
|
1
|
+
# @klarkxy/dsh-web-search-manager
|
|
2
|
+
|
|
3
|
+
<!-- release-state-note -->
|
|
2
4
|
|
|
3
5
|
在一个设置页里挑选用哪个服务回答模型的网络搜索、设定每次请求的上限,并让模型读取公开网页。
|
|
4
6
|
|
|
5
7
|
[English](../README.md)
|
|
6
8
|
|
|
7
|
-
需要 Node.js ≥22
|
|
9
|
+
需要 Node.js ≥22(已验证 26)、DSH `0.2.0-rc.2` 或更高版本,无需构建本仓库。
|
|
8
10
|
|
|
9
11
|
## 安装
|
|
10
12
|
|
|
@@ -35,7 +37,7 @@ dsh plugin --profile web add @klarkxy/dsh-web-search-manager
|
|
|
35
37
|
- **博查**:中文网页搜索 API,需要 `DSH_EDITOR_WEB_BOCHA_API_KEY`。
|
|
36
38
|
- **Serper**:Google 结果 Search API,需要 `DSH_EDITOR_WEB_SERPER_API_KEY`。
|
|
37
39
|
- **Firecrawl**:网页搜索与提取 API,需要 `DSH_EDITOR_WEB_FIRECRAWL_API_KEY`。
|
|
38
|
-
- **Tavily**:Search API,固定 basic 检索深度,不自动升级;需要 `DSH_EDITOR_WEB_TAVILY_API_KEY
|
|
40
|
+
- **Tavily**:Search API,固定 basic 检索深度,不自动升级;需要 `DSH_EDITOR_WEB_TAVILY_API_KEY`。适配器已并入本插件。
|
|
39
41
|
|
|
40
42
|
## 读取公开网页
|
|
41
43
|
|
|
@@ -57,35 +59,11 @@ dsh plugin --profile web add @klarkxy/dsh-web-search-manager
|
|
|
57
59
|
|
|
58
60
|
加入后,模型获得 `@deepseek-ai/dsh-tool-web` 的官方 `web_search` 与 `web_fetch` 工具,仅在搜索与网页读取开启时挂载。
|
|
59
61
|
|
|
60
|
-
##
|
|
62
|
+
## 供应商接入
|
|
61
63
|
|
|
62
64
|
供应商插件直接调用 `ctx.web.registerSearchProvider(provider)` 或 `ctx.web.registerFetchProvider(provider)`,无需依赖或调用 `webSearchManager`。`available()` 只检查本地状态;请求须传递取消信号,供应商自行读取自己的配置和凭据。可在供应商对象上添加可选的 `dshWebManagement` 展示元数据(`label`、`description`、`billing`、`configurationOwner`、`credentialHint`、`pricing`、HTTPS `pricingUrl`、显式 `configurationUrl` 等)。`configurationUrl` 是唯一的配置链接,允许 HTTPS、同宿主 `/…` 路径或 `#…` 片段;不要期待本页根据 `id` 或 `configurationOwner` 猜测路由。`credentialHint` 只作展示。不提供元数据也会被发现,列表使用供应商 id。供应商自己的介绍和费用优先;相同 id 的静态说明不会覆盖自定义/原生服务。无费用元数据时显示未提供,不会称为免费或已核对。元数据不携带凭据值。旧的管理器 factory 注册方法保留为兼容入口,新插件走原生注册。
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
const provider = {
|
|
66
|
-
id: 'my-search',
|
|
67
|
-
dshWebManagement: {
|
|
68
|
-
id: 'my-search', label: 'My search', description: 'Custom search service',
|
|
69
|
-
billing: 'request', configurationOwner: '@example/my-plugin',
|
|
70
|
-
configurationUrl: '/plugins/my-plugin',
|
|
71
|
-
},
|
|
72
|
-
available: () => locallyConfigured(),
|
|
73
|
-
search: (request, signal) => searchWithOwnConfiguration(request, signal),
|
|
74
|
-
}
|
|
75
|
-
ctx.web.registerSearchProvider(provider)
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
DSH 0.2.0-rc.2 尚无公开注册表枚举及动态选择接口,本包将兼容逻辑集中在 `web-registry.ts`:观察宿主实际 Map、管理可用性、临时接管选择,卸载时恢复实例和原有配置/环境选择。不匹配的宿主结构会明确报错,避免遗漏已注册服务。供应商的本地可用性变化可发出本仓库扩展事件 `web/provider-availability-updated`(参数:`search`/`fetch`、id);它不是官方 DSH 事件。状态刷新和每次执行也会重新检查 `available()`。保存迁移后的适配器 id 后,降级管理器需要重新选择旧路由;凭据引用和旧端点键仍保留。
|
|
79
|
-
|
|
80
|
-
在仓库根运行:
|
|
81
|
-
|
|
82
|
-
```sh
|
|
83
|
-
pnpm --filter @klarkxy/dsh-web-search-manager typecheck
|
|
84
|
-
pnpm --filter @klarkxy/dsh-web-search-manager test
|
|
85
|
-
pnpm --filter @klarkxy/dsh-web-search-manager build
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
测试使用模拟凭据和响应,不产生搜索费用。
|
|
66
|
+
DSH 尚无公开注册表枚举及动态选择接口,本包将兼容逻辑集中在内部兼容层:观察宿主实际 Map、管理可用性、临时接管选择,卸载时恢复实例和原有配置/环境选择。不匹配的宿主结构会明确报错,避免遗漏已注册服务。供应商的本地可用性变化可发出本仓库扩展事件 `web/provider-availability-updated`(参数:`search`/`fetch`、id);它不是官方 DSH 事件。状态刷新和每次执行也会重新检查 `available()`。保存迁移后的适配器 id 后,降级管理器需要重新选择旧路由;凭据引用和旧端点键仍保留。
|
|
89
67
|
|
|
90
68
|
## 边界与限制
|
|
91
69
|
|
|
@@ -98,4 +76,4 @@ pnpm --filter @klarkxy/dsh-web-search-manager build
|
|
|
98
76
|
- 管理器运行期间使用已保存的选择;卸载后恢复宿主原有配置和环境变量的选择。
|
|
99
77
|
- 设置保存失败时,受管联网会暂停,成功保存后恢复。
|
|
100
78
|
|
|
101
|
-
[
|
|
79
|
+
[许可证](https://github.com/klarkxy/dsh-plugins/blob/main/plugins/dsh-web-search-manager/LICENSE)
|
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);
|
|
@@ -462,6 +503,58 @@ const providerInfo = {
|
|
|
462
503
|
pricingUrl: "https://docs.tavily.com/documentation/api-credits"
|
|
463
504
|
}
|
|
464
505
|
};
|
|
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;
|
|
557
|
+
}
|
|
465
558
|
function bundledCatalogId(provider) {
|
|
466
559
|
if (!(typeof provider.configurationOwned === "boolean" ? provider.configurationOwned : !provider.configurationOwner || provider.configurationOwner === "@klarkxy/dsh-web-search-manager")) return;
|
|
467
560
|
return provider.id === "deepseek-managed" ? "deepseek-official" : provider.id;
|
|
@@ -476,8 +569,13 @@ function usesBundledPricing(provider) {
|
|
|
476
569
|
}
|
|
477
570
|
function searchProviderInfo(provider, locale = "zh") {
|
|
478
571
|
const catalogId = bundledCatalogId(provider);
|
|
479
|
-
const
|
|
480
|
-
const
|
|
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;
|
|
481
579
|
if (provider.pricing?.trim()) return {
|
|
482
580
|
description,
|
|
483
581
|
pricing: provider.pricing,
|
|
@@ -783,6 +881,26 @@ const copy = {
|
|
|
783
881
|
unknownOwner: "Configure this service in its plugin"
|
|
784
882
|
}
|
|
785
883
|
};
|
|
884
|
+
var UiError = class extends Error {
|
|
885
|
+
key;
|
|
886
|
+
constructor(key) {
|
|
887
|
+
super(key);
|
|
888
|
+
this.key = key;
|
|
889
|
+
}
|
|
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
|
+
}
|
|
786
904
|
function movedText(locale, label, position) {
|
|
787
905
|
return locale === "zh" ? `${label} 已移至第 ${position} 位` : `${label} moved to position ${position}`;
|
|
788
906
|
}
|
|
@@ -968,7 +1086,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
968
1086
|
return next;
|
|
969
1087
|
} catch (cause) {
|
|
970
1088
|
if (!mountedRef.current || mine !== requestSeq.current) return;
|
|
971
|
-
setLoadError(cause instanceof Error && cause.message ? cause.message :
|
|
1089
|
+
setLoadError(cause instanceof Error && cause.message ? cause.message : { key: "failed" });
|
|
972
1090
|
if (source === "poll") return;
|
|
973
1091
|
throw cause;
|
|
974
1092
|
} finally {
|
|
@@ -979,7 +1097,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
979
1097
|
(0, react.useEffect)(() => {
|
|
980
1098
|
mountedRef.current = true;
|
|
981
1099
|
readGate.reset();
|
|
982
|
-
load().catch((cause) => setLoadError(cause instanceof Error && cause.message ? cause.message :
|
|
1100
|
+
load().catch((cause) => setLoadError(cause instanceof Error && cause.message ? cause.message : { key: "failed" }));
|
|
983
1101
|
const bind = () => bindStatusRefresh({
|
|
984
1102
|
isHidden: () => isPageHidden(hiddenRef.current),
|
|
985
1103
|
tick: async () => {
|
|
@@ -1034,7 +1152,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1034
1152
|
try {
|
|
1035
1153
|
await run();
|
|
1036
1154
|
} catch (cause) {
|
|
1037
|
-
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" });
|
|
1038
1156
|
await load().catch(() => {});
|
|
1039
1157
|
} finally {
|
|
1040
1158
|
busyRef.current = false;
|
|
@@ -1067,7 +1185,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1067
1185
|
const snapshot = keys[provider.id] ?? "";
|
|
1068
1186
|
const typed = snapshot.trim();
|
|
1069
1187
|
if (!ownsProviderConfiguration(provider) || !provider.credentialRef || !typed) continue;
|
|
1070
|
-
if (writable[provider.credentialRef] === false) throw new
|
|
1188
|
+
if (writable[provider.credentialRef] === false) throw new UiError("readOnly");
|
|
1071
1189
|
unwrap(await client.remote.credentials.set(provider.credentialRef, typed));
|
|
1072
1190
|
setKeys((current) => current[provider.id] === snapshot ? {
|
|
1073
1191
|
...current,
|
|
@@ -1083,14 +1201,14 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1083
1201
|
[provider.id]: ""
|
|
1084
1202
|
}));
|
|
1085
1203
|
await load();
|
|
1086
|
-
setNote(
|
|
1204
|
+
setNote({ key: "keyDeleted" });
|
|
1087
1205
|
}
|
|
1088
1206
|
if (!status || !limitDraft) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1089
1207
|
className: "web-search-settings ws-root dsh-ui-panel",
|
|
1090
1208
|
children: [loadError ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1091
1209
|
role: "alert",
|
|
1092
1210
|
className: "dsh-ui-error",
|
|
1093
|
-
children: [text.loadFailed, loadError]
|
|
1211
|
+
children: [text.loadFailed, uiMessage(loadError, locale, status?.providers)]
|
|
1094
1212
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1095
1213
|
role: "status",
|
|
1096
1214
|
className: "dsh-ui-hint",
|
|
@@ -1129,7 +1247,10 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1129
1247
|
pendingRef.current = next;
|
|
1130
1248
|
setPendingOrder(next);
|
|
1131
1249
|
if (keyboard) refocusRef.current = id;
|
|
1132
|
-
setSortAnnouncement(
|
|
1250
|
+
setSortAnnouncement({
|
|
1251
|
+
moved: id,
|
|
1252
|
+
position: nextIndex + 1
|
|
1253
|
+
});
|
|
1133
1254
|
clearTimeout(timerRef.current);
|
|
1134
1255
|
timerRef.current = setTimeout(() => {
|
|
1135
1256
|
timerRef.current = void 0;
|
|
@@ -1150,13 +1271,13 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1150
1271
|
const value = parseLimit(limitDraft[key], min, max);
|
|
1151
1272
|
if (value === void 0) {
|
|
1152
1273
|
nextInvalid[key] = true;
|
|
1153
|
-
messages.push(
|
|
1274
|
+
messages.push(key);
|
|
1154
1275
|
} else parsed[key] = value;
|
|
1155
1276
|
}
|
|
1156
1277
|
setInvalid(nextInvalid);
|
|
1157
1278
|
if (messages.length) {
|
|
1158
1279
|
setNote("");
|
|
1159
|
-
setError(messages
|
|
1280
|
+
setError({ invalid: messages });
|
|
1160
1281
|
return;
|
|
1161
1282
|
}
|
|
1162
1283
|
action(async () => {
|
|
@@ -1170,7 +1291,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1170
1291
|
expectedRevision: current.settings.revision
|
|
1171
1292
|
}), true);
|
|
1172
1293
|
await load();
|
|
1173
|
-
setNote(
|
|
1294
|
+
setNote({ key: "saved" });
|
|
1174
1295
|
});
|
|
1175
1296
|
}
|
|
1176
1297
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
@@ -1197,19 +1318,19 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1197
1318
|
className: "ws-sr-only",
|
|
1198
1319
|
role: "status",
|
|
1199
1320
|
"aria-live": "polite",
|
|
1200
|
-
children: sortAnnouncement
|
|
1321
|
+
children: uiMessage(sortAnnouncement, locale, saved.providers)
|
|
1201
1322
|
}),
|
|
1202
1323
|
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1203
1324
|
role: "alert",
|
|
1204
1325
|
className: "dsh-ui-error",
|
|
1205
|
-
children: error
|
|
1326
|
+
children: uiMessage(error, locale, saved.providers)
|
|
1206
1327
|
}),
|
|
1207
1328
|
loadError ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1208
1329
|
className: "dsh-ui-stack",
|
|
1209
1330
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1210
1331
|
role: "alert",
|
|
1211
1332
|
className: "dsh-ui-error",
|
|
1212
|
-
children: [text.loadFailed, loadError]
|
|
1333
|
+
children: [text.loadFailed, uiMessage(loadError, locale, status?.providers)]
|
|
1213
1334
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1214
1335
|
variant: "outline",
|
|
1215
1336
|
size: "sm",
|
|
@@ -1223,7 +1344,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1223
1344
|
note && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1224
1345
|
role: "status",
|
|
1225
1346
|
className: "dsh-ui-notice",
|
|
1226
|
-
children: note
|
|
1347
|
+
children: uiMessage(note, locale, saved.providers)
|
|
1227
1348
|
}),
|
|
1228
1349
|
saved.storageFailed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1229
1350
|
role: "alert",
|
|
@@ -1349,7 +1470,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1349
1470
|
}),
|
|
1350
1471
|
draggable: !busy,
|
|
1351
1472
|
"aria-disabled": busy,
|
|
1352
|
-
"aria-label": text.dragPrefix + provider
|
|
1473
|
+
"aria-label": text.dragPrefix + providerDisplayLabel(provider, locale),
|
|
1353
1474
|
title: text.dragHint,
|
|
1354
1475
|
onDragStart: (event) => {
|
|
1355
1476
|
if (busy) {
|
|
@@ -1381,7 +1502,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1381
1502
|
children: [
|
|
1382
1503
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
|
|
1383
1504
|
className: "dsh-ui-heading",
|
|
1384
|
-
children: provider
|
|
1505
|
+
children: providerDisplayLabel(provider, locale)
|
|
1385
1506
|
}),
|
|
1386
1507
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1387
1508
|
className: "ws-provider-note",
|
|
@@ -1393,7 +1514,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1393
1514
|
}),
|
|
1394
1515
|
provider.signupUrl && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppLink, {
|
|
1395
1516
|
url: provider.signupUrl,
|
|
1396
|
-
label: provider
|
|
1517
|
+
label: providerDisplayLabel(provider, locale) + " " + text.signup,
|
|
1397
1518
|
children: text.signup
|
|
1398
1519
|
})
|
|
1399
1520
|
]
|
|
@@ -1402,7 +1523,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1402
1523
|
className: "web-search-provider-info",
|
|
1403
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, {
|
|
1404
1525
|
url: info.pricingUrl,
|
|
1405
|
-
label: provider
|
|
1526
|
+
label: providerDisplayLabel(provider, locale) + " " + text.pricing,
|
|
1406
1527
|
children: provider.id === "ddg" ? text.website : text.pricing
|
|
1407
1528
|
})] })] })]
|
|
1408
1529
|
}),
|
|
@@ -1422,7 +1543,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1422
1543
|
}) : null,
|
|
1423
1544
|
shared && participating && owned ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", {
|
|
1424
1545
|
className: "dsh-ui-meta",
|
|
1425
|
-
children: provider.credentialHint ?? text.sharedKey
|
|
1546
|
+
children: provider.credentialHint === "与模型设置共用 Key" ? locale === "zh" ? provider.credentialHint : "Shares a key with model settings" : provider.credentialHint ?? text.sharedKey
|
|
1426
1547
|
}) : null,
|
|
1427
1548
|
showKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1428
1549
|
className: "dsh-ui-field",
|
|
@@ -1452,7 +1573,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1452
1573
|
onClick: () => void action(async () => {
|
|
1453
1574
|
await saveKeys(provider);
|
|
1454
1575
|
await load();
|
|
1455
|
-
setNote(
|
|
1576
|
+
setNote({ key: "keySaved" });
|
|
1456
1577
|
}),
|
|
1457
1578
|
children: text.saveKey
|
|
1458
1579
|
}) : null, needsKey && !shared && provider.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmButton, {
|
|
@@ -1468,7 +1589,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1468
1589
|
className: "web-search-rank-move",
|
|
1469
1590
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
1470
1591
|
checked: participating,
|
|
1471
|
-
label: provider
|
|
1592
|
+
label: providerDisplayLabel(provider, locale),
|
|
1472
1593
|
disabled: busy,
|
|
1473
1594
|
onChange: (next) => {
|
|
1474
1595
|
const following = next ? [...order, provider.id] : order.filter((id) => id !== provider.id);
|
|
@@ -1494,7 +1615,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1494
1615
|
await saveKeys();
|
|
1495
1616
|
const result = await call("test");
|
|
1496
1617
|
await load();
|
|
1497
|
-
setNote(
|
|
1618
|
+
setNote({ sources: result.sources });
|
|
1498
1619
|
}),
|
|
1499
1620
|
children: text.test
|
|
1500
1621
|
})
|
|
@@ -1577,7 +1698,7 @@ function NetworkSearchSettings({ client, hidden }) {
|
|
|
1577
1698
|
]
|
|
1578
1699
|
}), saved.providers.filter((provider) => provider.kind === "fetch").map((provider) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
1579
1700
|
value: provider.id,
|
|
1580
|
-
children: [provider
|
|
1701
|
+
children: [providerDisplayLabel(provider, locale), provider.configured ? "" : " · " + text.unavailable]
|
|
1581
1702
|
}, provider.id))]
|
|
1582
1703
|
})]
|
|
1583
1704
|
}),
|