dsh-free-search 0.5.6 → 0.6.5
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 +28 -4
- package/cordis.patch.yml +2 -1
- package/lib/client.js +212 -17
- package/lib/index.js +390 -212
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -29,10 +29,12 @@ dsh 默认的搜索 provider 依赖 DeepSeek 官方 API key(`DEEPSEEK_API_KEY`
|
|
|
29
29
|
### 特性
|
|
30
30
|
|
|
31
31
|
- **零成本** —— 多个免费引擎,无需 key、无需注册
|
|
32
|
-
- **多引擎可选**:DuckDuckGo(html/lite)、Bing、SearXNG(元搜索,支持自定义实例)、AnySearch、Exa、Tavily、Keenable、Firecrawl、Parallel、Perplexity、SerpBase、DeepSeek 官方、You.com
|
|
32
|
+
- **多引擎可选**:DuckDuckGo(html/lite)、Bing、SearXNG(元搜索,支持自定义实例)、AnySearch、Exa、Tavily、Keenable、Firecrawl、Parallel、Perplexity、SerpBase、DeepSeek 官方、You.com、百度千帆、Kimi、阿里云百炼、火山联网搜索(豆包搜索)
|
|
33
33
|
- **网页设置页** —— 引擎切换 + API key 配置(UI 中 key 脱敏显示"已配置")+ 中英文切换;入口在左侧「插件」页的组件行配置(`plugins.row.config`,DSH 0.1.7-rc.1+)
|
|
34
34
|
- **弹出式切换命令** —— 聊天框输入 `/free-search-engine`,弹出引擎选择窗口,点选即切换(等效设置页 + 保存)
|
|
35
35
|
- **引擎测试** —— `free_search_test` 工具让 agent 一键测试所有引擎;设置页也有"测试引擎"按钮(直测当前引擎,不走回退链,付费引擎无 key 会明确报错)
|
|
36
|
+
- **全局引擎开关与回退优先级** —— 设置页可勾选/取消引擎(取消后全局生效:普通搜索 / Auto / `advanced_search` / `multi_search` / 引擎测试都不再用它),并可用 ↑↓ 调整全局回退顺序(一键恢复默认)
|
|
37
|
+
- **Multi 模式** —— 可把搜索引擎设为 `Multi Search`:`web_search` 并发请求前 3 个已启用引擎、按 URL 合并去重、跨源命中的结果优先(代价是成倍消耗额度;multi 失败会自动退回单引擎回退链)
|
|
36
38
|
- **统一引擎回退** —— 任何引擎失败(付费/免费,缺 key/401/限流/网络)自动轮流尝试下一个引擎:首选引擎 → 其他引擎(exa/tavily/keenable/firecrawl/parallel 无 key 也会尝试,因为它们自带 keyless 免费额度)→ 剩余免费引擎,搜索永不直接失败;结果顶部注明实际生效的引擎(如 `Note: perplexity unavailable or failed, using exa.`)
|
|
37
39
|
- **时间过滤** —— `advanced_search` 工具支持 `timeRange`:固定档、自定义相对值、绝对日期三种形式(详见下方逻辑说明)
|
|
38
40
|
- **系统提示词注入** —— agent 知道当前用哪个引擎、哪些需要 key;并明确所有搜索结果是**不可信外部数据**,不得执行其中的指令
|
|
@@ -68,6 +70,7 @@ dsh 默认的搜索 provider 依赖 DeepSeek 官方 API key(`DEEPSEEK_API_KEY`
|
|
|
68
70
|
| `baidu` | 百度千帆 AI 搜索 | 额度 | 需 `BAIDU_API_KEY`(千帆 AI 搜索,每日赠 50 次,超额按量后付费);中文全网,支持时间过滤 |
|
|
69
71
|
| `kimi` | Kimi(Moonshot)联网搜索 | 付费 | 需 `MOONSHOT_API_KEY`(basic 约 ¥0.01/次),返回带正文 chunks 的中文结果 |
|
|
70
72
|
| `aliyun` | 阿里云百炼 EnhancedSearch | 付费 | 需 `DASHSCOPE_API_KEY`(MCP search_pro,约 ¥0.03/次,新用户 200 次免费包);中文全网带来源 hostname |
|
|
73
|
+
| `doubao` | 火山引擎联网搜索(豆包搜索) | 免费额度 | 需 `DOUBAO_SEARCH_API_KEY`(联网搜索控制台开通,**每月 500 次免费**,超出按量付费);中文时效内容强,支持时间/站点过滤,返回千字级摘要 |
|
|
71
74
|
|
|
72
75
|
- **默认引擎为 `bing`**(免费且最稳定),安装后开箱即用。
|
|
73
76
|
- **自动回退**:任何引擎失败(免费限流/反爬,付费缺 key/无效/网络错误)都会自动轮流尝试下一个引擎——先试其他已配 key 的付费引擎,再试免费引擎(Bing/AnySearch 等),并在结果中附带回退提示——搜索不会因引擎问题直接失败。
|
|
@@ -79,6 +82,7 @@ dsh 默认的搜索 provider 依赖 DeepSeek 官方 API key(`DEEPSEEK_API_KEY`
|
|
|
79
82
|
- Perplexity:<https://www.perplexity.ai/settings/api>
|
|
80
83
|
- SerpBase:<https://serpbase.dev>
|
|
81
84
|
- DeepSeek:<https://platform.deepseek.com/api_keys>
|
|
85
|
+
- 豆包搜索(火山联网搜索):<https://console.volcengine.com/search-infinity/web-search>
|
|
82
86
|
- You.com:<https://you.com/platform/api-keys>
|
|
83
87
|
|
|
84
88
|
#### 为什么免费引擎不需要 key?
|
|
@@ -184,6 +188,14 @@ dsh web
|
|
|
184
188
|
|
|
185
189
|
命令只改首选引擎配置,搜索仍走 `web_search` + 统一回退链:即使首选引擎挂了也会自动换其他引擎,永不直接失败。系统提示词同步刷新。
|
|
186
190
|
|
|
191
|
+
#### 引擎开关、回退优先级与 Multi 模式
|
|
192
|
+
|
|
193
|
+
设置页有三个新块(都会保存进条目 config):
|
|
194
|
+
|
|
195
|
+
- **全局启用的搜索引擎**:取消勾选后,该引擎会从普通 `web_search` 回退链、Auto 智能路由、`advanced_search`、`multi_search` 和引擎测试中**全局排除**;至少要保留一个引擎。
|
|
196
|
+
- **全局回退优先级**:用 ↑↓ 调整先后顺序。首选引擎仍先尝试;Auto 保留"语言/时间"路由规则,但自定义后同一分组内及后续回退按此顺序。被禁用的引擎保留在列表里(标记「已禁用」)但不执行;「恢复默认顺序」一键还原。
|
|
197
|
+
- **搜索引擎下拉里的 `Multi Search`**:选中后 `web_search` 会并发查询路由/优先级前 3 个已启用引擎,URL 去重合并、跨源命中的结果排前面。注意并发会成倍消耗额度;Multi 失败时会自动退回普通单引擎回退链并在结果里注明。
|
|
198
|
+
|
|
187
199
|
#### 配置文件
|
|
188
200
|
|
|
189
201
|
DSH 0.1.7-rc.1 起,配置跟随 profile 的插件条目保存:设置页与 `/free-search-engine` 都会写入当前 profile 的 `cordis.patch.yml` 中 `web-search-free`(`dsh-free-search`)条目的 `config`。
|
|
@@ -192,7 +204,7 @@ DSH 0.1.7-rc.1 起,配置跟随 profile 的插件条目保存:设置页与 `
|
|
|
192
204
|
|
|
193
205
|
```yaml
|
|
194
206
|
# profiles/<profile>/cordis.patch.yml 中该条目的 config:
|
|
195
|
-
provider: bing # ddg / ddg-lite / bing / searxng / anysearch / exa / tavily / keenable / firecrawl / parallel / perplexity / serpbase / deepseek-official / you
|
|
207
|
+
provider: bing # ddg / ddg-lite / bing / searxng / anysearch / exa / tavily / keenable / firecrawl / parallel / perplexity / serpbase / deepseek-official / you / baidu / kimi / aliyun / doubao / auto / multi
|
|
196
208
|
lang: zh # 设置页界面语言(zh / en)
|
|
197
209
|
bingMarket: zh-CN # Bing 市场
|
|
198
210
|
region: cn-zh # DuckDuckGo 区域(可选)
|
|
@@ -340,10 +352,12 @@ This plugin provides multiple free search engines with automatic fallback, compl
|
|
|
340
352
|
### Features
|
|
341
353
|
|
|
342
354
|
- **Zero Cost** — Multiple free engines with no API key or registration required
|
|
343
|
-
- **Multi-Engine Support** — DuckDuckGo (HTML / Lite), Bing, AnySearch AI, SearXNG (meta-search with custom instances), Exa, Tavily, Keenable, Firecrawl, Parallel, Perplexity, SerpBase, DeepSeek Official,
|
|
355
|
+
- **Multi-Engine Support** — DuckDuckGo (HTML / Lite), Bing, AnySearch AI, SearXNG (meta-search with custom instances), Exa, Tavily, Keenable, Firecrawl, Parallel, Perplexity, SerpBase, DeepSeek Official, You.com, Baidu Qianfan, Kimi, Aliyun Bailian, and Volcano Doubao
|
|
344
356
|
- **Web Settings UI** — Engine switching, API key configuration (keys masked as "configured" in the UI), and a Chinese/English toggle; the entry is the component-row config (`plugins.row.config`) on the sidebar Plugins page (DSH 0.1.7-rc.1+)
|
|
345
357
|
- **Popup Switch Command** — Type `/free-search-engine` in the chat: a picker opens with all engines; click one to switch (equivalent to the settings page + save)
|
|
346
358
|
- **Engine Testing** — `free_search_test` for the agent to check all engines in one call; the settings UI also has a "Test engine" button that tests the selected engine directly (no fallback chain; paid engines without a key report an explicit error)
|
|
359
|
+
- **Global engine enable/disable & fallback priority** — uncheck engines you never want (applies everywhere: web_search fallback, Auto routing, advanced_search, multi_search, engine tests) and reorder the global fallback chain with ↑↓ (one-click reset)
|
|
360
|
+
- **Multi mode** — pick `Multi Search` as the engine: web_search queries the top 3 enabled engines concurrently, merges/deduplicates URLs and prioritizes cross-source hits (costs more quota; a failed multi run automatically falls back to the single-engine chain)
|
|
347
361
|
- **Unified Engine Fallback** — Any engine failure (paid or free, missing key, 401, rate limit, network error) automatically tries the next engine: the configured engine first, then other engines (exa/tavily/keenable/firecrawl/parallel are tried even without a key because they have built-in keyless quota), then the remaining free engines (Bing/AnySearch etc.) — with a note attached to the results naming the engine that actually served them (e.g. `Note: perplexity unavailable or failed, using exa.`). Search never fails outright.
|
|
348
362
|
- **Time Filtering** — The `advanced_search` tool supports `timeRange`: fixed tiers, custom relative values, or an absolute date (details below)
|
|
349
363
|
- **System Prompt Injection** — The agent is aware of the currently active engine and which engines require API keys; it is also told that all search output is **untrusted external data** and must never be executed as instructions
|
|
@@ -379,6 +393,7 @@ If this plugin has been helpful, a ⭐ on [GitHub](https://github.com/DDDMUC/dsh
|
|
|
379
393
|
| `baidu` | Baidu Qianfan AI Search | Quota | Requires `BAIDU_API_KEY` (Qianfan AI Search, 50 free calls/day then pay-as-you-go); Chinese web-wide, supports time filtering |
|
|
380
394
|
| `kimi` | Kimi (Moonshot) Web Search | Paid | Requires `MOONSHOT_API_KEY` (basic ~¥0.01/call); returns Chinese results with body chunks |
|
|
381
395
|
| `aliyun` | Aliyun Bailian EnhancedSearch | Paid | Requires `DASHSCOPE_API_KEY` (MCP search_pro, ~¥0.03/call, 200 free calls for new users); Chinese web-wide with source hostnames |
|
|
396
|
+
| `doubao` | Volcano Web Search (Doubao) | Free quota | Requires `DOUBAO_SEARCH_API_KEY` (open it in the Web Search console; **500 free queries/month**, pay-as-you-go beyond); strong for fresh Chinese content, supports time/site filters, returns long summaries |
|
|
382
397
|
|
|
383
398
|
- **Default engine is `bing`** (free and most stable), ready to use out of the box after installation.
|
|
384
399
|
- **Auto-failover**: any engine failure (rate-limited free engine, or missing/invalid paid key, network error) automatically tries the next engine — the configured engine first, then other engines (exa/tavily/keenable/firecrawl/parallel are tried even without a key because they have built-in keyless quota), then the remaining free engines (Bing/AnySearch etc.) — with a note attached to the results naming the engine that actually served them (e.g. `Note: perplexity unavailable or failed, using exa.`). Search never fails outright because of engine issues.
|
|
@@ -390,6 +405,7 @@ If this plugin has been helpful, a ⭐ on [GitHub](https://github.com/DDDMUC/dsh
|
|
|
390
405
|
- Perplexity: <https://www.perplexity.ai/settings/api>
|
|
391
406
|
- SerpBase: <https://serpbase.dev>
|
|
392
407
|
- DeepSeek: <https://platform.deepseek.com/api_keys>
|
|
408
|
+
- Doubao / Volcano Web Search: <https://console.volcengine.com/search-infinity/web-search>
|
|
393
409
|
- You.com: <https://you.com/platform/api-keys>
|
|
394
410
|
|
|
395
411
|
#### Why are some engines free?
|
|
@@ -495,6 +511,14 @@ You can also switch the engine right from the chat — no need to open the setti
|
|
|
495
511
|
|
|
496
512
|
The command only changes the preferred engine; search still goes through `web_search` + the unified fallback chain — even if the preferred engine fails, it automatically switches to others, never failing outright. The system prompt refreshes accordingly.
|
|
497
513
|
|
|
514
|
+
#### Engine Switches, Fallback Priority and Multi Mode
|
|
515
|
+
|
|
516
|
+
The settings page has three new blocks (all saved into the entry config):
|
|
517
|
+
|
|
518
|
+
- **Globally enabled engines**: unchecking an engine excludes it everywhere — the web_search fallback chain, Auto routing, `advanced_search`, `multi_search` and engine tests. At least one engine must remain enabled.
|
|
519
|
+
- **Global fallback priority**: use ↑↓ to reorder. The preferred engine is still tried first; Auto keeps its language/time routing, while a customized order governs engines inside a route group and the remaining fallback chain. Disabled engines stay in the list (marked "disabled") but are skipped; "Reset default order" restores the defaults.
|
|
520
|
+
- **`Multi Search` in the engine dropdown**: web_search then queries the top 3 enabled routed/prioritized engines concurrently, merges/deduplicates URLs and prioritizes cross-source hits. This multiplies quota usage; if a multi run fails it automatically falls back to the single-engine chain and says so in the result note.
|
|
521
|
+
|
|
498
522
|
#### Configuration File
|
|
499
523
|
|
|
500
524
|
Since DSH 0.1.7-rc.1 the configuration is stored with the profile's plugin entry: the settings page and `/free-search-engine` both write the `config` of the `web-search-free` (`dsh-free-search`) entry in the active profile's `cordis.patch.yml`.
|
|
@@ -503,7 +527,7 @@ The old `free-search:` section of `~/.dsh/settings.yaml` is **not** imported by
|
|
|
503
527
|
|
|
504
528
|
```yaml
|
|
505
529
|
# config of that entry in profiles/<profile>/cordis.patch.yml:
|
|
506
|
-
provider: bing # ddg / ddg-lite / bing / searxng / anysearch / exa / tavily / keenable / firecrawl / parallel / perplexity / serpbase / deepseek-official / you
|
|
530
|
+
provider: bing # ddg / ddg-lite / bing / searxng / anysearch / exa / tavily / keenable / firecrawl / parallel / perplexity / serpbase / deepseek-official / you / baidu / kimi / aliyun / doubao / auto / multi
|
|
507
531
|
lang: zh # settings UI language (zh / en)
|
|
508
532
|
bingMarket: zh-CN # Bing market
|
|
509
533
|
region: cn-zh # DuckDuckGo region (optional)
|
package/cordis.patch.yml
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# The engine is chosen from the sidebar Plugins page -> Free Search row config
|
|
5
5
|
# (plugins.row.config, DSH 0.1.7-rc.1+), from the /free-search-engine command,
|
|
6
6
|
# or directly via this entry's config, e.g.:
|
|
7
|
-
# provider: bing # engine id: ddg / bing / baidu / kimi / aliyun / auto / ...
|
|
7
|
+
# provider: bing # engine id: ddg / bing / baidu / kimi / aliyun / doubao / auto / multi / ...
|
|
8
8
|
# region: cn-zh # DuckDuckGo region
|
|
9
9
|
# bingMarket: zh-CN # Bing market
|
|
10
10
|
# A legacy ~/.dsh/settings.yaml "free-search:" section is migrated into this
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
name: dsh-free-search
|
|
20
20
|
config:
|
|
21
21
|
provider: bing
|
|
22
|
+
disabledEngines: []
|
|
22
23
|
bingMarket: zh-CN
|
|
23
24
|
# 自动接管默认搜索:让 harness 的 web_search 直接用本插件的 provider(内部含全部引擎)
|
|
24
25
|
# 注意 patch 语义是"整行替换 config"(DSH 0.1.2+):必须保留 fetchProvider,否则
|
package/lib/client.js
CHANGED
|
@@ -59,6 +59,15 @@ window.__ModuleLoader__.load({
|
|
|
59
59
|
".dshfs-platforms{display:flex;gap:12px 16px;flex-wrap:wrap}",
|
|
60
60
|
".dshfs-platform{display:flex;align-items:center;gap:6px;color:inherit;font-size:14px;cursor:pointer}",
|
|
61
61
|
".dshfs-platform input{accent-color:#0071e3;width:16px;height:16px}",
|
|
62
|
+
".dshfs-priorityList{display:flex;flex-direction:column;gap:6px}",
|
|
63
|
+
".dshfs-priorityRow{display:flex;align-items:center;gap:8px;padding:6px 8px;border:1px solid rgba(0,0,0,.08);border-radius:10px;background:rgba(0,0,0,.02)}",
|
|
64
|
+
"@media (prefers-color-scheme: dark){.dshfs-priorityRow{border-color:rgba(255,255,255,.09);background:rgba(255,255,255,.035)}}",
|
|
65
|
+
".dshfs-priorityRowDisabled{opacity:.58}",
|
|
66
|
+
".dshfs-priorityIndex{width:26px;flex:none;color:#6e6e73;font-size:12px;font-variant-numeric:tabular-nums;text-align:right}",
|
|
67
|
+
"@media (prefers-color-scheme: dark){.dshfs-priorityIndex{color:#a1a1a6}}",
|
|
68
|
+
".dshfs-priorityName{flex:1;min-width:0;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
|
|
69
|
+
".dshfs-priorityActions{display:flex;gap:4px;flex:none}",
|
|
70
|
+
".dshfs-priorityBtn{width:30px;height:28px;padding:0;font-size:14px}",
|
|
62
71
|
"@media (prefers-color-scheme: dark){.dshfs-platform input{accent-color:#0a84ff}}",
|
|
63
72
|
".dshfs-link{color:#0071e3;font-size:13px;text-decoration:none;align-self:flex-start;padding:2px 0;font-weight:500}",
|
|
64
73
|
".dshfs-link:hover{text-decoration:underline}",
|
|
@@ -80,6 +89,8 @@ window.__ModuleLoader__.load({
|
|
|
80
89
|
"@media (prefers-color-scheme: dark){.dshfs-badge{background:rgba(255,255,255,.1);color:#f5f5f7}}",
|
|
81
90
|
".dshfs-badgeAuto{background:rgba(175,82,222,.15);color:#af52de;border:1px solid rgba(175,82,222,.2)}",
|
|
82
91
|
"@media (prefers-color-scheme: dark){.dshfs-badgeAuto{background:rgba(191,90,242,.15);color:#bf5af2;border-color:rgba(191,90,242,.2)}}",
|
|
92
|
+
".dshfs-badgeMulti{background:rgba(0,122,255,.13);color:#007aff;border:1px solid rgba(0,122,255,.2)}",
|
|
93
|
+
"@media (prefers-color-scheme: dark){.dshfs-badgeMulti{background:rgba(10,132,255,.15);color:#0a84ff;border-color:rgba(10,132,255,.25)}}",
|
|
83
94
|
".dshfs-badgeFree{background:rgba(52,199,89,.15);color:#248a3d;border:1px solid rgba(52,199,89,.2)}",
|
|
84
95
|
"@media (prefers-color-scheme: dark){.dshfs-badgeFree{background:rgba(48,209,88,.15);color:#30d158;border-color:rgba(48,209,88,.2)}}",
|
|
85
96
|
".dshfs-badgeKey{background:rgba(255,149,0,.15);color:#cc7a00;border:1px solid rgba(255,149,0,.2)}",
|
|
@@ -122,7 +133,7 @@ window.__ModuleLoader__.load({
|
|
|
122
133
|
searchEngine: "搜索引擎",
|
|
123
134
|
visit: "访问官网 →",
|
|
124
135
|
getKey: "获取 API Key →",
|
|
125
|
-
engineHint: "
|
|
136
|
+
engineHint: "Multi Search 会并发搜索路由/优先级前 3 个已启用引擎并合并去重;Auto 根据查询语言及时间过滤自动优选单个引擎。Bing 是稳定的免费引擎。API KEY 引擎需在下方填写凭据。",
|
|
126
137
|
apiKeys: "API 密钥(可选)",
|
|
127
138
|
anysearchPh: (c) => c ? "AnySearch API 密钥(已配置)" : "AnySearch API 密钥(可选,不填免费匿名,填了提额)",
|
|
128
139
|
exaPh: (c) => c ? "Exa API 密钥(已配置)" : "Exa API 密钥(可选,不填也可免费使用)",
|
|
@@ -137,12 +148,19 @@ window.__ModuleLoader__.load({
|
|
|
137
148
|
baiduPh: (c) => c ? "Baidu API 密钥(已配置)" : "百度千帆 API 密钥(BAIDU_API_KEY)",
|
|
138
149
|
kimiPh: (c) => c ? "Kimi API 密钥(已配置)" : "Kimi API 密钥(MOONSHOT_API_KEY)",
|
|
139
150
|
aliyunPh: (c) => c ? "Aliyun API 密钥(已配置)" : "阿里云 DashScope 密钥(DASHSCOPE_API_KEY)",
|
|
151
|
+
doubaoPh: (c) => c ? "豆包搜索密钥(已配置)" : "豆包搜索 / 火山联网搜索密钥(DOUBAO_SEARCH_API_KEY,每月 500 次免费)",
|
|
140
152
|
keysHint: "密钥读取优先级:.credentials.yaml 凭据中心 > 这里 > 环境变量。推荐把 key 写进凭据中心(与官方 LLM 一致,一处管理)。",
|
|
141
153
|
keyStorage: "Key 存储位置",
|
|
142
154
|
keyStorageCred: "凭据中心(推荐)",
|
|
143
155
|
keyStorageSettings: "设置页(兼容)",
|
|
144
|
-
keyStorageCredHint: (c) => `保存后写入 ~/.dsh/.credentials.yaml(最高优先级)。当前已配置:${["anysearch", "exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek", "youcom", "baidu", "kimi", "aliyun"].filter((k) => c[k]).map((k) => k.toUpperCase()).join(", ") || "无"}`,
|
|
156
|
+
keyStorageCredHint: (c) => `保存后写入 ~/.dsh/.credentials.yaml(最高优先级)。当前已配置:${["anysearch", "exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek", "youcom", "baidu", "kimi", "aliyun", "doubao"].filter((k) => c[k]).map((k) => k.toUpperCase()).join(", ") || "无"}`,
|
|
145
157
|
keyStorageSettingsHint: "保存后写入当前 profile 的插件条目 config(cordis.patch.yml,优先级低于凭据中心)。",
|
|
158
|
+
engineEnable: "全局启用的搜索引擎",
|
|
159
|
+
engineEnableHint: "取消勾选后,该引擎会从普通 web_search 回退链、Auto 智能路由、advanced_search、multi_search 和引擎测试中全局排除。至少保留一个引擎。",
|
|
160
|
+
fallbackPriority: "全局回退优先级",
|
|
161
|
+
fallbackPriorityHint: "从上到下为回退顺序。首选引擎仍先尝试;Auto 保留语言/时间路由,但自定义后同一分组内及后续回退按此顺序。禁用引擎不会执行,但会保留当前位置。",
|
|
162
|
+
fallbackPriorityReset: "恢复默认顺序",
|
|
163
|
+
priorityDisabled: "已禁用",
|
|
146
164
|
platformSearch: "平台搜索(platform_search 工具)",
|
|
147
165
|
platformHint: "为 agent 的 platform_search 工具启用平台。禁用的平台会被跳过。",
|
|
148
166
|
cacheTtl: "结果缓存时长(分钟)",
|
|
@@ -195,7 +213,7 @@ window.__ModuleLoader__.load({
|
|
|
195
213
|
searchEngine: "Search engine",
|
|
196
214
|
visit: "Visit website →",
|
|
197
215
|
getKey: "Get API Key →",
|
|
198
|
-
engineHint: "
|
|
216
|
+
engineHint: "Multi Search queries the top 3 enabled routed/prioritized engines concurrently and merges/deduplicates results. Auto smartly routes a single engine by query language and time filter. API KEY engines need credentials below.",
|
|
199
217
|
apiKeys: "API keys (optional)",
|
|
200
218
|
anysearchPh: (c) => c ? "AnySearch API key (configured)" : "AnySearch API key (optional, free anonymous without; key raises quota)",
|
|
201
219
|
exaPh: (c) => c ? "Exa API key (configured)" : "Exa API key (optional, free without)",
|
|
@@ -210,12 +228,19 @@ window.__ModuleLoader__.load({
|
|
|
210
228
|
baiduPh: (c) => c ? "Baidu API key (configured)" : "Baidu Qianfan API key (BAIDU_API_KEY)",
|
|
211
229
|
kimiPh: (c) => c ? "Kimi API key (configured)" : "Kimi API key (MOONSHOT_API_KEY)",
|
|
212
230
|
aliyunPh: (c) => c ? "Aliyun API key (configured)" : "Aliyun DashScope API key (DASHSCOPE_API_KEY)",
|
|
231
|
+
doubaoPh: (c) => c ? "Doubao search key (configured)" : "Doubao / Volcano Web Search key (DOUBAO_SEARCH_API_KEY, 500 free/month)",
|
|
213
232
|
keysHint: "Key resolution: .credentials.yaml credential center > here > environment variables. Recommended: store keys in the credential center (same as official LLM providers, one place for all).",
|
|
214
233
|
keyStorage: "Key storage",
|
|
215
234
|
keyStorageCred: "Credential center (recommended)",
|
|
216
235
|
keyStorageSettings: "Settings page (legacy)",
|
|
217
|
-
keyStorageCredHint: (c) => `Saved to ~/.dsh/.credentials.yaml (highest priority). Currently configured: ${["anysearch", "exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek", "youcom", "baidu", "kimi", "aliyun"].filter((k) => c[k]).map((k) => k.toUpperCase()).join(", ") || "none"}`,
|
|
236
|
+
keyStorageCredHint: (c) => `Saved to ~/.dsh/.credentials.yaml (highest priority). Currently configured: ${["anysearch", "exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek", "youcom", "baidu", "kimi", "aliyun", "doubao"].filter((k) => c[k]).map((k) => k.toUpperCase()).join(", ") || "none"}`,
|
|
218
237
|
keyStorageSettingsHint: "Saved to the active profile plugin entry config (cordis.patch.yml; lower priority than the credential center).",
|
|
238
|
+
engineEnable: "Globally enabled search engines",
|
|
239
|
+
engineEnableHint: "Unchecked engines are excluded globally from web_search fallback, Auto routing, advanced_search, multi_search, and engine tests. At least one engine must remain enabled.",
|
|
240
|
+
fallbackPriority: "Global fallback priority",
|
|
241
|
+
fallbackPriorityHint: "Top to bottom is the fallback order. The preferred engine is still tried first. Auto keeps language/time routing, while customized priority orders engines within a route group and the remaining fallback chain. Disabled engines keep their position but are skipped.",
|
|
242
|
+
fallbackPriorityReset: "Reset default order",
|
|
243
|
+
priorityDisabled: "disabled",
|
|
219
244
|
platformSearch: "Platform search (platform_search tool)",
|
|
220
245
|
platformHint: "Enable platforms for the agent's platform_search tool. Disabled platforms are skipped.",
|
|
221
246
|
cacheTtl: "Result cache TTL (minutes)",
|
|
@@ -265,9 +290,10 @@ window.__ModuleLoader__.load({
|
|
|
265
290
|
};
|
|
266
291
|
const tt = (lang) => I18N[lang === "en" ? "en" : "zh"];
|
|
267
292
|
// 当前插件版本(与 lib/index.js 的 PLUGIN_VERSION 保持一致)
|
|
268
|
-
const PLUGIN_VERSION = "0.5
|
|
293
|
+
const PLUGIN_VERSION = "0.6.5";
|
|
269
294
|
const ENGINES = [
|
|
270
295
|
{ id: "auto", label: "Auto · 智能路由", badge: "AUTO", link: null },
|
|
296
|
+
{ id: "multi", label: "Multi Search · 多引擎", badge: "MULTI", link: null },
|
|
271
297
|
{ id: "ddg", label: "DuckDuckGo · HTML", badge: "FREE", link: "https://duckduckgo.com" },
|
|
272
298
|
{ id: "ddg-lite", label: "DuckDuckGo · Lite", badge: "FREE", link: "https://duckduckgo.com" },
|
|
273
299
|
{ id: "bing", label: "Bing", badge: "FREE", link: "https://www.bing.com" },
|
|
@@ -285,7 +311,14 @@ window.__ModuleLoader__.load({
|
|
|
285
311
|
{ id: "baidu", label: "Baidu · 百度千帆", badge: "API KEY", link: "https://console.bce.baidu.com/qianfan" },
|
|
286
312
|
{ id: "kimi", label: "Kimi · Moonshot", badge: "API KEY", link: "https://platform.moonshot.cn" },
|
|
287
313
|
{ id: "aliyun", label: "Aliyun · 百炼", badge: "API KEY", link: "https://bailian.console.aliyun.com" },
|
|
314
|
+
{ id: "doubao", label: "Doubao · 豆包搜索", badge: "API KEY", link: "https://console.volcengine.com/search-infinity/web-search" },
|
|
288
315
|
];
|
|
316
|
+
const ENGINE_IDS = ENGINES.filter((engine) => engine.id !== "auto" && engine.id !== "multi").map((engine) => engine.id);
|
|
317
|
+
const DEFAULT_FALLBACK_ORDER = ["exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek-official", "you", "baidu", "kimi", "aliyun", "doubao", "bing", "anysearch", "ddg", "ddg-lite", "searxng"];
|
|
318
|
+
const normalizeFallbackOrder = (value) => {
|
|
319
|
+
const configured = Array.isArray(value) ? value.filter((id) => ENGINE_IDS.includes(id)) : [];
|
|
320
|
+
return [...new Set([...configured, ...DEFAULT_FALLBACK_ORDER, ...ENGINE_IDS])];
|
|
321
|
+
};
|
|
289
322
|
|
|
290
323
|
async function bridgeDescribe() {
|
|
291
324
|
const response = await fetch(`${BRIDGE_PREFIX}/describe`, {
|
|
@@ -385,6 +418,8 @@ window.__ModuleLoader__.load({
|
|
|
385
418
|
const [open, setOpen] = react.useState(pageMode);
|
|
386
419
|
const [state, setState] = react.useState({ status: "loading" });
|
|
387
420
|
const [provider, setProvider] = react.useState("bing");
|
|
421
|
+
const [disabledEngines, setDisabledEngines] = react.useState([]);
|
|
422
|
+
const [fallbackOrder, setFallbackOrder] = react.useState(DEFAULT_FALLBACK_ORDER);
|
|
388
423
|
const [safeSearch, setSafeSearch] = react.useState("off");
|
|
389
424
|
const [bingMarket, setBingMarket] = react.useState("zh-CN");
|
|
390
425
|
const [anysearchKey, setAnysearchKey] = react.useState("");
|
|
@@ -400,6 +435,7 @@ window.__ModuleLoader__.load({
|
|
|
400
435
|
const [baiduKey, setBaiduKey] = react.useState("");
|
|
401
436
|
const [kimiKey, setKimiKey] = react.useState("");
|
|
402
437
|
const [aliyunKey, setAliyunKey] = react.useState("");
|
|
438
|
+
const [doubaoKey, setDoubaoKey] = react.useState("");
|
|
403
439
|
const [platforms, setPlatforms] = react.useState(["github", "v2ex", "bilibili", "reddit", "hn", "stackoverflow", "wikipedia", "npm"]);
|
|
404
440
|
const [cacheTtl, setCacheTtl] = react.useState(5);
|
|
405
441
|
const [keysConfigured, setKeysConfigured] = react.useState({});
|
|
@@ -427,6 +463,12 @@ window.__ModuleLoader__.load({
|
|
|
427
463
|
if (view) {
|
|
428
464
|
const v = view.value ?? {};
|
|
429
465
|
setProvider(v.provider ?? "ddg");
|
|
466
|
+
setDisabledEngines(
|
|
467
|
+
Array.isArray(v.disabledEngines)
|
|
468
|
+
? [...new Set(v.disabledEngines.filter((id) => ENGINES.some((engine) => engine.id === id && id !== "auto" && id !== "multi")))]
|
|
469
|
+
: []
|
|
470
|
+
);
|
|
471
|
+
setFallbackOrder(normalizeFallbackOrder(v.fallbackOrder));
|
|
430
472
|
setSafeSearch(v.safeSearch === "strict" || v.safeSearch === "moderate" ? v.safeSearch : "off");
|
|
431
473
|
setBingMarket(v.bingMarket === undefined ? "zh-CN" : v.bingMarket);
|
|
432
474
|
setLang(v.lang === "en" ? "en" : "zh");
|
|
@@ -443,6 +485,7 @@ window.__ModuleLoader__.load({
|
|
|
443
485
|
setBaiduKey(v.baiduApiKey ?? "");
|
|
444
486
|
setKimiKey(v.kimiApiKey ?? "");
|
|
445
487
|
setAliyunKey(v.aliyunApiKey ?? "");
|
|
488
|
+
setDoubaoKey(v.doubaoApiKey ?? "");
|
|
446
489
|
setPlatforms(Array.isArray(v.platforms) && v.platforms.length > 0 ? v.platforms : ["github", "v2ex", "bilibili", "reddit", "hn", "stackoverflow", "wikipedia", "npm"]);
|
|
447
490
|
setCacheTtl(v.cacheTtl === undefined ? 5 : Math.min(Math.max(Number(v.cacheTtl) ?? 5, 0), 5));
|
|
448
491
|
// secrets 字段标记哪些 key 已配置(值被脱敏,仅显示"已配置")
|
|
@@ -463,6 +506,7 @@ window.__ModuleLoader__.load({
|
|
|
463
506
|
if (path === "baiduApiKey") configured.baidu = true;
|
|
464
507
|
if (path === "kimiApiKey") configured.kimi = true;
|
|
465
508
|
if (path === "aliyunApiKey") configured.aliyun = true;
|
|
509
|
+
if (path === "doubaoApiKey") configured.doubao = true;
|
|
466
510
|
}
|
|
467
511
|
}
|
|
468
512
|
setKeysConfigured(configured);
|
|
@@ -474,7 +518,7 @@ window.__ModuleLoader__.load({
|
|
|
474
518
|
const cred = await bridgeCredentialsStatus();
|
|
475
519
|
if (cred.ok) {
|
|
476
520
|
const cc = {};
|
|
477
|
-
const map = { anysearchApiKey: "anysearch", exaApiKey: "exa", tavilyApiKey: "tavily", keenableApiKey: "keenable", firecrawlApiKey: "firecrawl", parallelApiKey: "parallel", perplexityApiKey: "perplexity", deepseekApiKey: "deepseek", serpbaseApiKey: "serpbase", youcomApiKey: "youcom", baiduApiKey: "baidu", kimiApiKey: "kimi", aliyunApiKey: "aliyun" };
|
|
521
|
+
const map = { anysearchApiKey: "anysearch", exaApiKey: "exa", tavilyApiKey: "tavily", keenableApiKey: "keenable", firecrawlApiKey: "firecrawl", parallelApiKey: "parallel", perplexityApiKey: "perplexity", deepseekApiKey: "deepseek", serpbaseApiKey: "serpbase", youcomApiKey: "youcom", baiduApiKey: "baidu", kimiApiKey: "kimi", aliyunApiKey: "aliyun", doubaoApiKey: "doubao" };
|
|
478
522
|
for (const [k, v] of Object.entries(cred.value.configured ?? {})) {
|
|
479
523
|
if (map[k]) cc[map[k]] = v;
|
|
480
524
|
}
|
|
@@ -524,6 +568,7 @@ window.__ModuleLoader__.load({
|
|
|
524
568
|
["baiduApiKey", baiduKey],
|
|
525
569
|
["kimiApiKey", kimiKey],
|
|
526
570
|
["aliyunApiKey", aliyunKey],
|
|
571
|
+
["doubaoApiKey", doubaoKey],
|
|
527
572
|
];
|
|
528
573
|
if (keyStorage === "credentials") {
|
|
529
574
|
for (const [field, value] of keyFields) {
|
|
@@ -537,6 +582,8 @@ window.__ModuleLoader__.load({
|
|
|
537
582
|
}
|
|
538
583
|
}
|
|
539
584
|
const ops = [{ op: "set", path: ["provider"], value: provider }];
|
|
585
|
+
ops.push({ op: "set", path: ["disabledEngines"], value: disabledEngines });
|
|
586
|
+
ops.push({ op: "set", path: ["fallbackOrder"], value: normalizeFallbackOrder(fallbackOrder) });
|
|
540
587
|
ops.push({ op: "set", path: ["lang"], value: lang });
|
|
541
588
|
ops.push({ op: "set", path: ["keyStorage"], value: keyStorage });
|
|
542
589
|
ops.push({ op: "set", path: ["safeSearch"], value: safeSearch });
|
|
@@ -662,13 +709,29 @@ window.__ModuleLoader__.load({
|
|
|
662
709
|
const t = tt(lang);
|
|
663
710
|
const title = "Free Search";
|
|
664
711
|
const description = t.description;
|
|
712
|
+
const enabledEngineCount = ENGINES.filter((e) => e.id !== "auto" && e.id !== "multi" && !disabledEngines.includes(e.id)).length;
|
|
665
713
|
const currentEngine = ENGINES.find((e) => e.id === provider) ?? ENGINES.find((e) => e.id === "bing") ?? ENGINES[0];
|
|
714
|
+
const moveFallbackEngine = (engineId, delta) => {
|
|
715
|
+
setFallbackOrder((prev) => {
|
|
716
|
+
const next = normalizeFallbackOrder(prev);
|
|
717
|
+
const from = next.indexOf(engineId);
|
|
718
|
+
const to = from + delta;
|
|
719
|
+
if (from < 0 || to < 0 || to >= next.length) return next;
|
|
720
|
+
[next[from], next[to]] = [next[to], next[from]];
|
|
721
|
+
return next;
|
|
722
|
+
});
|
|
723
|
+
setDirty(true);
|
|
724
|
+
setFailed(false);
|
|
725
|
+
setSaveError("");
|
|
726
|
+
};
|
|
666
727
|
const badgeClass =
|
|
667
728
|
currentEngine.badge === "AUTO"
|
|
668
729
|
? "dshfs-badge dshfs-badgeAuto"
|
|
669
|
-
: currentEngine.badge === "
|
|
670
|
-
? "dshfs-badge dshfs-
|
|
671
|
-
:
|
|
730
|
+
: currentEngine.badge === "MULTI"
|
|
731
|
+
? "dshfs-badge dshfs-badgeMulti"
|
|
732
|
+
: currentEngine.badge === "FREE"
|
|
733
|
+
? "dshfs-badge dshfs-badgeFree"
|
|
734
|
+
: "dshfs-badge dshfs-badgeKey";
|
|
672
735
|
|
|
673
736
|
const toggleLang = () => {
|
|
674
737
|
setLang((prev) => (prev === "en" ? "zh" : "en"));
|
|
@@ -728,7 +791,11 @@ react_jsx_runtime.jsx("div", {
|
|
|
728
791
|
disabled: !ready || saving,
|
|
729
792
|
onChange: (e) => select(e.target.value),
|
|
730
793
|
children: ENGINES.map((engine) =>
|
|
731
|
-
react_jsx_runtime.jsx("option", {
|
|
794
|
+
react_jsx_runtime.jsx("option", {
|
|
795
|
+
value: engine.id,
|
|
796
|
+
disabled: engine.id !== "auto" && engine.id !== "multi" && disabledEngines.includes(engine.id),
|
|
797
|
+
children: `${engine.label} (${engine.badge})${engine.id !== "auto" && engine.id !== "multi" && disabledEngines.includes(engine.id) ? " · DISABLED" : ""}`,
|
|
798
|
+
}, engine.id)
|
|
732
799
|
),
|
|
733
800
|
}),
|
|
734
801
|
currentEngine.link
|
|
@@ -749,6 +816,118 @@ react_jsx_runtime.jsx("div", {
|
|
|
749
816
|
}),
|
|
750
817
|
],
|
|
751
818
|
}),
|
|
819
|
+
react_jsx_runtime.jsx("div", {
|
|
820
|
+
className: "dshfs-field",
|
|
821
|
+
children: [
|
|
822
|
+
react_jsx_runtime.jsx("div", {
|
|
823
|
+
className: "dshfs-label",
|
|
824
|
+
children: t.engineEnable,
|
|
825
|
+
}),
|
|
826
|
+
react_jsx_runtime.jsx("div", {
|
|
827
|
+
className: "dshfs-platforms",
|
|
828
|
+
children: ENGINES.filter((engine) => engine.id !== "auto" && engine.id !== "multi").map((engine) => {
|
|
829
|
+
const enabled = !disabledEngines.includes(engine.id);
|
|
830
|
+
const lastEnabled = enabled && enabledEngineCount <= 1;
|
|
831
|
+
return react_jsx_runtime.jsx("label", {
|
|
832
|
+
className: "dshfs-platform",
|
|
833
|
+
children: [
|
|
834
|
+
react_jsx_runtime.jsx("input", {
|
|
835
|
+
type: "checkbox",
|
|
836
|
+
checked: enabled,
|
|
837
|
+
disabled: !ready || saving || lastEnabled,
|
|
838
|
+
onChange: (e) => {
|
|
839
|
+
const nextEnabled = e.target.checked;
|
|
840
|
+
setDisabledEngines((prev) => {
|
|
841
|
+
const next = nextEnabled
|
|
842
|
+
? prev.filter((id) => id !== engine.id)
|
|
843
|
+
: [...new Set([...prev, engine.id])];
|
|
844
|
+
return next;
|
|
845
|
+
});
|
|
846
|
+
if (!nextEnabled && provider === engine.id) setProvider("auto");
|
|
847
|
+
setDirty(true);
|
|
848
|
+
setFailed(false);
|
|
849
|
+
setSaveError("");
|
|
850
|
+
},
|
|
851
|
+
}),
|
|
852
|
+
`${engine.label} (${engine.badge})`,
|
|
853
|
+
],
|
|
854
|
+
}, engine.id);
|
|
855
|
+
}),
|
|
856
|
+
}),
|
|
857
|
+
react_jsx_runtime.jsx("p", {
|
|
858
|
+
className: "dshfs-hint",
|
|
859
|
+
children: t.engineEnableHint,
|
|
860
|
+
}),
|
|
861
|
+
],
|
|
862
|
+
}),
|
|
863
|
+
react_jsx_runtime.jsx("div", {
|
|
864
|
+
className: "dshfs-field",
|
|
865
|
+
children: [
|
|
866
|
+
react_jsx_runtime.jsx("div", {
|
|
867
|
+
className: "dshfs-label",
|
|
868
|
+
children: t.fallbackPriority,
|
|
869
|
+
}),
|
|
870
|
+
react_jsx_runtime.jsx("div", {
|
|
871
|
+
className: "dshfs-priorityList",
|
|
872
|
+
children: fallbackOrder.map((engineId, index) => {
|
|
873
|
+
const engine = ENGINES.find((item) => item.id === engineId);
|
|
874
|
+
if (!engine) return null;
|
|
875
|
+
const isDisabled = disabledEngines.includes(engineId);
|
|
876
|
+
return react_jsx_runtime.jsx("div", {
|
|
877
|
+
className: "dshfs-priorityRow" + (isDisabled ? " dshfs-priorityRowDisabled" : ""),
|
|
878
|
+
children: [
|
|
879
|
+
react_jsx_runtime.jsx("span", { className: "dshfs-priorityIndex", children: String(index + 1) }),
|
|
880
|
+
react_jsx_runtime.jsx("span", {
|
|
881
|
+
className: "dshfs-priorityName",
|
|
882
|
+
children: `${engine.label} (${engine.badge})${isDisabled ? " · " + t.priorityDisabled : ""}`,
|
|
883
|
+
}),
|
|
884
|
+
react_jsx_runtime.jsx("span", {
|
|
885
|
+
className: "dshfs-priorityActions",
|
|
886
|
+
children: [
|
|
887
|
+
react_jsx_runtime.jsx("button", {
|
|
888
|
+
type: "button",
|
|
889
|
+
className: "dshfs-btn dshfs-priorityBtn",
|
|
890
|
+
disabled: !ready || saving || index === 0,
|
|
891
|
+
onClick: () => moveFallbackEngine(engineId, -1),
|
|
892
|
+
"aria-label": `${engine.label} ↑`,
|
|
893
|
+
title: "↑",
|
|
894
|
+
children: "↑",
|
|
895
|
+
}, "up"),
|
|
896
|
+
react_jsx_runtime.jsx("button", {
|
|
897
|
+
type: "button",
|
|
898
|
+
className: "dshfs-btn dshfs-priorityBtn",
|
|
899
|
+
disabled: !ready || saving || index === fallbackOrder.length - 1,
|
|
900
|
+
onClick: () => moveFallbackEngine(engineId, 1),
|
|
901
|
+
"aria-label": `${engine.label} ↓`,
|
|
902
|
+
title: "↓",
|
|
903
|
+
children: "↓",
|
|
904
|
+
}, "down"),
|
|
905
|
+
],
|
|
906
|
+
}),
|
|
907
|
+
],
|
|
908
|
+
}, engineId);
|
|
909
|
+
}),
|
|
910
|
+
}),
|
|
911
|
+
react_jsx_runtime.jsx("div", {
|
|
912
|
+
children: react_jsx_runtime.jsx("button", {
|
|
913
|
+
type: "button",
|
|
914
|
+
className: "dshfs-btn",
|
|
915
|
+
disabled: !ready || saving || fallbackOrder.join(",") === DEFAULT_FALLBACK_ORDER.join(","),
|
|
916
|
+
onClick: () => {
|
|
917
|
+
setFallbackOrder(DEFAULT_FALLBACK_ORDER.slice());
|
|
918
|
+
setDirty(true);
|
|
919
|
+
setFailed(false);
|
|
920
|
+
setSaveError("");
|
|
921
|
+
},
|
|
922
|
+
children: t.fallbackPriorityReset,
|
|
923
|
+
}),
|
|
924
|
+
}),
|
|
925
|
+
react_jsx_runtime.jsx("p", {
|
|
926
|
+
className: "dshfs-hint",
|
|
927
|
+
children: t.fallbackPriorityHint,
|
|
928
|
+
}),
|
|
929
|
+
],
|
|
930
|
+
}),
|
|
752
931
|
react_jsx_runtime.jsx("div", {
|
|
753
932
|
className: "dshfs-field",
|
|
754
933
|
children: [
|
|
@@ -982,6 +1161,19 @@ react_jsx_runtime.jsx("div", {
|
|
|
982
1161
|
setSaveError("");
|
|
983
1162
|
},
|
|
984
1163
|
}),
|
|
1164
|
+
react_jsx_runtime.jsx("input", {
|
|
1165
|
+
className: "dshfs-input",
|
|
1166
|
+
type: "password",
|
|
1167
|
+
placeholder: t.doubaoPh(keysConfigured.doubao),
|
|
1168
|
+
value: doubaoKey,
|
|
1169
|
+
disabled: !ready || saving,
|
|
1170
|
+
onChange: (e) => {
|
|
1171
|
+
setDoubaoKey(e.target.value);
|
|
1172
|
+
setDirty(true);
|
|
1173
|
+
setFailed(false);
|
|
1174
|
+
setSaveError("");
|
|
1175
|
+
},
|
|
1176
|
+
}),
|
|
985
1177
|
react_jsx_runtime.jsx("p", {
|
|
986
1178
|
className: "dshfs-hint",
|
|
987
1179
|
children: t.keysHint,
|
|
@@ -1218,7 +1410,7 @@ react_jsx_runtime.jsx("div", {
|
|
|
1218
1410
|
setFailed(false);
|
|
1219
1411
|
setSaveError("");
|
|
1220
1412
|
},
|
|
1221
|
-
disabled: saving || !ready || provider === "bing",
|
|
1413
|
+
disabled: saving || !ready || provider === "bing" || disabledEngines.includes("bing"),
|
|
1222
1414
|
children: t.useBing,
|
|
1223
1415
|
}),
|
|
1224
1416
|
react_jsx_runtime.jsx("button", {
|
|
@@ -1283,12 +1475,15 @@ react_jsx_runtime.jsx("div", {
|
|
|
1283
1475
|
const result = await bridgeDescribe();
|
|
1284
1476
|
const view = result.ok ? result.value.namespaces.find((n) => n.ns === NS) : undefined;
|
|
1285
1477
|
const current = view?.value?.provider ?? "bing";
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1478
|
+
const disabled = new Set(Array.isArray(view?.value?.disabledEngines) ? view.value.disabledEngines : []);
|
|
1479
|
+
return ENGINES
|
|
1480
|
+
.filter((e) => e.id === "auto" || e.id === "multi" || !disabled.has(e.id))
|
|
1481
|
+
.map((e) => ({
|
|
1482
|
+
id: e.id,
|
|
1483
|
+
label: `${e.label}${e.badge === "AUTO" ? " · 智能路由" : e.badge === "MULTI" ? " · 并发合并" : e.badge === "FREE" ? " · 免费" : " · API Key"}`,
|
|
1484
|
+
detail: e.id === current ? (view?.value?.lang === "en" ? "current" : "当前") : undefined,
|
|
1485
|
+
active: e.id === current,
|
|
1486
|
+
}));
|
|
1292
1487
|
},
|
|
1293
1488
|
onSelect: async (option) => {
|
|
1294
1489
|
await bridgeMutate({ ns: NS, ops: [{ op: "set", path: ["provider"], value: option.id }] });
|