@klarkxy/dsh-web-search-manager 0.1.2 → 0.1.4

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 CHANGED
@@ -1,90 +1,42 @@
1
1
  # @klarkxy/dsh-web-search-manager
2
2
 
3
- Manage web search providers, credentials, permissions, and request lifecycles through DSH's existing `ctx.web` service. Includes a settings UI and an optional agent tool entry point.
3
+ Configure DSH web search providers and page fetching from one settings page.
4
4
 
5
5
  [简体中文](https://github.com/klarkxy/dsh-editor/blob/main/packages/dsh-web-search-manager/docs/README.zh-CN.md)
6
6
 
7
- ## Install in DSH Web
7
+ ## Install
8
8
 
9
- Requires Node.js 22 or later and a DSH `0.1.5-rc.2` Web profile.
9
+ Requires Node.js ≥22 and DSH `0.1.7-alpha.1`. No repository build is needed.
10
10
 
11
11
  ```sh
12
12
  dsh plugin --profile web add @klarkxy/dsh-web-search-manager
13
13
  ```
14
14
 
15
- Restart `dsh web`, then open **Settings → Web search (设置 → 网络搜索)**. To expose search tools to an agent, add this entry to the plugin list in its `agent.cordis.yml`:
15
+ Restart DSH Web and open **Settings → Web search**. For agent access, add this entry to the plugin list in its `agent.cordis.yml`:
16
16
 
17
17
  ```yaml
18
18
  - name: '@klarkxy/dsh-web-search-manager/tools'
19
19
  ```
20
20
 
21
- The package includes the compiled host plugin, Web client, type declarations, and `cordis.patch.yml`. No repository build is needed for installation. DSH Editor's desktop composition already includes this plugin and its Tavily adapter.
21
+ DSH Editor already includes the plugin, its tools, and the Tavily adapter.
22
22
 
23
- ## Choose a provider
23
+ ## Configure search
24
24
 
25
- DuckDuckGo is enabled by default and requires no API key. Brave, Bocha, Serper, Firecrawl, Exa, DeepSeek, and Tavily are also supported; enable the provider you want and configure its credentials in the settings page. DeepSeek reuses the API key from the model settings and may incur additional search charges.
25
+ DuckDuckGo is enabled by default and requires no key. Enable another provider and configure credentials in settings if needed; one ready provider is enough. DeepSeek shares the key from model settings and may incur additional search charges.
26
26
 
27
- One enabled provider is enough. The manager selects the first ready provider by priority, skipping providers with missing credentials. A failed request does not trigger a retry with another provider, including on authentication or quota errors.
27
+ The first ready provider by priority is used. Missing credentials are skipped; failed requests do not automatically switch providers. Enabling search also enables public-page fetching. A connection test sends a fixed query, not manuscript content, and may incur a provider charge.
28
28
 
29
- Installing [@klarkxy/dsh-zhihu](https://www.npmjs.com/package/@klarkxy/dsh-zhihu) in the same profile adds a **Zhihu global search (知乎全网搜索)** backend. It shares the access token configured in the Zhihu settings.
29
+ With [@klarkxy/dsh-zhihu](https://www.npmjs.com/package/@klarkxy/dsh-zhihu) installed, **Zhihu global search** shares the Access Secret from Zhihu settings.
30
30
 
31
- Enabling search also enables public-page fetching through the local HTTP adapter. Agents use the standard `web_search` tool. Connection tests run a fixed query only when requested by the user; they may incur a provider charge and do not send manuscript content.
31
+ Only configure trusted HTTPS endpoints: API keys are sent to them. Credentials use DSH storage; protection at rest depends on its configured backend. Usage counters are call attempts, not billing statements or a spending cap.
32
32
 
33
- Provider endpoints can be customized to trusted HTTPS URLs. API keys are sent to the configured endpoint.
33
+ Disabling a managed provider cancels its requests, but does not sandbox HTTP calls made directly by other plugins. Search results are external material and cannot authorize changes to manuscripts.
34
34
 
35
- ## Runtime behavior
35
+ If deployment environment overrides conflict with the settings selection, remove the conflicting overrides. If saving configuration fails, managed network access pauses until settings are successfully saved.
36
36
 
37
- The `@klarkxy/dsh-web-search-manager/tools` entry mounts the official `dsh-tool-web` tools in the session context according to the current authorization state. Disabling a provider, changing its configuration, or uninstalling it cancels its in-flight managed requests.
37
+ ## Extend or develop
38
38
 
39
- Other plugins continue to use the standard DSH API:
40
-
41
- ```ts
42
- const result = await ctx.web.search({ query: 'example', maxResults: 5 }, signal)
43
- const page = await ctx.web.fetch({ url: 'https://example.com' }, signal)
44
- ```
45
-
46
- DeepSeek uses `@deepseek-ai/dsh-web-search-deepseek` and its Anthropic-compatible `web_search` support. Exa uses the official `ExaSearchProvider`. Page fetching uses `HttpFetchProvider`, including its public-address checks, DNS pinning, and redirect restrictions.
47
-
48
- The bundled Tavily adapter retains provider ID `tavily`, credential reference `DSH_EDITOR_WEB_TAVILY_API_KEY`, and existing priority and endpoint settings. It uses basic search, does not upgrade search depth automatically, does not retry billable requests, and does not forward keys through redirects. A separate Tavily plugin is not required.
49
-
50
- ## Add a provider
51
-
52
- A plugin can inject `webSearchManager` and register a provider descriptor and factory. The manager registers an authorized standard `WebSearchProvider` with the existing `ctx.web` service; DSH retains provider selection and the search result contract.
53
-
54
- The following sketch assumes you have implemented `ExampleProvider`:
55
-
56
- ```ts
57
- export const inject = ['webSearchManager']
58
-
59
- export function apply(ctx) {
60
- ctx.effect(() => ctx.webSearchManager.registerSearchProvider({
61
- id: 'example',
62
- label: 'Example Search',
63
- description: 'Independent REST search',
64
- defaultBaseURL: 'https://search.example.com',
65
- credentialRef: 'DSH_EDITOR_WEB_EXAMPLE_API_KEY',
66
- billing: 'request',
67
- }, options => new ExampleProvider(options)))
68
- }
69
- ```
70
-
71
- The factory accepts `ProviderOptions` and returns a standard `WebSearchProvider`. Providers must forward cancellation signals. Their `available()` method checks local state only; it must not make a network request. Providers that do not require a key can omit the credential reference.
72
-
73
- Use `registerFetchProvider` to register a page-fetching provider. The built-in Tavily adapter in `src/tavily.ts` is a concrete search-provider example.
74
-
75
- Standalone DSH deployments need the `/tools` entry shown above in each relevant `agent.cordis.yml`. DSH Editor's composition build adds it to bundled writing presets when this feature is selected.
76
-
77
- ## Credentials and authorization
78
-
79
- - Settings use the host's authentication and origin checks. Saves are serialized and checked for revision conflicts. Provider configuration does not contain secrets.
80
- - Credentials are stored through the existing DSH host credential service and resolved for each request. They are not copied into environment variables or model context. Protection at rest depends on the DSH credential backend; this plugin does not guarantee operating-system keychain encryption.
81
- - API key inputs are not saved to browser local storage. The UI disables search before deleting a key. If saving configuration fails, managed network access is paused for the current host process.
82
- - Global `ctx.web` environment overrides can conflict with the settings page's provider selection. DSH rejects the call in that case; remove the conflicting deployment override to use the settings selection.
83
- - Disabling managed providers controls requests made through this registration API. It does not sandbox arbitrary HTTP calls made directly by other plugins.
84
- - Search results and fetched pages are untrusted external material. They do not authorize instructions embedded in a page or change the application's author-confirmation requirements.
85
- - Query count, result count, timeouts, and response length are bounded. These limits are not a monetary budget. Runtime counters track adapter call attempts without retaining queries or page bodies; they are not provider billing statements. Provider-specific extensions must supply exact billing or model-native search accounting where needed.
86
-
87
- ## Development
39
+ Use DSH's existing `ctx.web` service. For a provider extension, see [manager contracts](https://github.com/klarkxy/dsh-editor/blob/main/packages/dsh-web-search-manager/src/contracts.ts), [built-in registration](https://github.com/klarkxy/dsh-editor/blob/main/packages/dsh-web-search-manager/src/builtins.ts), and the [Tavily adapter](https://github.com/klarkxy/dsh-editor/blob/main/packages/dsh-web-search-manager/src/tavily.ts). Forward cancellation signals; `available()` must check local state without a network request.
88
40
 
89
41
  From the repository root:
90
42
 
@@ -94,6 +46,6 @@ pnpm exec vitest run packages/dsh-web-search-manager/src
94
46
  pnpm --filter @klarkxy/dsh-web-search-manager build
95
47
  ```
96
48
 
97
- Tests use mock credentials and responses and do not incur real search charges.
49
+ Tests use mock credentials and responses and incur no search charges.
98
50
 
99
- See the Chinese [user guide](https://github.com/klarkxy/dsh-editor/blob/main/docs/user-guide.md) and [publishing and marketplace guide](https://github.com/klarkxy/dsh-editor/blob/main/docs/plugin-distribution.md) for repository documentation. Package license: [LICENSE](https://github.com/klarkxy/dsh-editor/blob/main/packages/dsh-web-search-manager/LICENSE).
51
+ [Publishing](https://github.com/klarkxy/dsh-editor/blob/main/packages/PUBLISHING.md) · [License](https://github.com/klarkxy/dsh-editor/blob/main/packages/dsh-web-search-manager/LICENSE)
@@ -1,70 +1,44 @@
1
1
  # @klarkxy/dsh-web-search-manager
2
2
 
3
- [English](https://github.com/klarkxy/dsh-editor/blob/main/packages/dsh-web-search-manager/README.md)
3
+ 在一个设置页中配置 DSH 网络搜索与网页读取。
4
4
 
5
- DSH `0.1.5-rc.2` 的独立网络搜索管理插件。提供设置页、明确授权、供应商管理描述和生命周期控制;不实现第二套搜索执行服务。
5
+ [English](https://github.com/klarkxy/dsh-editor/blob/main/packages/dsh-web-search-manager/README.md)
6
6
 
7
- ## 安装到 DSH Web
7
+ ## 安装
8
8
 
9
- 需要 Node.js 22 或更高版本,以及 DSH `0.1.5-rc.2` 的 Web profile。npm 包名是 `@klarkxy/dsh-web-search-manager`(`web-search` 之间有连字符)。
9
+ 需要 Node.js ≥22、DSH `0.1.7-alpha.1`,无需构建本仓库。
10
10
 
11
11
  ```sh
12
12
  dsh plugin --profile web add @klarkxy/dsh-web-search-manager
13
13
  ```
14
14
 
15
- 安装后重启 `dsh web`,进入 **设置 → 网络搜索**。模型需要使用搜索工具时,还需在所用 `agent.cordis.yml` 的插件列表中加入:
15
+ 重启 DSH Web,打开「设置 → 网络搜索」。模型需要使用搜索工具时,在所用 `agent.cordis.yml` 的插件列表加入:
16
16
 
17
17
  ```yaml
18
- - name: "@klarkxy/dsh-web-search-manager/tools"
18
+ - name: '@klarkxy/dsh-web-search-manager/tools'
19
19
  ```
20
20
 
21
- 包内附带编译后的 Host、Web 客户端、类型声明与 `cordis.patch.yml`,安装时无需克隆或构建本仓库。发布状态与市场收录见[插件发布与发现](https://github.com/klarkxy/dsh-editor/blob/main/docs/plugin-distribution.md)。
21
+ DSH Editor 已内置此插件、模型工具及 Tavily 适配器。
22
22
 
23
- ## 使用
23
+ ## 配置搜索
24
24
 
25
- 桌面默认组合安装此插件,Tavily 与其它搜索供应商一同内置。进入 **设置 → 网络搜索**:只需开启一个后端。默认只打开 DuckDuckGo;Brave、博查、Serper、Firecrawl、Exa、DeepSeek、Tavily 需自行注册(各有注册链接)。同一 profile 若还装了知乎资料,会多出一个「知乎全网搜索」后端,与知乎设置共用 Access Secret。打开后按优先级选用第一个已就绪的后端;缺 Key 的跳过,单次失败不换家。模型只看到官方 `web_search`。打开搜索时会同时挂上本机公开网页读取。DeepSeek 搜索复用模型设置中的 API Key,并可能产生额外搜索费用。测试连接是用户主动触发的固定查询,可能产生一次供应商调用费用,不发送作品内容。
25
+ 默认开启 DuckDuckGo,无需密钥。需要其它后端时在设置中开启并填写凭据;一个可用后端即可。DeepSeek 共用模型设置中的密钥,可能产生额外搜索费用。
26
26
 
27
- DeepSeek 搜索复用 `@deepseek-ai/dsh-web-search-deepseek`(Anthropic 兼容 `web_search`),Exa 复用官方 `ExaSearchProvider`,HTTP 读取复用 `HttpFetchProvider` 的公开地址、DNS 固定和重定向安全策略。DuckDuckGo 无需 Key;其它供应商不会在鉴权或额度失败时自动切换。供应商可配置可信 HTTPS API 端点,密钥将发送到该端点。
27
+ 按优先级使用第一个就绪后端,缺密钥的跳过,失败不自动换家。开启搜索也会启用公开网页读取。连接测试发送固定查询,不发送作品内容,可能产生一次调用费用。
28
28
 
29
- 模型工具由 `@klarkxy/dsh-web-search-manager/tools` 在会话上下文内按授权状态挂载官方 `dsh-tool-web`。关闭、切换配置或卸载供应商会取消正在进行的受管请求。其他业务插件继续使用:
29
+ 安装 [@klarkxy/dsh-zhihu](https://www.npmjs.com/package/@klarkxy/dsh-zhihu) 后,「知乎全网搜索」共用知乎设置中的 Access Secret。
30
30
 
31
- ```ts
32
- const result = await ctx.web.search({ query: 'example', maxResults: 5 }, signal)
33
- const page = await ctx.web.fetch({ url: 'https://example.com' }, signal)
34
- ```
31
+ 只配置可信 HTTPS 端点,密钥会发送到该地址。凭据由 DSH 保存,落盘保护取决于其后端配置。用量计数是调用尝试,不等于账单或消费上限。
35
32
 
36
- Tavily 沿用凭据引用 `DSH_EDITOR_WEB_TAVILY_API_KEY`、供应商 ID `tavily` 与原有排序和端点配置。使用 basic 搜索,不自动升级深度、不重试计费请求、不跟随重定向转发密钥。无需额外安装 Tavily 插件。
33
+ 关闭受管后端会取消其请求,但不能阻止其它插件自行联网。搜索结果是外部资料,不授权修改稿件。
37
34
 
38
- ## 扩展供应商
35
+ 若部署环境的供应商覆盖与设置页冲突,移除冲突的覆盖配置。设置保存失败时,受管联网会暂停,成功保存后恢复。
39
36
 
40
- 独立插件注入 `webSearchManager`,使用管理注册接口贡献描述和工厂。管理层把受授权保护的标准 `WebSearchProvider` 注册到 **现有 `ctx.web`**;供应商选择和结果协议仍归 DSH 管理。
41
-
42
- ```ts
43
- export const inject = ['webSearchManager']
44
- export function apply(ctx) {
45
- ctx.effect(() => ctx.webSearchManager.registerSearchProvider({
46
- id: 'example', label: 'Example Search', description: 'Independent REST search',
47
- defaultBaseURL: 'https://search.example.com',
48
- credentialRef: 'DSH_EDITOR_WEB_EXAMPLE_API_KEY', billing: 'request',
49
- }, options => new ExampleProvider(options)))
50
- }
51
- ```
37
+ ## 扩展与开发
52
38
 
53
- 工厂遵循 `ProviderOptions`,返回标准 `WebSearchProvider`,必须传递取消信号。`available()` 只检查本地状态,不进行联网测试。无 Key 的搜索后端可以不声明凭据引用。纯网页读取供应商可通过 `registerFetchProvider` 注册;Editor 在启用搜索时同时挂上本机 HTTP 读取。内置 Tavily 适配器位于 `src/tavily.ts`。
39
+ 复用 DSH 的 `ctx.web`。扩展供应商可参照[管理接口](https://github.com/klarkxy/dsh-editor/blob/main/packages/dsh-web-search-manager/src/contracts.ts)、[内置注册](https://github.com/klarkxy/dsh-editor/blob/main/packages/dsh-web-search-manager/src/builtins.ts)和 [Tavily 适配器](https://github.com/klarkxy/dsh-editor/blob/main/packages/dsh-web-search-manager/src/tavily.ts)。须传递取消信号;`available()` 只检查本地状态,不发送网络请求。
54
40
 
55
- 独立 DSH 部署还需在所用的 `agent.cordis.yml` 加入 `name: "@klarkxy/dsh-web-search-manager/tools"`。Editor 的组合构建会为已选择该能力的应用自带写作预设加入这一入口;未选择该插件的组合不会产生悬空引用。
56
-
57
- ## 边界与安全
58
-
59
- - 设置通过 Host 的认证/来源检查,按版本号串行保存,配置不包含密钥。凭据写入现有 Host 凭据服务,每次请求重新解析,不复制到环境变量或模型上下文。现有凭据后端的落盘保护取决于 DSH 配置,本插件不宣称操作系统密钥库加密。
60
- - 默认只开启 DuckDuckGo。其它后端须手动打开并填写 Key。只需开启一个。打开后按优先级使用第一个已就绪的后端;缺 Key 的跳过。单次请求失败不会自动换下一家。
61
- - 全局 `ctx.web` 的环境供应商选择覆盖可能与管理页选择冲突,此时 DSH 会拒绝调用,不进行隐藏回退。移除这类部署覆盖再使用设置页选择供应商。
62
- - 管理接口的关闭操作约束受管供应商,不是任意第三方插件 HTTP 请求的网络沙箱。自行绕过管理注册直接发 HTTP 的插件不在此授权边界内。
63
- - API Key 输入不落本地存储。界面删除前先停用搜索;并发设置有 revision 冲突检查。配置保存失败时,本次 Host 运行暂停受管网络访问。
64
- - 网页和搜索结果是外部不可信资料,不授权模型执行网页内指令,也不改变写作提案和作者确认边界。
65
- - 查询数量、结果数、超时及正文长度有边界,但不是美元预算上限。当前运行的计数只统计适配器调用尝试,不保存查询或正文,不声称等于供应商账单。模型原生搜索的内部计费与精确账单仍应由对应扩展另行提供。
66
-
67
- ## 验证
41
+ 在仓库根运行:
68
42
 
69
43
  ```sh
70
44
  pnpm --filter @klarkxy/dsh-web-search-manager typecheck
@@ -72,4 +46,6 @@ pnpm exec vitest run packages/dsh-web-search-manager/src
72
46
  pnpm --filter @klarkxy/dsh-web-search-manager build
73
47
  ```
74
48
 
75
- 测试使用模拟 Key 和响应,不产生真实搜索费用。
49
+ 测试使用模拟凭据和响应,不产生搜索费用。
50
+
51
+ [发布维护](https://github.com/klarkxy/dsh-editor/blob/main/packages/PUBLISHING.md) · [许可证](https://github.com/klarkxy/dsh-editor/blob/main/packages/dsh-web-search-manager/LICENSE)
@@ -130,6 +130,31 @@ function canEnableSearch(order, providers, keys, writable) {
130
130
  function nextSearchEnabled(currentlyEnabled, nextOrder, providers, keys, writable) {
131
131
  return currentlyEnabled && canEnableSearch(nextOrder, providers, keys, writable);
132
132
  }
133
+ function ConfirmButton(props) {
134
+ const [armed, setArmed] = (0, react.useState)(false);
135
+ const timer = (0, react.useRef)(void 0);
136
+ (0, react.useEffect)(() => () => clearTimeout(timer.current), []);
137
+ function disarm() {
138
+ clearTimeout(timer.current);
139
+ setArmed(false);
140
+ }
141
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
142
+ type: "button",
143
+ className: "web-search-danger",
144
+ disabled: props.disabled,
145
+ onClick: () => {
146
+ if (!armed) {
147
+ setArmed(true);
148
+ timer.current = setTimeout(() => setArmed(false), 3e3);
149
+ return;
150
+ }
151
+ disarm();
152
+ props.onConfirm();
153
+ },
154
+ onBlur: disarm,
155
+ children: armed ? props.confirmLabel : props.label
156
+ });
157
+ }
133
158
  function NetworkSearchSettings({ client }) {
134
159
  const tabsId = (0, react.useId)();
135
160
  const [tab, setTab] = (0, react.useState)("providers");
@@ -196,12 +221,16 @@ function NetworkSearchSettings({ client }) {
196
221
  [provider.id]: ""
197
222
  } : current);
198
223
  }
224
+ const bounded = (key) => {
225
+ const row = limits.find((item) => item[0] === key);
226
+ return Math.min(row[3], Math.max(row[2], Number.isFinite(draft[key]) ? draft[key] : row[2]));
227
+ };
199
228
  await update({
200
229
  ...editable(status.settings),
201
- maxResults: draft.maxResults,
202
- maxQueries: draft.maxQueries,
203
- timeoutMs: draft.timeoutMs,
204
- maxFetchChars: draft.maxFetchChars,
230
+ maxResults: bounded("maxResults"),
231
+ maxQueries: bounded("maxQueries"),
232
+ timeoutMs: bounded("timeoutMs"),
233
+ maxFetchChars: bounded("maxFetchChars"),
205
234
  endpoints: draft.endpoints,
206
235
  searchOrder: order,
207
236
  searchEnabled: on,
@@ -225,13 +254,13 @@ function NetworkSearchSettings({ client }) {
225
254
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
226
255
  role: "status",
227
256
  children: error || "正在读取设置…"
228
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
257
+ }), error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
229
258
  type: "button",
230
259
  onClick: () => void action(async () => {
231
260
  await load();
232
261
  }),
233
262
  children: "重新连接"
234
- })]
263
+ }) : null]
235
264
  });
236
265
  const enabled = searchBackends(status);
237
266
  const backends = catalogSearchBackends(status);
@@ -507,11 +536,11 @@ function NetworkSearchSettings({ client }) {
507
536
  [provider.id]: event.target.value
508
537
  }))
509
538
  })] }) : null,
510
- needsKey && !shared && provider.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
511
- type: "button",
539
+ needsKey && !shared && provider.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmButton, {
512
540
  disabled: busy || writable[provider.credentialRef ?? ""] === false,
513
- onClick: () => void action(() => removeKey(provider)),
514
- children: "删除 Key"
541
+ label: "删除 Key",
542
+ confirmLabel: "确认删除 Key?",
543
+ onConfirm: () => void action(() => removeKey(provider))
515
544
  }) : null
516
545
  ]
517
546
  }),
@@ -603,7 +632,7 @@ const styles = `
603
632
  .web-search-settings p{margin:0;line-height:1.5}
604
633
  .web-search-settings small,.web-search-meta,.web-search-empty{font-size:var(--font-size-1,13px);color:var(--gray-11,inherit)}
605
634
  .web-search-error{color:var(--red-11,#b42318)}
606
- .web-search-card{display:grid;gap:12px}
635
+ .web-search-card{display:grid;gap:12px;padding:16px;border:1px solid var(--gray-6,color-mix(in srgb,currentColor 15%,transparent));border-radius:12px}
607
636
  .web-search-card header{display:flex;justify-content:space-between;gap:12px;align-items:center;padding-bottom:8px}
608
637
  .web-search-card h3{margin:0;font-size:var(--font-size-3,16px);font-weight:600}
609
638
  .web-search-provider{display:grid;gap:8px;min-width:0}
@@ -611,18 +640,24 @@ const styles = `
611
640
  .web-search-provider-heading{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;min-height:24px}
612
641
  .web-search-provider-heading strong{font-weight:500}
613
642
  .web-search-card label{display:grid;gap:6px}
614
- .web-search-settings input{box-sizing:border-box;width:100%;min-width:0;padding:8px 10px;border:1px solid var(--gray-6,color-mix(in srgb,currentColor 22%,transparent));border-radius:6px;background:var(--color-surface,transparent);color:inherit;font:inherit}
615
- .web-search-settings button:not([role="switch"]){min-height:34px;padding:6px 12px;border:1px solid color-mix(in srgb,currentColor 25%,transparent);border-radius:6px;background:transparent;color:inherit;cursor:pointer;font:inherit;justify-self:start}
643
+ .web-search-settings input{box-sizing:border-box;width:100%;min-width:0;padding:8px 10px;border:1px solid var(--gray-6,color-mix(in srgb,currentColor 22%,transparent));border-radius:8px;background:var(--color-surface,transparent);color:inherit;font:inherit;transition:border-color 150ms ease,box-shadow 150ms ease}
644
+ .web-search-settings input:focus{border-color:var(--accent-9,#3b82f6);box-shadow:0 0 0 2px color-mix(in srgb,var(--accent-9,#3b82f6) 25%,transparent)}
645
+ .web-search-settings button:not([role="switch"]){min-height:34px;padding:6px 12px;border:1px solid color-mix(in srgb,currentColor 25%,transparent);border-radius:8px;background:transparent;color:inherit;cursor:pointer;font:inherit;justify-self:start;transition:background-color 150ms ease,color 150ms ease,border-color 150ms ease,box-shadow 150ms ease,transform 150ms ease}
646
+ .web-search-settings button:not([role="switch"]):not([role="tab"]):not(.web-search-drag-handle):hover:not(:disabled){background:var(--gray-3,color-mix(in srgb,currentColor 6%,transparent));border-color:color-mix(in srgb,currentColor 35%,transparent)}
647
+ .web-search-settings button:not([role="switch"]):not([role="tab"]):not(.web-search-drag-handle):active:not(:disabled){transform:scale(.97)}
648
+ .web-search-danger:hover:not(:disabled){border-color:var(--red-11,#b42318);color:var(--red-11,#b42318);background:color-mix(in srgb,var(--red-11,#b42318) 8%,transparent)}
616
649
  .web-search-settings button:disabled{opacity:.45;cursor:not-allowed}
617
650
  .web-search-tabs{display:flex;gap:20px;border-bottom:1px solid var(--gray-6,color-mix(in srgb,currentColor 15%,transparent))}
618
651
  .web-search-tabs button[role="tab"]{padding:8px 0;border:0;border-bottom:2px solid transparent;border-radius:0;color:var(--gray-11,inherit)}
652
+ .web-search-tabs button[role="tab"]:hover{color:inherit}
619
653
  .web-search-tabs button[aria-selected="true"]{border-bottom-color:var(--accent-9,#3b82f6);color:var(--accent-11,inherit);font-weight:600}
654
+ .web-search-settings [role="tabpanel"]{border-radius:12px}
620
655
  .web-search-settings .web-search-switch{all:unset;box-sizing:border-box;position:relative;display:inline-block;width:36px;height:20px;flex:none;border-radius:999px;background:var(--gray-7,color-mix(in srgb,currentColor 28%,transparent));cursor:pointer}
621
656
  .web-search-switch.is-on{background:var(--accent-9,#3b82f6)}
622
657
  .web-search-switch:disabled{cursor:not-allowed;opacity:.7}
623
658
  .web-search-switch-thumb{position:absolute;top:3px;left:3px;width:14px;height:14px;border-radius:999px;background:#fff;transition:transform 150ms ease}
624
659
  .web-search-switch.is-on .web-search-switch-thumb{transform:translateX(16px)}
625
- .web-search-settings :focus-visible{outline:2px solid currentColor;outline-offset:3px}
660
+ .web-search-settings :focus-visible{outline:2px solid var(--accent-9,currentColor);outline-offset:3px}
626
661
  .web-search-card-actions{display:flex;flex-wrap:wrap;gap:8px}
627
662
  .web-search-rank{margin:0;padding:0;list-style:none}
628
663
  .web-search-rank li{position:relative;display:grid;grid-template-columns:24px minmax(0,1fr) auto;gap:10px;align-items:start;padding:14px 0;border-top:1px solid var(--gray-6,color-mix(in srgb,currentColor 15%,transparent))}
@@ -641,7 +676,7 @@ const styles = `
641
676
  .web-search-limits{margin:0;border:0;padding:0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px 20px}
642
677
  .web-search-limits label{display:grid;gap:6px;font-size:var(--font-size-2,14px)}
643
678
  @media(max-width:560px){.web-search-rank-move{flex-direction:column;align-items:flex-end}.web-search-limits{grid-template-columns:minmax(0,1fr)}}
644
- @media(prefers-reduced-motion:reduce){.web-search-switch-thumb{transition:none}}
679
+ @media(prefers-reduced-motion:reduce){.web-search-switch-thumb{transition:none}.web-search-settings button:not([role="switch"]),.web-search-settings input{transition:none}}
645
680
  `;
646
681
  function apply(ctx) {
647
682
  const client = ctx;
@@ -1 +1 @@
1
- {"version":3,"file":"client.inner.cjs","names":["useId","useState"],"sources":["../src/provider-info.ts","../src/contracts.ts","../src/client.tsx"],"sourcesContent":["import type { ProviderView } from './contracts.ts'\n\n// Public prices checked on 2026-09-21; links remain available for current account terms.\nexport const PROVIDER_PRICING_CHECKED = '2026-09-21'\n\ninterface ProviderInfo {\n description: string\n pricing: string\n pricingUrl?: string\n}\n\nconst providerInfo: Record<string, ProviderInfo> = {\n ddg: {\n description: '搜索 DuckDuckGo 公开网页,无需注册或 API Key。',\n pricing: '免费使用,不产生搜索 API 费用。',\n pricingUrl: 'https://duckduckgo.com/',\n },\n 'zhihu-global': {\n description: '融合知乎问答与全网内容,返回可溯源的搜索结果。',\n pricing: '注册可获 5,000 次/天试用额度;超额价格需向平台咨询。',\n pricingUrl: 'https://developer.zhihu.com/',\n },\n bocha: {\n description: '面向中文内容的网页搜索,返回标题、链接和摘要。',\n pricing: '按调用次数计费,目录价 ¥0.036/次;试用额度以官方活动为准。',\n pricingUrl: 'https://aq6ky2b8nql.feishu.cn/wiki/JYSbwzdPIiFnz4kDYPXcHSDrnZb',\n },\n brave: {\n description: '使用 Brave 独立网页索引,搜索网页、新闻等内容。',\n pricing: '每月赠 $5(约 1,000 次);超出 $5/千次,激活需绑卡。',\n pricingUrl: 'https://brave.com/search/api/',\n },\n 'deepseek-official': {\n description: '由 DeepSeek 模型调用内置搜索,复用模型 API Key。',\n pricing: '默认 Flash 按 tokens 计费:每百万输入 ¥1–2(缓存命中 ¥0.02–0.04)、输出 ¥4–8,随峰谷时段变化;赠送余额和搜索工具费用以平台为准。',\n pricingUrl: 'https://api-docs.deepseek.com/zh-cn/quick_start/pricing/',\n },\n exa: {\n description: '面向 AI 的网页搜索,提供相关网页与内容片段。',\n pricing: '注册赠 $20,此后每月赠 $10;$7/千次(每次最多 10 条),超过 10 条每多一条加 $1/千次。',\n pricingUrl: 'https://exa.ai/pricing',\n },\n firecrawl: {\n description: '搜索网页,并可配合网页内容提取。',\n pricing: '每月免费 1,000 积分,搜索每 10 条结果扣 2 积分;Hobby 含 5,000 积分/月,年付折合 $16/月,额外 $5/千积分。',\n pricingUrl: 'https://www.firecrawl.dev/pricing',\n },\n serper: {\n description: '获取 Google 的网页搜索结果。',\n pricing: '注册赠 2,500 次(一次性);充值 $50/5万次($1/千次),付费额度有效期 6 个月。',\n pricingUrl: 'https://serper.dev/',\n },\n tavily: {\n description: '面向 AI 的网页搜索,当前使用基础搜索。',\n pricing: '每月免费 1,000 积分,基础搜索 1 积分/次;按量付费 $0.008/积分($8/千次)。',\n pricingUrl: 'https://docs.tavily.com/documentation/api-credits',\n },\n}\n\nexport function searchProviderInfo(provider: ProviderView): ProviderInfo {\n return (Object.hasOwn(providerInfo, provider.id) ? providerInfo[provider.id] : undefined) ?? {\n description: provider.description,\n pricing: provider.billing === 'none' ? '此后端不收取搜索费用。'\n : provider.billing === 'model-and-tools' ? '按模型用量和工具调用计费;免费额度及单价请查看供应商说明。'\n : '按 API 调用计费;免费额度及单价请查看供应商说明。',\n pricingUrl: provider.signupUrl,\n }\n}\n","import type { WebFetchProvider, WebSearchProvider } from '@deepseek-ai/dsh-web'\n\nexport const WEB_SEARCH_RPC_CHANNEL = '/web-search-manager'\nexport type RpcResult<T = unknown> = { ok: true; value: T } | {\n ok: false; error: { code: string; message: string; details: Record<string, unknown> }\n}\nexport type ProviderKind = 'search' | 'fetch'\n/** Management metadata only. Execution and source vocabulary belong to ctx.web. */\nexport interface ProviderDescriptor {\n readonly id: string\n readonly label: string\n readonly description: string\n readonly defaultBaseURL?: string\n readonly credentialRef?: string\n /** True when the key is shared with another surface (e.g. the chat model Key). The page must not delete it. */\n readonly credentialShared?: boolean\n readonly billing: 'request' | 'model-and-tools' | 'none'\n /** Public HTTPS page where the user can create an API key. */\n readonly signupUrl?: string\n}\nexport interface ProviderOptions {\n readonly apiKey?: string\n readonly baseURL?: string\n readonly timeoutMs: number\n readonly maxFetchChars: number\n}\nexport type SearchProviderFactory = (options: ProviderOptions) => WebSearchProvider\nexport type FetchProviderFactory = (options: ProviderOptions) => WebFetchProvider\n/** Never store credential values here. Keys are resolved by reference per call. */\nexport interface WebSettings {\n revision: number\n searchEnabled: boolean\n fetchEnabled: boolean\n searchProvider: string\n fetchProvider: string\n searchOrder: string[]\n maxResults: number\n maxQueries: number\n timeoutMs: number\n maxFetchChars: number\n endpoints: Record<string, string>\n}\nexport const DEFAULT_SEARCH_ORDER = ['ddg'] as const\n\nexport const defaultSettings = (): WebSettings => ({\n revision: 0, searchEnabled: true, fetchEnabled: true,\n searchProvider: 'ddg', fetchProvider: 'http', searchOrder: [...DEFAULT_SEARCH_ORDER],\n maxResults: 5, maxQueries: 1,\n timeoutMs: 30_000, maxFetchChars: 100_000, endpoints: {},\n})\n\n/** Keyed backends first, keyless last — same idea as OpenClaw auto-detect. */\nexport function defaultSearchOrder(ids: readonly string[], keyed: (id: string) => boolean): string[] {\n return [...ids.filter(keyed), ...ids.filter(id => !keyed(id))]\n}\n\nexport function migrateSearchOrder(settings: { searchOrder?: string[]; searchProvider?: string }): string[] {\n return settings.searchOrder ?? (settings.searchProvider ? [settings.searchProvider] : [...DEFAULT_SEARCH_ORDER])\n}\n\nexport function resolveSearchOrder(\n knownIds: readonly string[],\n settings: Pick<WebSettings, 'searchOrder' | 'searchProvider'>,\n _keyed: (id: string) => boolean,\n): string[] {\n return [...new Set(settings.searchOrder.filter(id => knownIds.includes(id)))]\n}\n\nexport function pickActiveSearch(order: readonly string[], configured: (id: string) => boolean): string {\n return order.find(configured) ?? ''\n}\nexport interface ProviderView extends ProviderDescriptor {\n kind: ProviderKind\n configured: boolean\n baseURL?: string\n calls: number\n failures: number\n}\nexport interface WebStatus {\n settings: WebSettings\n providers: ProviderView[]\n searchActive: boolean\n fetchActive: boolean\n storageFailed: boolean\n}\nexport function providerKey(kind: ProviderKind, id: string): string { return `${kind}:${id}` }\nexport function validateBaseURL(value: string): string {\n const url = new URL(value)\n if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash) {\n throw new Error('供应商地址必须是 HTTPS,且不能包含用户名、密码、查询参数或片段。')\n }\n return url.href.replace(/\\/+$/, '')\n}\n","import type { Context } from '@deepseek-ai/cordis'\nimport { useEffect, useId, useState } from 'react'\nimport { PROVIDER_PRICING_CHECKED, searchProviderInfo } from './provider-info.ts'\nimport {\n WEB_SEARCH_RPC_CHANNEL, pickActiveSearch, resolveSearchOrder,\n type ProviderView, type RpcResult, type WebSettings, type WebStatus,\n} from './contracts.ts'\n\nexport const name = 'dsh-web-search-manager-client'\nexport const inject = ['slots', 'connection', 'remote', 'remote.credentials'] as const\n\ninterface Credentials {\n describe(refs: string[]): Promise<RpcResult<Record<string, { configured: boolean; writable: boolean; source?: string }>>>\n set(ref: string, value: string): Promise<RpcResult<unknown>>\n unset(ref: string): Promise<RpcResult<unknown>>\n}\ninterface Client {\n connection: { rpc: { call(channel: string, endpoint: string, payload: unknown): Promise<unknown> } }\n remote: { credentials: Credentials }\n slots: {\n inject(key: string, callback: () => unknown): () => void\n register(spec: { name: string; id: string; label: string; order: number }, render: unknown): () => void\n }\n}\n\nfunction unwrap<T>(result: RpcResult<T>): T {\n if (!result.ok) throw new Error(result.error.message)\n return result.value\n}\nfunction editable(settings: WebSettings): Omit<WebSettings, 'revision'> {\n const { revision: _revision, ...rest } = settings\n return rest\n}\n\nexport function isProviderOn(status: WebStatus, provider: ProviderView): boolean {\n const enabled = provider.kind === 'search' ? status.searchActive : status.fetchActive\n const selected = status.settings[provider.kind === 'search' ? 'searchProvider' : 'fetchProvider']\n return enabled && selected === provider.id\n}\n\nexport function searchBackends(status: WebStatus): ProviderView[] {\n const all = status.providers.filter(provider => provider.kind === 'search')\n const order = resolveSearchOrder(\n all.map(provider => provider.id),\n status.settings,\n id => Boolean(all.find(provider => provider.id === id)?.credentialRef),\n )\n return order.flatMap(id => all.find(provider => provider.id === id) ?? [])\n}\n\nexport function catalogSearchBackends(status: WebStatus): ProviderView[] {\n const enabled = searchBackends(status)\n const rest = status.providers.filter(provider => provider.kind === 'search' && !enabled.some(row => row.id === provider.id))\n return [...enabled, ...rest]\n}\n\nexport function selectedSearchBackend(status: WebStatus): ProviderView | undefined {\n const backends = searchBackends(status)\n const id = pickActiveSearch(backends.map(provider => provider.id), id => Boolean(backends.find(provider => provider.id === id)?.configured))\n return backends.find(provider => provider.id === id)\n}\n\nfunction ExternalLink({ url, label, children }: { url: string; label: string; children: string }) {\n return <a href={url} target=\"_blank\" rel=\"noreferrer noopener\" aria-label={label}\n onClick={event => {\n const bridge = (globalThis as { dshWindow?: { openExternal?(url: string): void } }).dshWindow\n if (bridge?.openExternal) {\n event.preventDefault()\n bridge.openExternal(url)\n }\n }}>{children}</a>\n}\n\nexport function canEnableSearch(\n order: readonly string[],\n providers: readonly ProviderView[],\n keys: Record<string, string>,\n writable: Record<string, boolean>,\n): boolean {\n return order.some(id => {\n const provider = providers.find(item => item.kind === 'search' && item.id === id)\n if (!provider) return false\n if (provider.configured) return true\n const typed = (keys[provider.id] ?? '').trim()\n return Boolean(provider.credentialRef && typed && writable[provider.credentialRef] !== false)\n })\n}\n\nexport function nextSearchEnabled(\n currentlyEnabled: boolean,\n nextOrder: readonly string[],\n providers: readonly ProviderView[],\n keys: Record<string, string>,\n writable: Record<string, boolean>,\n): boolean {\n return currentlyEnabled && canEnableSearch(nextOrder, providers, keys, writable)\n}\n\nexport function NetworkSearchSettings({ client }: { client: Client }) {\n const tabsId = useId()\n const [tab, setTab] = useState<'providers' | 'limits'>('providers')\n const [status, setStatus] = useState<WebStatus>()\n const [draft, setDraft] = useState<WebSettings>()\n const [keys, setKeys] = useState<Record<string, string>>({})\n const [busy, setBusy] = useState(false)\n const [dragSource, setDragSource] = useState<string | null>(null)\n const [dropTarget, setDropTarget] = useState<string | null>(null)\n const [sortAnnouncement, setSortAnnouncement] = useState('')\n const [note, setNote] = useState('')\n const [error, setError] = useState('')\n const [writable, setWritable] = useState<Record<string, boolean>>({})\n\n async function call<T>(endpoint: string, payload: unknown = {}): Promise<T> {\n return unwrap(await client.connection.rpc.call(WEB_SEARCH_RPC_CHANNEL, endpoint, payload) as RpcResult<T>)\n }\n async function load() {\n const next = await call<WebStatus>('status')\n setStatus(next); setDraft(next.settings)\n const refs = next.providers.flatMap(provider => provider.credentialRef ? [provider.credentialRef] : [])\n if (refs.length) {\n const credentials = unwrap(await client.remote.credentials.describe(refs))\n setWritable(Object.fromEntries(Object.entries(credentials).map(([ref, value]) => [ref, value.writable])))\n }\n return next\n }\n useEffect(() => { void load().catch(() => setError('无法读取网络搜索设置,请检查 Host 连接后重试。')) }, [client])\n\n async function action(run: () => Promise<void>) {\n setBusy(true); setNote(''); setError('')\n try { await run() }\n catch (cause) { setError(cause instanceof Error ? cause.message : '操作失败。'); await load().catch(() => {}) }\n finally { setBusy(false) }\n }\n async function update(settings: Omit<WebSettings, 'revision'>, expectedRevision: number) {\n const next = await call<WebStatus>('update', { settings, expectedRevision })\n setStatus(next); setDraft(next.settings)\n return next\n }\n\n async function persist(on: boolean, order: string[]) {\n if (!status || !draft) return\n for (const provider of status.providers) {\n if (provider.kind !== 'search') continue\n const snapshot = keys[provider.id] ?? ''\n const typed = snapshot.trim()\n if (!provider.credentialRef || !typed) continue\n if (writable[provider.credentialRef] === false) throw new Error('当前凭据为只读,无法写入。')\n unwrap(await client.remote.credentials.set(provider.credentialRef, typed))\n setKeys(current => current[provider.id] === snapshot ? { ...current, [provider.id]: '' } : current)\n }\n await update({\n ...editable(status.settings),\n maxResults: draft.maxResults, maxQueries: draft.maxQueries,\n timeoutMs: draft.timeoutMs, maxFetchChars: draft.maxFetchChars,\n endpoints: draft.endpoints, searchOrder: order,\n searchEnabled: on, fetchProvider: 'http', fetchEnabled: on,\n }, status.settings.revision)\n await load()\n }\n\n async function removeKey(provider: ProviderView) {\n if (!provider.credentialRef || provider.credentialShared || !status) return\n unwrap(await client.remote.credentials.unset(provider.credentialRef))\n setKeys(current => ({ ...current, [provider.id]: '' }))\n await load()\n setNote('已删除 Key。')\n }\n\n if (!status || !draft) {\n return <section className=\"web-search-settings\">\n <p role=\"status\">{error || '正在读取设置…'}</p>\n <button type=\"button\" onClick={() => void action(async () => { await load() })}>重新连接</button>\n </section>\n }\n\n const enabled = searchBackends(status)\n const backends = catalogSearchBackends(status)\n const selected = selectedSearchBackend(status)\n const on = status.settings.searchEnabled\n const order = enabled.map(provider => provider.id)\n const canEnable = canEnableSearch(order, status.providers, keys, writable)\n const limits = [\n ['maxResults', '每条查询的结果上限', 1, 20],\n ['maxQueries', '每次工具调用的查询上限', 1, 5],\n ['timeoutMs', '请求超时(毫秒)', 1000, 120000],\n ['maxFetchChars', '网页正文字符上限', 1000, 200000],\n ] as const\n\n function move(id: string, nextIndex: number) {\n const index = order.indexOf(id)\n if (busy || index < 0 || nextIndex < 0 || nextIndex >= order.length || index === nextIndex) return\n const next = [...order]\n const [item] = next.splice(index, 1)\n next.splice(nextIndex, 0, item)\n void action(async () => {\n await persist(on, next)\n setSortAnnouncement((backends.find(provider => provider.id === id)?.label ?? id) + ' 已移至第 ' + (nextIndex + 1) + ' 位')\n })\n }\n\n function endDrag() {\n setDragSource(null)\n setDropTarget(null)\n }\n\n return <section className=\"web-search-settings\" data-testid=\"web-search-settings\">\n <div className=\"web-search-tabs\" role=\"tablist\" aria-label=\"网络搜索设置\" onKeyDown={event => {\n if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return\n const buttons = [...event.currentTarget.querySelectorAll<HTMLButtonElement>('[role=\"tab\"]')]\n const index = buttons.indexOf(event.target as HTMLButtonElement)\n if (index < 0) return\n event.preventDefault()\n const next = event.key === 'Home' ? 0 : event.key === 'End' ? buttons.length - 1\n : (index + (event.key === 'ArrowRight' ? 1 : -1) + buttons.length) % buttons.length\n buttons[next]?.focus()\n buttons[next]?.click()\n }}>\n {([['providers', '搜索服务'], ['limits', '请求限制']] as const).map(([key, label]) => (\n <button key={key} type=\"button\" role=\"tab\" id={tabsId + '-' + key + '-tab'}\n aria-controls={tabsId + '-' + key + '-panel'} aria-selected={tab === key}\n tabIndex={tab === key ? 0 : -1} onClick={() => setTab(key)}>{label}</button>\n ))}\n </div>\n <span className=\"web-search-sr-only\" role=\"status\" aria-live=\"polite\">{sortAnnouncement}</span>\n {error && <p role=\"alert\" className=\"web-search-error\">{error}</p>}\n {note && <p role=\"status\">{note}</p>}\n {status.storageFailed && <p role=\"alert\">配置保存异常,当前运行已暂停网络访问。请重新保存。</p>}\n <div role=\"tabpanel\" id={tabsId + '-providers-panel'} aria-labelledby={tabsId + '-providers-tab'}\n hidden={tab !== 'providers'} tabIndex={0}>\n {backends.length === 0 ? <p className=\"web-search-empty\">还没有可用的搜索后端。</p> : <article\n className=\"web-search-card\"\n data-testid=\"web-search-tool\"\n data-on={on ? 'true' : 'false'}>\n <header>\n <div>\n <h3>联网搜索</h3>\n {!canEnable && <p className=\"web-search-meta\">请选择搜索服务并配置 Key。</p>}\n </div>\n <button\n type=\"button\"\n role=\"switch\"\n className={`web-search-switch${on ? ' is-on' : ''}`}\n aria-checked={on}\n aria-label={on ? '关闭联网搜索' : '启用联网搜索'}\n disabled={busy || (!on && !canEnable)}\n onClick={() => void action(() => persist(!on, order))}>\n <span className=\"web-search-switch-thumb\" aria-hidden=\"true\" />\n </button>\n </header>\n <ol className=\"web-search-rank\" aria-label=\"搜索后端\">\n {backends.map(provider => {\n const participating = order.includes(provider.id)\n const rank = order.indexOf(provider.id)\n const active = status.searchActive && selected?.id === provider.id\n const info = searchProviderInfo(provider)\n const needsKey = Boolean(provider.credentialRef)\n const shared = Boolean(provider.credentialShared)\n const showKey = needsKey && participating && (!shared || !provider.configured)\n const canSort = participating && order.length > 1\n return <li key={provider.id} className={active ? 'is-active' : undefined} data-testid={`web-search-rank-${provider.id}`}\n data-dragging={dragSource === provider.id || undefined}\n data-drop={dropTarget === provider.id && dragSource ? (order.indexOf(dragSource) < rank ? 'after' : 'before') : undefined}\n onDragOver={event => {\n if (busy || !canSort || !dragSource || dragSource === provider.id) return\n event.preventDefault()\n event.dataTransfer.dropEffect = 'move'\n setDropTarget(provider.id)\n }}\n onDragLeave={event => {\n if (!event.currentTarget.contains(event.relatedTarget as Node | null)) setDropTarget(null)\n }}\n onDrop={event => {\n if (busy || !canSort || !dragSource) return\n event.preventDefault()\n move(dragSource, rank)\n endDrag()\n }}>\n {canSort ? <button type=\"button\" className=\"web-search-drag-handle\"\n draggable={!busy} aria-disabled={busy}\n aria-label={'拖动排序 ' + provider.label}\n title=\"拖动排序,或用上下方向键调整\"\n onDragStart={event => {\n if (busy) { event.preventDefault(); return }\n event.dataTransfer.effectAllowed = 'move'\n event.dataTransfer.setData('application/x-dsh-search-provider', provider.id)\n const row = event.currentTarget.closest('li')\n if (row) event.dataTransfer.setDragImage(row, 16, 16)\n setDragSource(provider.id)\n setSortAnnouncement('')\n }}\n onDragEnd={endDrag}\n onKeyDown={event => {\n if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') return\n event.preventDefault()\n move(provider.id, rank + (event.key === 'ArrowUp' ? -1 : 1))\n }}>\n <svg width=\"16\" height=\"20\" viewBox=\"0 0 16 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <circle cx=\"5\" cy=\"5\" r=\"1.5\" /><circle cx=\"11\" cy=\"5\" r=\"1.5\" />\n <circle cx=\"5\" cy=\"10\" r=\"1.5\" /><circle cx=\"11\" cy=\"10\" r=\"1.5\" />\n <circle cx=\"5\" cy=\"15\" r=\"1.5\" /><circle cx=\"11\" cy=\"15\" r=\"1.5\" />\n </svg>\n </button> : <span className=\"web-search-rank-index\">{participating ? rank + 1 : '—'}</span>}\n <div className=\"web-search-provider\">\n <div className=\"web-search-provider-heading\">\n <strong>{provider.label}</strong>\n {active && <span className=\"web-search-meta\">当前使用</span>}\n {provider.signupUrl && <ExternalLink url={provider.signupUrl} label={provider.label + ' 去注册'}>去注册</ExternalLink>}\n </div>\n <div className=\"web-search-provider-info\">\n <p>{info.description}</p>\n <p>{info.pricing}{info.pricingUrl && <> <ExternalLink url={info.pricingUrl}\n label={provider.label + ' 费用说明'}>{provider.id === 'ddg' ? '官网' : '费用说明'}</ExternalLink></>}</p>\n </div>\n {participating && !provider.configured && <p className=\"web-search-meta\">{needsKey ? '待配置 Key' : '暂不可用'}</p>}\n {shared && participating && <small>{provider.id === 'zhihu-global' ? '与「知乎资料」共用 Access Secret' : '与模型设置共用 Key'}</small>}\n {showKey ? <label>API Key\n <input type=\"password\" autoComplete=\"off\" spellCheck={false}\n disabled={busy || writable[provider.credentialRef ?? ''] === false}\n value={keys[provider.id] ?? ''}\n placeholder={provider.configured ? '已配置,留空不替换' : '请输入 API Key'}\n onChange={event => setKeys(current => ({ ...current, [provider.id]: event.target.value }))} />\n </label> : null}\n {needsKey && !shared && provider.configured ? <button type=\"button\"\n disabled={busy || writable[provider.credentialRef ?? ''] === false}\n onClick={() => void action(() => removeKey(provider))}>删除 Key</button> : null}\n </div>\n <div className=\"web-search-rank-move\">\n <button\n type=\"button\"\n role=\"switch\"\n className={`web-search-switch${participating ? ' is-on' : ''}`}\n aria-checked={participating}\n aria-label={`${participating ? '关闭' : '开启'} ${provider.label}`}\n disabled={busy}\n onClick={() => {\n const next = participating ? order.filter(id => id !== provider.id) : [...order, provider.id]\n void action(() => persist(nextSearchEnabled(status.settings.searchEnabled, next, status.providers, keys, writable), next))\n }}>\n <span className=\"web-search-switch-thumb\" aria-hidden=\"true\" />\n </button>\n </div>\n </li>\n })}\n </ol>\n <p className=\"web-search-meta\">价格核对于 {PROVIDER_PRICING_CHECKED},实际额度与费用以供应商账户为准。</p>\n {on ? <div className=\"web-search-card-actions\">\n <button type=\"button\" disabled={busy} onClick={() => void action(async () => {\n await persist(status.settings.searchEnabled, order)\n const result = await call<{ sources: number }>('test')\n await load()\n setNote(`连接正常,返回 ${result.sources} 条来源。`)\n })}>测试连接(可能计费)</button>\n </div> : null}\n </article>}\n </div>\n <div role=\"tabpanel\" id={tabsId + '-limits-panel'} aria-labelledby={tabsId + '-limits-tab'}\n hidden={tab !== 'limits'} tabIndex={0}>\n <fieldset className=\"web-search-limits\" disabled={busy} aria-label=\"请求限制\">\n {limits.map(([key, label, min, max]) => (\n <label key={key}>{label}\n <input type=\"number\" min={min} max={max} step={1} value={draft[key]}\n onChange={event => setDraft({ ...draft, [key]: Number(event.target.value) })} />\n </label>\n ))}\n </fieldset>\n </div>\n <button type=\"button\" disabled={busy} onClick={() => void action(async () => {\n await persist(status.settings.searchEnabled, order)\n setNote('已保存。')\n })}>保存</button>\n </section>\n}\n\nconst styles = `\n.web-search-settings{max-width:760px;display:grid;gap:24px;color:inherit;font:400 var(--font-size-2,14px)/1.5 var(--default-font-family,system-ui,sans-serif)}\n.web-search-settings p{margin:0;line-height:1.5}\n.web-search-settings small,.web-search-meta,.web-search-empty{font-size:var(--font-size-1,13px);color:var(--gray-11,inherit)}\n.web-search-error{color:var(--red-11,#b42318)}\n.web-search-card{display:grid;gap:12px}\n.web-search-card header{display:flex;justify-content:space-between;gap:12px;align-items:center;padding-bottom:8px}\n.web-search-card h3{margin:0;font-size:var(--font-size-3,16px);font-weight:600}\n.web-search-provider{display:grid;gap:8px;min-width:0}\n.web-search-provider-info{display:grid;gap:3px;font-size:var(--font-size-1,13px);color:var(--gray-11,inherit);overflow-wrap:anywhere}\n.web-search-provider-heading{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;min-height:24px}\n.web-search-provider-heading strong{font-weight:500}\n.web-search-card label{display:grid;gap:6px}\n.web-search-settings input{box-sizing:border-box;width:100%;min-width:0;padding:8px 10px;border:1px solid var(--gray-6,color-mix(in srgb,currentColor 22%,transparent));border-radius:6px;background:var(--color-surface,transparent);color:inherit;font:inherit}\n.web-search-settings button:not([role=\"switch\"]){min-height:34px;padding:6px 12px;border:1px solid color-mix(in srgb,currentColor 25%,transparent);border-radius:6px;background:transparent;color:inherit;cursor:pointer;font:inherit;justify-self:start}\n.web-search-settings button:disabled{opacity:.45;cursor:not-allowed}\n.web-search-tabs{display:flex;gap:20px;border-bottom:1px solid var(--gray-6,color-mix(in srgb,currentColor 15%,transparent))}\n.web-search-tabs button[role=\"tab\"]{padding:8px 0;border:0;border-bottom:2px solid transparent;border-radius:0;color:var(--gray-11,inherit)}\n.web-search-tabs button[aria-selected=\"true\"]{border-bottom-color:var(--accent-9,#3b82f6);color:var(--accent-11,inherit);font-weight:600}\n.web-search-settings .web-search-switch{all:unset;box-sizing:border-box;position:relative;display:inline-block;width:36px;height:20px;flex:none;border-radius:999px;background:var(--gray-7,color-mix(in srgb,currentColor 28%,transparent));cursor:pointer}\n.web-search-switch.is-on{background:var(--accent-9,#3b82f6)}\n.web-search-switch:disabled{cursor:not-allowed;opacity:.7}\n.web-search-switch-thumb{position:absolute;top:3px;left:3px;width:14px;height:14px;border-radius:999px;background:#fff;transition:transform 150ms ease}\n.web-search-switch.is-on .web-search-switch-thumb{transform:translateX(16px)}\n.web-search-settings :focus-visible{outline:2px solid currentColor;outline-offset:3px}\n.web-search-card-actions{display:flex;flex-wrap:wrap;gap:8px}\n.web-search-rank{margin:0;padding:0;list-style:none}\n.web-search-rank li{position:relative;display:grid;grid-template-columns:24px minmax(0,1fr) auto;gap:10px;align-items:start;padding:14px 0;border-top:1px solid var(--gray-6,color-mix(in srgb,currentColor 15%,transparent))}\n.web-search-settings button.web-search-drag-handle{display:grid;place-items:center;width:24px;height:24px;min-height:24px;padding:0;border:0;cursor:grab;color:inherit;opacity:.65}\n.web-search-settings button.web-search-drag-handle:hover{opacity:1;background:var(--gray-3,color-mix(in srgb,currentColor 8%,transparent))}\n.web-search-settings button.web-search-drag-handle:active{cursor:grabbing}\n.web-search-settings button.web-search-drag-handle[aria-disabled=\"true\"]{cursor:wait;opacity:.4}\n.web-search-rank li[data-dragging]{opacity:.45}\n.web-search-rank li[data-drop]::after{content:'';position:absolute;left:0;right:0;height:2px;background:var(--accent-9,#3b82f6);pointer-events:none}\n.web-search-rank li[data-drop=\"before\"]::after{top:-1px}\n.web-search-rank li[data-drop=\"after\"]::after{bottom:-1px}\n.web-search-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}\n.web-search-rank-index{opacity:.6;font-variant-numeric:tabular-nums;line-height:24px}\n.web-search-rank-move{display:flex;flex-wrap:wrap;gap:6px;align-items:center;justify-content:flex-end;padding-top:2px}\n.web-search-settings a{color:var(--accent-11,currentColor);font-size:var(--font-size-1,13px);text-underline-offset:3px}\n.web-search-limits{margin:0;border:0;padding:0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px 20px}\n.web-search-limits label{display:grid;gap:6px;font-size:var(--font-size-2,14px)}\n@media(max-width:560px){.web-search-rank-move{flex-direction:column;align-items:flex-end}.web-search-limits{grid-template-columns:minmax(0,1fr)}}\n@media(prefers-reduced-motion:reduce){.web-search-switch-thumb{transition:none}}\n`\n\nexport function apply(ctx: Context): void {\n const client = ctx as unknown as Client\n ctx.effect(() => {\n if (typeof document === 'undefined') return () => {}\n const style = document.createElement('style')\n style.setAttribute('data-plugin', '@klarkxy/dsh-web-search-manager'); style.textContent = styles\n style.dataset.dshWebSearch = ''; document.head.appendChild(style)\n return () => style.remove()\n }, 'web-search-manager.styles')\n ctx.effect(() => client.slots.inject('settings.section', () => client.slots.register({\n name: 'settings.section', id: 'web-search', order: 70, label: '网络搜索',\n }, () => <NetworkSearchSettings client={client} />)), 'web-search-manager.settings')\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.inner.cjs","names":["useState","useRef","useId"],"sources":["../src/provider-info.ts","../src/contracts.ts","../src/client.tsx"],"sourcesContent":["import type { ProviderView } from './contracts.ts'\n\n// Public prices checked on 2026-09-21; links remain available for current account terms.\nexport const PROVIDER_PRICING_CHECKED = '2026-09-21'\n\ninterface ProviderInfo {\n description: string\n pricing: string\n pricingUrl?: string\n}\n\nconst providerInfo: Record<string, ProviderInfo> = {\n ddg: {\n description: '搜索 DuckDuckGo 公开网页,无需注册或 API Key。',\n pricing: '免费使用,不产生搜索 API 费用。',\n pricingUrl: 'https://duckduckgo.com/',\n },\n 'zhihu-global': {\n description: '融合知乎问答与全网内容,返回可溯源的搜索结果。',\n pricing: '注册可获 5,000 次/天试用额度;超额价格需向平台咨询。',\n pricingUrl: 'https://developer.zhihu.com/',\n },\n bocha: {\n description: '面向中文内容的网页搜索,返回标题、链接和摘要。',\n pricing: '按调用次数计费,目录价 ¥0.036/次;试用额度以官方活动为准。',\n pricingUrl: 'https://aq6ky2b8nql.feishu.cn/wiki/JYSbwzdPIiFnz4kDYPXcHSDrnZb',\n },\n brave: {\n description: '使用 Brave 独立网页索引,搜索网页、新闻等内容。',\n pricing: '每月赠 $5(约 1,000 次);超出 $5/千次,激活需绑卡。',\n pricingUrl: 'https://brave.com/search/api/',\n },\n 'deepseek-official': {\n description: '由 DeepSeek 模型调用内置搜索,复用模型 API Key。',\n pricing: '默认 Flash 按 tokens 计费:每百万输入 ¥1–2(缓存命中 ¥0.02–0.04)、输出 ¥4–8,随峰谷时段变化;赠送余额和搜索工具费用以平台为准。',\n pricingUrl: 'https://api-docs.deepseek.com/zh-cn/quick_start/pricing/',\n },\n exa: {\n description: '面向 AI 的网页搜索,提供相关网页与内容片段。',\n pricing: '注册赠 $20,此后每月赠 $10;$7/千次(每次最多 10 条),超过 10 条每多一条加 $1/千次。',\n pricingUrl: 'https://exa.ai/pricing',\n },\n firecrawl: {\n description: '搜索网页,并可配合网页内容提取。',\n pricing: '每月免费 1,000 积分,搜索每 10 条结果扣 2 积分;Hobby 含 5,000 积分/月,年付折合 $16/月,额外 $5/千积分。',\n pricingUrl: 'https://www.firecrawl.dev/pricing',\n },\n serper: {\n description: '获取 Google 的网页搜索结果。',\n pricing: '注册赠 2,500 次(一次性);充值 $50/5万次($1/千次),付费额度有效期 6 个月。',\n pricingUrl: 'https://serper.dev/',\n },\n tavily: {\n description: '面向 AI 的网页搜索,当前使用基础搜索。',\n pricing: '每月免费 1,000 积分,基础搜索 1 积分/次;按量付费 $0.008/积分($8/千次)。',\n pricingUrl: 'https://docs.tavily.com/documentation/api-credits',\n },\n}\n\nexport function searchProviderInfo(provider: ProviderView): ProviderInfo {\n return (Object.hasOwn(providerInfo, provider.id) ? providerInfo[provider.id] : undefined) ?? {\n description: provider.description,\n pricing: provider.billing === 'none' ? '此后端不收取搜索费用。'\n : provider.billing === 'model-and-tools' ? '按模型用量和工具调用计费;免费额度及单价请查看供应商说明。'\n : '按 API 调用计费;免费额度及单价请查看供应商说明。',\n pricingUrl: provider.signupUrl,\n }\n}\n","import type { WebFetchProvider, WebSearchProvider } from '@deepseek-ai/dsh-web'\n\nexport const WEB_SEARCH_RPC_CHANNEL = '/web-search-manager'\nexport type RpcResult<T = unknown> = { ok: true; value: T } | {\n ok: false; error: { code: string; message: string; details: Record<string, unknown> }\n}\nexport type ProviderKind = 'search' | 'fetch'\n/** Management metadata only. Execution and source vocabulary belong to ctx.web. */\nexport interface ProviderDescriptor {\n readonly id: string\n readonly label: string\n readonly description: string\n readonly defaultBaseURL?: string\n readonly credentialRef?: string\n /** True when the key is shared with another surface (e.g. the chat model Key). The page must not delete it. */\n readonly credentialShared?: boolean\n readonly billing: 'request' | 'model-and-tools' | 'none'\n /** Public HTTPS page where the user can create an API key. */\n readonly signupUrl?: string\n}\nexport interface ProviderOptions {\n readonly apiKey?: string\n readonly baseURL?: string\n readonly timeoutMs: number\n readonly maxFetchChars: number\n}\nexport type SearchProviderFactory = (options: ProviderOptions) => WebSearchProvider\nexport type FetchProviderFactory = (options: ProviderOptions) => WebFetchProvider\n/** Never store credential values here. Keys are resolved by reference per call. */\nexport interface WebSettings {\n revision: number\n searchEnabled: boolean\n fetchEnabled: boolean\n searchProvider: string\n fetchProvider: string\n searchOrder: string[]\n maxResults: number\n maxQueries: number\n timeoutMs: number\n maxFetchChars: number\n endpoints: Record<string, string>\n}\nexport const DEFAULT_SEARCH_ORDER = ['ddg'] as const\n\nexport const defaultSettings = (): WebSettings => ({\n revision: 0, searchEnabled: true, fetchEnabled: true,\n searchProvider: 'ddg', fetchProvider: 'http', searchOrder: [...DEFAULT_SEARCH_ORDER],\n maxResults: 5, maxQueries: 1,\n timeoutMs: 30_000, maxFetchChars: 100_000, endpoints: {},\n})\n\n/** Keyed backends first, keyless last — same idea as OpenClaw auto-detect. */\nexport function defaultSearchOrder(ids: readonly string[], keyed: (id: string) => boolean): string[] {\n return [...ids.filter(keyed), ...ids.filter(id => !keyed(id))]\n}\n\nexport function migrateSearchOrder(settings: { searchOrder?: string[]; searchProvider?: string }): string[] {\n return settings.searchOrder ?? (settings.searchProvider ? [settings.searchProvider] : [...DEFAULT_SEARCH_ORDER])\n}\n\nexport function resolveSearchOrder(\n knownIds: readonly string[],\n settings: Pick<WebSettings, 'searchOrder' | 'searchProvider'>,\n _keyed: (id: string) => boolean,\n): string[] {\n return [...new Set(settings.searchOrder.filter(id => knownIds.includes(id)))]\n}\n\nexport function pickActiveSearch(order: readonly string[], configured: (id: string) => boolean): string {\n return order.find(configured) ?? ''\n}\nexport interface ProviderView extends ProviderDescriptor {\n kind: ProviderKind\n configured: boolean\n baseURL?: string\n calls: number\n failures: number\n}\nexport interface WebStatus {\n settings: WebSettings\n providers: ProviderView[]\n searchActive: boolean\n fetchActive: boolean\n storageFailed: boolean\n}\nexport function providerKey(kind: ProviderKind, id: string): string { return `${kind}:${id}` }\nexport function validateBaseURL(value: string): string {\n const url = new URL(value)\n if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash) {\n throw new Error('供应商地址必须是 HTTPS,且不能包含用户名、密码、查询参数或片段。')\n }\n return url.href.replace(/\\/+$/, '')\n}\n","import type { Context } from '@deepseek-ai/cordis'\nimport { useEffect, useId, useRef, useState } from 'react'\nimport { PROVIDER_PRICING_CHECKED, searchProviderInfo } from './provider-info.ts'\nimport {\n WEB_SEARCH_RPC_CHANNEL, pickActiveSearch, resolveSearchOrder,\n type ProviderView, type RpcResult, type WebSettings, type WebStatus,\n} from './contracts.ts'\n\nexport const name = 'dsh-web-search-manager-client'\nexport const inject = ['slots', 'connection', 'remote', 'remote.credentials'] as const\n\ninterface Credentials {\n describe(refs: string[]): Promise<RpcResult<Record<string, { configured: boolean; writable: boolean; source?: string }>>>\n set(ref: string, value: string): Promise<RpcResult<unknown>>\n unset(ref: string): Promise<RpcResult<unknown>>\n}\ninterface Client {\n connection: { rpc: { call(channel: string, endpoint: string, payload: unknown): Promise<unknown> } }\n remote: { credentials: Credentials }\n slots: {\n inject(key: string, callback: () => unknown): () => void\n register(spec: { name: string; id: string; label: string; order: number }, render: unknown): () => void\n }\n}\n\nfunction unwrap<T>(result: RpcResult<T>): T {\n if (!result.ok) throw new Error(result.error.message)\n return result.value\n}\nfunction editable(settings: WebSettings): Omit<WebSettings, 'revision'> {\n const { revision: _revision, ...rest } = settings\n return rest\n}\n\nexport function isProviderOn(status: WebStatus, provider: ProviderView): boolean {\n const enabled = provider.kind === 'search' ? status.searchActive : status.fetchActive\n const selected = status.settings[provider.kind === 'search' ? 'searchProvider' : 'fetchProvider']\n return enabled && selected === provider.id\n}\n\nexport function searchBackends(status: WebStatus): ProviderView[] {\n const all = status.providers.filter(provider => provider.kind === 'search')\n const order = resolveSearchOrder(\n all.map(provider => provider.id),\n status.settings,\n id => Boolean(all.find(provider => provider.id === id)?.credentialRef),\n )\n return order.flatMap(id => all.find(provider => provider.id === id) ?? [])\n}\n\nexport function catalogSearchBackends(status: WebStatus): ProviderView[] {\n const enabled = searchBackends(status)\n const rest = status.providers.filter(provider => provider.kind === 'search' && !enabled.some(row => row.id === provider.id))\n return [...enabled, ...rest]\n}\n\nexport function selectedSearchBackend(status: WebStatus): ProviderView | undefined {\n const backends = searchBackends(status)\n const id = pickActiveSearch(backends.map(provider => provider.id), id => Boolean(backends.find(provider => provider.id === id)?.configured))\n return backends.find(provider => provider.id === id)\n}\n\nfunction ExternalLink({ url, label, children }: { url: string; label: string; children: string }) {\n return <a href={url} target=\"_blank\" rel=\"noreferrer noopener\" aria-label={label}\n onClick={event => {\n const bridge = (globalThis as { dshWindow?: { openExternal?(url: string): void } }).dshWindow\n if (bridge?.openExternal) {\n event.preventDefault()\n bridge.openExternal(url)\n }\n }}>{children}</a>\n}\n\nexport function canEnableSearch(\n order: readonly string[],\n providers: readonly ProviderView[],\n keys: Record<string, string>,\n writable: Record<string, boolean>,\n): boolean {\n return order.some(id => {\n const provider = providers.find(item => item.kind === 'search' && item.id === id)\n if (!provider) return false\n if (provider.configured) return true\n const typed = (keys[provider.id] ?? '').trim()\n return Boolean(provider.credentialRef && typed && writable[provider.credentialRef] !== false)\n })\n}\n\nexport function nextSearchEnabled(\n currentlyEnabled: boolean,\n nextOrder: readonly string[],\n providers: readonly ProviderView[],\n keys: Record<string, string>,\n writable: Record<string, boolean>,\n): boolean {\n return currentlyEnabled && canEnableSearch(nextOrder, providers, keys, writable)\n}\n\nfunction ConfirmButton(props: { label: string; confirmLabel: string; disabled?: boolean; onConfirm(): void }) {\n const [armed, setArmed] = useState(false)\n const timer = useRef<ReturnType<typeof setTimeout>>(undefined)\n useEffect(() => () => clearTimeout(timer.current), [])\n function disarm() {\n clearTimeout(timer.current)\n setArmed(false)\n }\n return <button type=\"button\" className=\"web-search-danger\" disabled={props.disabled}\n onClick={() => {\n if (!armed) {\n setArmed(true)\n timer.current = setTimeout(() => setArmed(false), 3000)\n return\n }\n disarm()\n props.onConfirm()\n }}\n onBlur={disarm}>{armed ? props.confirmLabel : props.label}</button>\n}\n\nexport function NetworkSearchSettings({ client }: { client: Client }) {\n const tabsId = useId()\n const [tab, setTab] = useState<'providers' | 'limits'>('providers')\n const [status, setStatus] = useState<WebStatus>()\n const [draft, setDraft] = useState<WebSettings>()\n const [keys, setKeys] = useState<Record<string, string>>({})\n const [busy, setBusy] = useState(false)\n const [dragSource, setDragSource] = useState<string | null>(null)\n const [dropTarget, setDropTarget] = useState<string | null>(null)\n const [sortAnnouncement, setSortAnnouncement] = useState('')\n const [note, setNote] = useState('')\n const [error, setError] = useState('')\n const [writable, setWritable] = useState<Record<string, boolean>>({})\n\n async function call<T>(endpoint: string, payload: unknown = {}): Promise<T> {\n return unwrap(await client.connection.rpc.call(WEB_SEARCH_RPC_CHANNEL, endpoint, payload) as RpcResult<T>)\n }\n async function load() {\n const next = await call<WebStatus>('status')\n setStatus(next); setDraft(next.settings)\n const refs = next.providers.flatMap(provider => provider.credentialRef ? [provider.credentialRef] : [])\n if (refs.length) {\n const credentials = unwrap(await client.remote.credentials.describe(refs))\n setWritable(Object.fromEntries(Object.entries(credentials).map(([ref, value]) => [ref, value.writable])))\n }\n return next\n }\n useEffect(() => { void load().catch(() => setError('无法读取网络搜索设置,请检查 Host 连接后重试。')) }, [client])\n\n async function action(run: () => Promise<void>) {\n setBusy(true); setNote(''); setError('')\n try { await run() }\n catch (cause) { setError(cause instanceof Error ? cause.message : '操作失败。'); await load().catch(() => {}) }\n finally { setBusy(false) }\n }\n async function update(settings: Omit<WebSettings, 'revision'>, expectedRevision: number) {\n const next = await call<WebStatus>('update', { settings, expectedRevision })\n setStatus(next); setDraft(next.settings)\n return next\n }\n\n async function persist(on: boolean, order: string[]) {\n if (!status || !draft) return\n for (const provider of status.providers) {\n if (provider.kind !== 'search') continue\n const snapshot = keys[provider.id] ?? ''\n const typed = snapshot.trim()\n if (!provider.credentialRef || !typed) continue\n if (writable[provider.credentialRef] === false) throw new Error('当前凭据为只读,无法写入。')\n unwrap(await client.remote.credentials.set(provider.credentialRef, typed))\n setKeys(current => current[provider.id] === snapshot ? { ...current, [provider.id]: '' } : current)\n }\n const bounded = (key: (typeof limits)[number][0]) => {\n const row = limits.find(item => item[0] === key)!\n return Math.min(row[3], Math.max(row[2], Number.isFinite(draft[key]) ? draft[key] : row[2]))\n }\n await update({\n ...editable(status.settings),\n maxResults: bounded('maxResults'), maxQueries: bounded('maxQueries'),\n timeoutMs: bounded('timeoutMs'), maxFetchChars: bounded('maxFetchChars'),\n endpoints: draft.endpoints, searchOrder: order,\n searchEnabled: on, fetchProvider: 'http', fetchEnabled: on,\n }, status.settings.revision)\n await load()\n }\n\n async function removeKey(provider: ProviderView) {\n if (!provider.credentialRef || provider.credentialShared || !status) return\n unwrap(await client.remote.credentials.unset(provider.credentialRef))\n setKeys(current => ({ ...current, [provider.id]: '' }))\n await load()\n setNote('已删除 Key。')\n }\n\n if (!status || !draft) {\n return <section className=\"web-search-settings\">\n <p role=\"status\">{error || '正在读取设置…'}</p>\n {error ? <button type=\"button\" onClick={() => void action(async () => { await load() })}>重新连接</button> : null}\n </section>\n }\n\n const enabled = searchBackends(status)\n const backends = catalogSearchBackends(status)\n const selected = selectedSearchBackend(status)\n const on = status.settings.searchEnabled\n const order = enabled.map(provider => provider.id)\n const canEnable = canEnableSearch(order, status.providers, keys, writable)\n const limits = [\n ['maxResults', '每条查询的结果上限', 1, 20],\n ['maxQueries', '每次工具调用的查询上限', 1, 5],\n ['timeoutMs', '请求超时(毫秒)', 1000, 120000],\n ['maxFetchChars', '网页正文字符上限', 1000, 200000],\n ] as const\n\n function move(id: string, nextIndex: number) {\n const index = order.indexOf(id)\n if (busy || index < 0 || nextIndex < 0 || nextIndex >= order.length || index === nextIndex) return\n const next = [...order]\n const [item] = next.splice(index, 1)\n next.splice(nextIndex, 0, item)\n void action(async () => {\n await persist(on, next)\n setSortAnnouncement((backends.find(provider => provider.id === id)?.label ?? id) + ' 已移至第 ' + (nextIndex + 1) + ' 位')\n })\n }\n\n function endDrag() {\n setDragSource(null)\n setDropTarget(null)\n }\n\n return <section className=\"web-search-settings\" data-testid=\"web-search-settings\">\n <div className=\"web-search-tabs\" role=\"tablist\" aria-label=\"网络搜索设置\" onKeyDown={event => {\n if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return\n const buttons = [...event.currentTarget.querySelectorAll<HTMLButtonElement>('[role=\"tab\"]')]\n const index = buttons.indexOf(event.target as HTMLButtonElement)\n if (index < 0) return\n event.preventDefault()\n const next = event.key === 'Home' ? 0 : event.key === 'End' ? buttons.length - 1\n : (index + (event.key === 'ArrowRight' ? 1 : -1) + buttons.length) % buttons.length\n buttons[next]?.focus()\n buttons[next]?.click()\n }}>\n {([['providers', '搜索服务'], ['limits', '请求限制']] as const).map(([key, label]) => (\n <button key={key} type=\"button\" role=\"tab\" id={tabsId + '-' + key + '-tab'}\n aria-controls={tabsId + '-' + key + '-panel'} aria-selected={tab === key}\n tabIndex={tab === key ? 0 : -1} onClick={() => setTab(key)}>{label}</button>\n ))}\n </div>\n <span className=\"web-search-sr-only\" role=\"status\" aria-live=\"polite\">{sortAnnouncement}</span>\n {error && <p role=\"alert\" className=\"web-search-error\">{error}</p>}\n {note && <p role=\"status\">{note}</p>}\n {status.storageFailed && <p role=\"alert\">配置保存异常,当前运行已暂停网络访问。请重新保存。</p>}\n <div role=\"tabpanel\" id={tabsId + '-providers-panel'} aria-labelledby={tabsId + '-providers-tab'}\n hidden={tab !== 'providers'} tabIndex={0}>\n {backends.length === 0 ? <p className=\"web-search-empty\">还没有可用的搜索后端。</p> : <article\n className=\"web-search-card\"\n data-testid=\"web-search-tool\"\n data-on={on ? 'true' : 'false'}>\n <header>\n <div>\n <h3>联网搜索</h3>\n {!canEnable && <p className=\"web-search-meta\">请选择搜索服务并配置 Key。</p>}\n </div>\n <button\n type=\"button\"\n role=\"switch\"\n className={`web-search-switch${on ? ' is-on' : ''}`}\n aria-checked={on}\n aria-label={on ? '关闭联网搜索' : '启用联网搜索'}\n disabled={busy || (!on && !canEnable)}\n onClick={() => void action(() => persist(!on, order))}>\n <span className=\"web-search-switch-thumb\" aria-hidden=\"true\" />\n </button>\n </header>\n <ol className=\"web-search-rank\" aria-label=\"搜索后端\">\n {backends.map(provider => {\n const participating = order.includes(provider.id)\n const rank = order.indexOf(provider.id)\n const active = status.searchActive && selected?.id === provider.id\n const info = searchProviderInfo(provider)\n const needsKey = Boolean(provider.credentialRef)\n const shared = Boolean(provider.credentialShared)\n const showKey = needsKey && participating && (!shared || !provider.configured)\n const canSort = participating && order.length > 1\n return <li key={provider.id} className={active ? 'is-active' : undefined} data-testid={`web-search-rank-${provider.id}`}\n data-dragging={dragSource === provider.id || undefined}\n data-drop={dropTarget === provider.id && dragSource ? (order.indexOf(dragSource) < rank ? 'after' : 'before') : undefined}\n onDragOver={event => {\n if (busy || !canSort || !dragSource || dragSource === provider.id) return\n event.preventDefault()\n event.dataTransfer.dropEffect = 'move'\n setDropTarget(provider.id)\n }}\n onDragLeave={event => {\n if (!event.currentTarget.contains(event.relatedTarget as Node | null)) setDropTarget(null)\n }}\n onDrop={event => {\n if (busy || !canSort || !dragSource) return\n event.preventDefault()\n move(dragSource, rank)\n endDrag()\n }}>\n {canSort ? <button type=\"button\" className=\"web-search-drag-handle\"\n draggable={!busy} aria-disabled={busy}\n aria-label={'拖动排序 ' + provider.label}\n title=\"拖动排序,或用上下方向键调整\"\n onDragStart={event => {\n if (busy) { event.preventDefault(); return }\n event.dataTransfer.effectAllowed = 'move'\n event.dataTransfer.setData('application/x-dsh-search-provider', provider.id)\n const row = event.currentTarget.closest('li')\n if (row) event.dataTransfer.setDragImage(row, 16, 16)\n setDragSource(provider.id)\n setSortAnnouncement('')\n }}\n onDragEnd={endDrag}\n onKeyDown={event => {\n if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') return\n event.preventDefault()\n move(provider.id, rank + (event.key === 'ArrowUp' ? -1 : 1))\n }}>\n <svg width=\"16\" height=\"20\" viewBox=\"0 0 16 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <circle cx=\"5\" cy=\"5\" r=\"1.5\" /><circle cx=\"11\" cy=\"5\" r=\"1.5\" />\n <circle cx=\"5\" cy=\"10\" r=\"1.5\" /><circle cx=\"11\" cy=\"10\" r=\"1.5\" />\n <circle cx=\"5\" cy=\"15\" r=\"1.5\" /><circle cx=\"11\" cy=\"15\" r=\"1.5\" />\n </svg>\n </button> : <span className=\"web-search-rank-index\">{participating ? rank + 1 : '—'}</span>}\n <div className=\"web-search-provider\">\n <div className=\"web-search-provider-heading\">\n <strong>{provider.label}</strong>\n {active && <span className=\"web-search-meta\">当前使用</span>}\n {provider.signupUrl && <ExternalLink url={provider.signupUrl} label={provider.label + ' 去注册'}>去注册</ExternalLink>}\n </div>\n <div className=\"web-search-provider-info\">\n <p>{info.description}</p>\n <p>{info.pricing}{info.pricingUrl && <> <ExternalLink url={info.pricingUrl}\n label={provider.label + ' 费用说明'}>{provider.id === 'ddg' ? '官网' : '费用说明'}</ExternalLink></>}</p>\n </div>\n {participating && !provider.configured && <p className=\"web-search-meta\">{needsKey ? '待配置 Key' : '暂不可用'}</p>}\n {shared && participating && <small>{provider.id === 'zhihu-global' ? '与「知乎资料」共用 Access Secret' : '与模型设置共用 Key'}</small>}\n {showKey ? <label>API Key\n <input type=\"password\" autoComplete=\"off\" spellCheck={false}\n disabled={busy || writable[provider.credentialRef ?? ''] === false}\n value={keys[provider.id] ?? ''}\n placeholder={provider.configured ? '已配置,留空不替换' : '请输入 API Key'}\n onChange={event => setKeys(current => ({ ...current, [provider.id]: event.target.value }))} />\n </label> : null}\n {needsKey && !shared && provider.configured ? <ConfirmButton\n disabled={busy || writable[provider.credentialRef ?? ''] === false}\n label=\"删除 Key\" confirmLabel=\"确认删除 Key?\"\n onConfirm={() => void action(() => removeKey(provider))} /> : null}\n </div>\n <div className=\"web-search-rank-move\">\n <button\n type=\"button\"\n role=\"switch\"\n className={`web-search-switch${participating ? ' is-on' : ''}`}\n aria-checked={participating}\n aria-label={`${participating ? '关闭' : '开启'} ${provider.label}`}\n disabled={busy}\n onClick={() => {\n const next = participating ? order.filter(id => id !== provider.id) : [...order, provider.id]\n void action(() => persist(nextSearchEnabled(status.settings.searchEnabled, next, status.providers, keys, writable), next))\n }}>\n <span className=\"web-search-switch-thumb\" aria-hidden=\"true\" />\n </button>\n </div>\n </li>\n })}\n </ol>\n <p className=\"web-search-meta\">价格核对于 {PROVIDER_PRICING_CHECKED},实际额度与费用以供应商账户为准。</p>\n {on ? <div className=\"web-search-card-actions\">\n <button type=\"button\" disabled={busy} onClick={() => void action(async () => {\n await persist(status.settings.searchEnabled, order)\n const result = await call<{ sources: number }>('test')\n await load()\n setNote(`连接正常,返回 ${result.sources} 条来源。`)\n })}>测试连接(可能计费)</button>\n </div> : null}\n </article>}\n </div>\n <div role=\"tabpanel\" id={tabsId + '-limits-panel'} aria-labelledby={tabsId + '-limits-tab'}\n hidden={tab !== 'limits'} tabIndex={0}>\n <fieldset className=\"web-search-limits\" disabled={busy} aria-label=\"请求限制\">\n {limits.map(([key, label, min, max]) => (\n <label key={key}>{label}\n <input type=\"number\" min={min} max={max} step={1} value={draft[key]}\n onChange={event => setDraft({ ...draft, [key]: Number(event.target.value) })} />\n </label>\n ))}\n </fieldset>\n </div>\n <button type=\"button\" disabled={busy} onClick={() => void action(async () => {\n await persist(status.settings.searchEnabled, order)\n setNote('已保存。')\n })}>保存</button>\n </section>\n}\n\nconst styles = `\n.web-search-settings{max-width:760px;display:grid;gap:24px;color:inherit;font:400 var(--font-size-2,14px)/1.5 var(--default-font-family,system-ui,sans-serif)}\n.web-search-settings p{margin:0;line-height:1.5}\n.web-search-settings small,.web-search-meta,.web-search-empty{font-size:var(--font-size-1,13px);color:var(--gray-11,inherit)}\n.web-search-error{color:var(--red-11,#b42318)}\n.web-search-card{display:grid;gap:12px;padding:16px;border:1px solid var(--gray-6,color-mix(in srgb,currentColor 15%,transparent));border-radius:12px}\n.web-search-card header{display:flex;justify-content:space-between;gap:12px;align-items:center;padding-bottom:8px}\n.web-search-card h3{margin:0;font-size:var(--font-size-3,16px);font-weight:600}\n.web-search-provider{display:grid;gap:8px;min-width:0}\n.web-search-provider-info{display:grid;gap:3px;font-size:var(--font-size-1,13px);color:var(--gray-11,inherit);overflow-wrap:anywhere}\n.web-search-provider-heading{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;min-height:24px}\n.web-search-provider-heading strong{font-weight:500}\n.web-search-card label{display:grid;gap:6px}\n.web-search-settings input{box-sizing:border-box;width:100%;min-width:0;padding:8px 10px;border:1px solid var(--gray-6,color-mix(in srgb,currentColor 22%,transparent));border-radius:8px;background:var(--color-surface,transparent);color:inherit;font:inherit;transition:border-color 150ms ease,box-shadow 150ms ease}\n.web-search-settings input:focus{border-color:var(--accent-9,#3b82f6);box-shadow:0 0 0 2px color-mix(in srgb,var(--accent-9,#3b82f6) 25%,transparent)}\n.web-search-settings button:not([role=\"switch\"]){min-height:34px;padding:6px 12px;border:1px solid color-mix(in srgb,currentColor 25%,transparent);border-radius:8px;background:transparent;color:inherit;cursor:pointer;font:inherit;justify-self:start;transition:background-color 150ms ease,color 150ms ease,border-color 150ms ease,box-shadow 150ms ease,transform 150ms ease}\n.web-search-settings button:not([role=\"switch\"]):not([role=\"tab\"]):not(.web-search-drag-handle):hover:not(:disabled){background:var(--gray-3,color-mix(in srgb,currentColor 6%,transparent));border-color:color-mix(in srgb,currentColor 35%,transparent)}\n.web-search-settings button:not([role=\"switch\"]):not([role=\"tab\"]):not(.web-search-drag-handle):active:not(:disabled){transform:scale(.97)}\n.web-search-danger:hover:not(:disabled){border-color:var(--red-11,#b42318);color:var(--red-11,#b42318);background:color-mix(in srgb,var(--red-11,#b42318) 8%,transparent)}\n.web-search-settings button:disabled{opacity:.45;cursor:not-allowed}\n.web-search-tabs{display:flex;gap:20px;border-bottom:1px solid var(--gray-6,color-mix(in srgb,currentColor 15%,transparent))}\n.web-search-tabs button[role=\"tab\"]{padding:8px 0;border:0;border-bottom:2px solid transparent;border-radius:0;color:var(--gray-11,inherit)}\n.web-search-tabs button[role=\"tab\"]:hover{color:inherit}\n.web-search-tabs button[aria-selected=\"true\"]{border-bottom-color:var(--accent-9,#3b82f6);color:var(--accent-11,inherit);font-weight:600}\n.web-search-settings [role=\"tabpanel\"]{border-radius:12px}\n.web-search-settings .web-search-switch{all:unset;box-sizing:border-box;position:relative;display:inline-block;width:36px;height:20px;flex:none;border-radius:999px;background:var(--gray-7,color-mix(in srgb,currentColor 28%,transparent));cursor:pointer}\n.web-search-switch.is-on{background:var(--accent-9,#3b82f6)}\n.web-search-switch:disabled{cursor:not-allowed;opacity:.7}\n.web-search-switch-thumb{position:absolute;top:3px;left:3px;width:14px;height:14px;border-radius:999px;background:#fff;transition:transform 150ms ease}\n.web-search-switch.is-on .web-search-switch-thumb{transform:translateX(16px)}\n.web-search-settings :focus-visible{outline:2px solid var(--accent-9,currentColor);outline-offset:3px}\n.web-search-card-actions{display:flex;flex-wrap:wrap;gap:8px}\n.web-search-rank{margin:0;padding:0;list-style:none}\n.web-search-rank li{position:relative;display:grid;grid-template-columns:24px minmax(0,1fr) auto;gap:10px;align-items:start;padding:14px 0;border-top:1px solid var(--gray-6,color-mix(in srgb,currentColor 15%,transparent))}\n.web-search-settings button.web-search-drag-handle{display:grid;place-items:center;width:24px;height:24px;min-height:24px;padding:0;border:0;cursor:grab;color:inherit;opacity:.65}\n.web-search-settings button.web-search-drag-handle:hover{opacity:1;background:var(--gray-3,color-mix(in srgb,currentColor 8%,transparent))}\n.web-search-settings button.web-search-drag-handle:active{cursor:grabbing}\n.web-search-settings button.web-search-drag-handle[aria-disabled=\"true\"]{cursor:wait;opacity:.4}\n.web-search-rank li[data-dragging]{opacity:.45}\n.web-search-rank li[data-drop]::after{content:'';position:absolute;left:0;right:0;height:2px;background:var(--accent-9,#3b82f6);pointer-events:none}\n.web-search-rank li[data-drop=\"before\"]::after{top:-1px}\n.web-search-rank li[data-drop=\"after\"]::after{bottom:-1px}\n.web-search-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}\n.web-search-rank-index{opacity:.6;font-variant-numeric:tabular-nums;line-height:24px}\n.web-search-rank-move{display:flex;flex-wrap:wrap;gap:6px;align-items:center;justify-content:flex-end;padding-top:2px}\n.web-search-settings a{color:var(--accent-11,currentColor);font-size:var(--font-size-1,13px);text-underline-offset:3px}\n.web-search-limits{margin:0;border:0;padding:0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px 20px}\n.web-search-limits label{display:grid;gap:6px;font-size:var(--font-size-2,14px)}\n@media(max-width:560px){.web-search-rank-move{flex-direction:column;align-items:flex-end}.web-search-limits{grid-template-columns:minmax(0,1fr)}}\n@media(prefers-reduced-motion:reduce){.web-search-switch-thumb{transition:none}.web-search-settings button:not([role=\"switch\"]),.web-search-settings input{transition:none}}\n`\n\nexport function apply(ctx: Context): void {\n const client = ctx as unknown as Client\n ctx.effect(() => {\n if (typeof document === 'undefined') return () => {}\n const style = document.createElement('style')\n style.setAttribute('data-plugin', '@klarkxy/dsh-web-search-manager'); style.textContent = styles\n style.dataset.dshWebSearch = ''; document.head.appendChild(style)\n return () => style.remove()\n }, 'web-search-manager.styles')\n ctx.effect(() => client.slots.inject('settings.section', () => client.slots.register({\n name: 'settings.section', id: 'web-search', order: 70, label: '网络搜索',\n }, () => <NetworkSearchSettings client={client} />)), 'web-search-manager.settings')\n}\n"],"mappings":"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"}
package/lib/client.js CHANGED
@@ -297,6 +297,31 @@ function canEnableSearch(order, providers, keys, writable) {
297
297
  function nextSearchEnabled(currentlyEnabled, nextOrder, providers, keys, writable) {
298
298
  return currentlyEnabled && canEnableSearch(nextOrder, providers, keys, writable);
299
299
  }
300
+ function ConfirmButton(props) {
301
+ const [armed, setArmed] = (0, react.useState)(false);
302
+ const timer = (0, react.useRef)(void 0);
303
+ (0, react.useEffect)(() => () => clearTimeout(timer.current), []);
304
+ function disarm() {
305
+ clearTimeout(timer.current);
306
+ setArmed(false);
307
+ }
308
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
309
+ type: "button",
310
+ className: "web-search-danger",
311
+ disabled: props.disabled,
312
+ onClick: () => {
313
+ if (!armed) {
314
+ setArmed(true);
315
+ timer.current = setTimeout(() => setArmed(false), 3e3);
316
+ return;
317
+ }
318
+ disarm();
319
+ props.onConfirm();
320
+ },
321
+ onBlur: disarm,
322
+ children: armed ? props.confirmLabel : props.label
323
+ });
324
+ }
300
325
  function NetworkSearchSettings({ client }) {
301
326
  const tabsId = (0, react.useId)();
302
327
  const [tab, setTab] = (0, react.useState)("providers");
@@ -363,12 +388,16 @@ function NetworkSearchSettings({ client }) {
363
388
  [provider.id]: ""
364
389
  } : current);
365
390
  }
391
+ const bounded = (key) => {
392
+ const row = limits.find((item) => item[0] === key);
393
+ return Math.min(row[3], Math.max(row[2], Number.isFinite(draft[key]) ? draft[key] : row[2]));
394
+ };
366
395
  await update({
367
396
  ...editable(status.settings),
368
- maxResults: draft.maxResults,
369
- maxQueries: draft.maxQueries,
370
- timeoutMs: draft.timeoutMs,
371
- maxFetchChars: draft.maxFetchChars,
397
+ maxResults: bounded("maxResults"),
398
+ maxQueries: bounded("maxQueries"),
399
+ timeoutMs: bounded("timeoutMs"),
400
+ maxFetchChars: bounded("maxFetchChars"),
372
401
  endpoints: draft.endpoints,
373
402
  searchOrder: order,
374
403
  searchEnabled: on,
@@ -392,13 +421,13 @@ function NetworkSearchSettings({ client }) {
392
421
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
393
422
  role: "status",
394
423
  children: error || "正在读取设置…"
395
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
424
+ }), error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
396
425
  type: "button",
397
426
  onClick: () => void action(async () => {
398
427
  await load();
399
428
  }),
400
429
  children: "重新连接"
401
- })]
430
+ }) : null]
402
431
  });
403
432
  const enabled = searchBackends(status);
404
433
  const backends = catalogSearchBackends(status);
@@ -674,11 +703,11 @@ function NetworkSearchSettings({ client }) {
674
703
  [provider.id]: event.target.value
675
704
  }))
676
705
  })] }) : null,
677
- needsKey && !shared && provider.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
678
- type: "button",
706
+ needsKey && !shared && provider.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmButton, {
679
707
  disabled: busy || writable[provider.credentialRef ?? ""] === false,
680
- onClick: () => void action(() => removeKey(provider)),
681
- children: "删除 Key"
708
+ label: "删除 Key",
709
+ confirmLabel: "确认删除 Key?",
710
+ onConfirm: () => void action(() => removeKey(provider))
682
711
  }) : null
683
712
  ]
684
713
  }),
@@ -770,7 +799,7 @@ const styles = `
770
799
  .web-search-settings p{margin:0;line-height:1.5}
771
800
  .web-search-settings small,.web-search-meta,.web-search-empty{font-size:var(--font-size-1,13px);color:var(--gray-11,inherit)}
772
801
  .web-search-error{color:var(--red-11,#b42318)}
773
- .web-search-card{display:grid;gap:12px}
802
+ .web-search-card{display:grid;gap:12px;padding:16px;border:1px solid var(--gray-6,color-mix(in srgb,currentColor 15%,transparent));border-radius:12px}
774
803
  .web-search-card header{display:flex;justify-content:space-between;gap:12px;align-items:center;padding-bottom:8px}
775
804
  .web-search-card h3{margin:0;font-size:var(--font-size-3,16px);font-weight:600}
776
805
  .web-search-provider{display:grid;gap:8px;min-width:0}
@@ -778,18 +807,24 @@ const styles = `
778
807
  .web-search-provider-heading{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;min-height:24px}
779
808
  .web-search-provider-heading strong{font-weight:500}
780
809
  .web-search-card label{display:grid;gap:6px}
781
- .web-search-settings input{box-sizing:border-box;width:100%;min-width:0;padding:8px 10px;border:1px solid var(--gray-6,color-mix(in srgb,currentColor 22%,transparent));border-radius:6px;background:var(--color-surface,transparent);color:inherit;font:inherit}
782
- .web-search-settings button:not([role="switch"]){min-height:34px;padding:6px 12px;border:1px solid color-mix(in srgb,currentColor 25%,transparent);border-radius:6px;background:transparent;color:inherit;cursor:pointer;font:inherit;justify-self:start}
810
+ .web-search-settings input{box-sizing:border-box;width:100%;min-width:0;padding:8px 10px;border:1px solid var(--gray-6,color-mix(in srgb,currentColor 22%,transparent));border-radius:8px;background:var(--color-surface,transparent);color:inherit;font:inherit;transition:border-color 150ms ease,box-shadow 150ms ease}
811
+ .web-search-settings input:focus{border-color:var(--accent-9,#3b82f6);box-shadow:0 0 0 2px color-mix(in srgb,var(--accent-9,#3b82f6) 25%,transparent)}
812
+ .web-search-settings button:not([role="switch"]){min-height:34px;padding:6px 12px;border:1px solid color-mix(in srgb,currentColor 25%,transparent);border-radius:8px;background:transparent;color:inherit;cursor:pointer;font:inherit;justify-self:start;transition:background-color 150ms ease,color 150ms ease,border-color 150ms ease,box-shadow 150ms ease,transform 150ms ease}
813
+ .web-search-settings button:not([role="switch"]):not([role="tab"]):not(.web-search-drag-handle):hover:not(:disabled){background:var(--gray-3,color-mix(in srgb,currentColor 6%,transparent));border-color:color-mix(in srgb,currentColor 35%,transparent)}
814
+ .web-search-settings button:not([role="switch"]):not([role="tab"]):not(.web-search-drag-handle):active:not(:disabled){transform:scale(.97)}
815
+ .web-search-danger:hover:not(:disabled){border-color:var(--red-11,#b42318);color:var(--red-11,#b42318);background:color-mix(in srgb,var(--red-11,#b42318) 8%,transparent)}
783
816
  .web-search-settings button:disabled{opacity:.45;cursor:not-allowed}
784
817
  .web-search-tabs{display:flex;gap:20px;border-bottom:1px solid var(--gray-6,color-mix(in srgb,currentColor 15%,transparent))}
785
818
  .web-search-tabs button[role="tab"]{padding:8px 0;border:0;border-bottom:2px solid transparent;border-radius:0;color:var(--gray-11,inherit)}
819
+ .web-search-tabs button[role="tab"]:hover{color:inherit}
786
820
  .web-search-tabs button[aria-selected="true"]{border-bottom-color:var(--accent-9,#3b82f6);color:var(--accent-11,inherit);font-weight:600}
821
+ .web-search-settings [role="tabpanel"]{border-radius:12px}
787
822
  .web-search-settings .web-search-switch{all:unset;box-sizing:border-box;position:relative;display:inline-block;width:36px;height:20px;flex:none;border-radius:999px;background:var(--gray-7,color-mix(in srgb,currentColor 28%,transparent));cursor:pointer}
788
823
  .web-search-switch.is-on{background:var(--accent-9,#3b82f6)}
789
824
  .web-search-switch:disabled{cursor:not-allowed;opacity:.7}
790
825
  .web-search-switch-thumb{position:absolute;top:3px;left:3px;width:14px;height:14px;border-radius:999px;background:#fff;transition:transform 150ms ease}
791
826
  .web-search-switch.is-on .web-search-switch-thumb{transform:translateX(16px)}
792
- .web-search-settings :focus-visible{outline:2px solid currentColor;outline-offset:3px}
827
+ .web-search-settings :focus-visible{outline:2px solid var(--accent-9,currentColor);outline-offset:3px}
793
828
  .web-search-card-actions{display:flex;flex-wrap:wrap;gap:8px}
794
829
  .web-search-rank{margin:0;padding:0;list-style:none}
795
830
  .web-search-rank li{position:relative;display:grid;grid-template-columns:24px minmax(0,1fr) auto;gap:10px;align-items:start;padding:14px 0;border-top:1px solid var(--gray-6,color-mix(in srgb,currentColor 15%,transparent))}
@@ -808,7 +843,7 @@ const styles = `
808
843
  .web-search-limits{margin:0;border:0;padding:0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px 20px}
809
844
  .web-search-limits label{display:grid;gap:6px;font-size:var(--font-size-2,14px)}
810
845
  @media(max-width:560px){.web-search-rank-move{flex-direction:column;align-items:flex-end}.web-search-limits{grid-template-columns:minmax(0,1fr)}}
811
- @media(prefers-reduced-motion:reduce){.web-search-switch-thumb{transition:none}}
846
+ @media(prefers-reduced-motion:reduce){.web-search-switch-thumb{transition:none}.web-search-settings button:not([role="switch"]),.web-search-settings input{transition:none}}
812
847
  `;
813
848
  function apply(ctx) {
814
849
  const client = ctx;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@klarkxy/dsh-web-search-manager",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Opt-in web search settings and managed provider lifecycle over DSH ctx.web",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
@@ -67,23 +67,23 @@
67
67
  ]
68
68
  },
69
69
  "dependencies": {
70
- "@deepseek-ai/dsh-storage-domain": "0.1.5-rc.2",
71
- "@deepseek-ai/dsh-web-search-deepseek": "0.1.5-rc.2",
72
- "@deepseek-ai/dsh-web-search-exa": "0.1.5-rc.2",
73
- "@deepseek-ai/dsh-web-fetch-http": "0.1.5-rc.2",
70
+ "@deepseek-ai/dsh-storage-domain": "0.1.7-alpha.1",
71
+ "@deepseek-ai/dsh-web-search-deepseek": "0.1.7-alpha.1",
72
+ "@deepseek-ai/dsh-web-search-exa": "0.1.7-alpha.1",
73
+ "@deepseek-ai/dsh-web-fetch-http": "0.1.7-alpha.1",
74
74
  "zod": "^4.1.5"
75
75
  },
76
76
  "peerDependencies": {
77
- "@deepseek-ai/cordis": "^4.0.2",
78
- "@deepseek-ai/dsh-credentials": "0.1.5-rc.2",
79
- "@deepseek-ai/dsh-web": "0.1.5-rc.2",
80
- "@deepseek-ai/dsh-tool-web": "0.1.5-rc.2"
77
+ "@deepseek-ai/cordis": "^4.0.3",
78
+ "@deepseek-ai/dsh-credentials": "0.1.7-alpha.1",
79
+ "@deepseek-ai/dsh-web": "0.1.7-alpha.1",
80
+ "@deepseek-ai/dsh-tool-web": "0.1.7-alpha.1"
81
81
  },
82
82
  "devDependencies": {
83
- "@deepseek-ai/cordis": "^4.0.2",
84
- "@deepseek-ai/dsh-credentials": "0.1.5-rc.2",
85
- "@deepseek-ai/dsh-web": "0.1.5-rc.2",
86
- "@deepseek-ai/dsh-tool-web": "0.1.5-rc.2",
83
+ "@deepseek-ai/cordis": "^4.0.3",
84
+ "@deepseek-ai/dsh-credentials": "0.1.7-alpha.1",
85
+ "@deepseek-ai/dsh-web": "0.1.7-alpha.1",
86
+ "@deepseek-ai/dsh-tool-web": "0.1.7-alpha.1",
87
87
  "react": "^18.2.0",
88
88
  "@types/react": "18.3.31"
89
89
  },
@@ -109,7 +109,7 @@
109
109
  "registry": "https://registry.npmjs.org/"
110
110
  },
111
111
  "dshRelease": {
112
- "contentHash": "sha256-37ddd81206a74bc67ae4cfebce7cbe71baf3434c6c32d447d2ca58a5f4be4c6c"
112
+ "contentHash": "sha256-72ad10649aa4bc6ea62a71cfe9aef9714b17f702a322a8f31e761a0cdd00c344"
113
113
  },
114
114
  "scripts": {
115
115
  "build": "tsdown && node ../../scripts/wrap-client.mjs @klarkxy/dsh-web-search-manager",