@klarkxy/dsh-web-search-manager 0.1.11 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -10
- package/cordis.patch.yml +1 -8
- package/docs/README.zh-CN.md +31 -10
- package/lib/client.inner.cjs +649 -104
- package/lib/client.inner.cjs.map +1 -1
- package/lib/client.js +650 -266
- package/lib/contracts.d.ts +15 -20
- package/lib/contracts.js +13 -1
- package/lib/contracts.js.map +1 -1
- package/lib/index.d.ts +19 -3
- package/lib/index.js +506 -389
- package/lib/index.js.map +1 -1
- package/package.json +23 -22
package/lib/client.js
CHANGED
|
@@ -1,174 +1,54 @@
|
|
|
1
|
-
function __createDshNodeShims() {
|
|
2
|
-
class EventEmitter {
|
|
3
|
-
constructor() {
|
|
4
|
-
this._e = Object.create(null)
|
|
5
|
-
}
|
|
6
|
-
on(type, fn) {
|
|
7
|
-
(this._e[type] ||= []).push(fn)
|
|
8
|
-
return this
|
|
9
|
-
}
|
|
10
|
-
addListener(type, fn) {
|
|
11
|
-
return this.on(type, fn)
|
|
12
|
-
}
|
|
13
|
-
once(type, fn) {
|
|
14
|
-
const wrap = (...args) => {
|
|
15
|
-
this.off(type, wrap)
|
|
16
|
-
fn(...args)
|
|
17
|
-
}
|
|
18
|
-
return this.on(type, wrap)
|
|
19
|
-
}
|
|
20
|
-
off(type, fn) {
|
|
21
|
-
const list = this._e[type]
|
|
22
|
-
if (list) this._e[type] = list.filter((item) => item !== fn)
|
|
23
|
-
return this
|
|
24
|
-
}
|
|
25
|
-
removeListener(type, fn) {
|
|
26
|
-
return this.off(type, fn)
|
|
27
|
-
}
|
|
28
|
-
emit(type, ...args) {
|
|
29
|
-
const list = this._e[type]
|
|
30
|
-
if (!list) return false
|
|
31
|
-
for (const fn of list.slice()) fn(...args)
|
|
32
|
-
return true
|
|
33
|
-
}
|
|
34
|
-
removeAllListeners(type) {
|
|
35
|
-
if (type) delete this._e[type]
|
|
36
|
-
else this._e = Object.create(null)
|
|
37
|
-
return this
|
|
38
|
-
}
|
|
39
|
-
listeners(type) {
|
|
40
|
-
return (this._e[type] || []).slice()
|
|
41
|
-
}
|
|
42
|
-
setMaxListeners() {
|
|
43
|
-
return this
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
class Buffer extends Uint8Array {
|
|
48
|
-
static from(value, encodingOrOffset, length) {
|
|
49
|
-
if (typeof value === 'string') return new Buffer(new TextEncoder().encode(value))
|
|
50
|
-
if (value instanceof ArrayBuffer) return new Buffer(new Uint8Array(value, encodingOrOffset, length))
|
|
51
|
-
if (ArrayBuffer.isView(value)) return new Buffer(new Uint8Array(value.buffer, value.byteOffset, value.byteLength))
|
|
52
|
-
if (Array.isArray(value) || value && typeof value.length === 'number') return new Buffer(Uint8Array.from(value))
|
|
53
|
-
return new Buffer(0)
|
|
54
|
-
}
|
|
55
|
-
static alloc(size, fill) {
|
|
56
|
-
const buf = new Buffer(size)
|
|
57
|
-
if (fill !== undefined && fill !== 0) {
|
|
58
|
-
if (typeof fill === 'string') {
|
|
59
|
-
const bytes = new TextEncoder().encode(fill)
|
|
60
|
-
for (let i = 0; i < size; i += 1) buf[i] = bytes[i % bytes.length]
|
|
61
|
-
} else buf.fill(fill)
|
|
62
|
-
}
|
|
63
|
-
return buf
|
|
64
|
-
}
|
|
65
|
-
static allocUnsafe(size) {
|
|
66
|
-
return new Buffer(size)
|
|
67
|
-
}
|
|
68
|
-
static allocUnsafeSlow(size) {
|
|
69
|
-
return new Buffer(size)
|
|
70
|
-
}
|
|
71
|
-
static concat(list, totalLength) {
|
|
72
|
-
const length = totalLength ?? list.reduce((sum, item) => sum + item.length, 0)
|
|
73
|
-
const out = new Buffer(length)
|
|
74
|
-
let offset = 0
|
|
75
|
-
for (const item of list) {
|
|
76
|
-
out.set(item, offset)
|
|
77
|
-
offset += item.length
|
|
78
|
-
}
|
|
79
|
-
return out
|
|
80
|
-
}
|
|
81
|
-
static isBuffer(value) {
|
|
82
|
-
return value instanceof Buffer
|
|
83
|
-
}
|
|
84
|
-
static byteLength(value) {
|
|
85
|
-
if (typeof value === 'string') return new TextEncoder().encode(value).length
|
|
86
|
-
return value?.byteLength ?? value?.length ?? 0
|
|
87
|
-
}
|
|
88
|
-
static isEncoding(encoding) {
|
|
89
|
-
return /^(utf8|utf-8|ascii|latin1|binary|hex|base64|base64url)$/i.test(String(encoding || ''))
|
|
90
|
-
}
|
|
91
|
-
toString(encoding) {
|
|
92
|
-
if (encoding === 'hex') return [...this].map((byte) => byte.toString(16).padStart(2, '0')).join('')
|
|
93
|
-
if (encoding === 'base64') {
|
|
94
|
-
let binary = ''
|
|
95
|
-
this.forEach((byte) => {
|
|
96
|
-
binary += String.fromCharCode(byte)
|
|
97
|
-
})
|
|
98
|
-
return btoa(binary)
|
|
99
|
-
}
|
|
100
|
-
return new TextDecoder().decode(this)
|
|
101
|
-
}
|
|
102
|
-
slice(start, end) {
|
|
103
|
-
return Buffer.from(super.subarray(start, end))
|
|
104
|
-
}
|
|
105
|
-
copy(target, targetStart = 0, start = 0, end = this.length) {
|
|
106
|
-
const sliced = this.subarray(start, end)
|
|
107
|
-
target.set(sliced, targetStart)
|
|
108
|
-
return sliced.length
|
|
109
|
-
}
|
|
110
|
-
write(string, offset = 0) {
|
|
111
|
-
const bytes = new TextEncoder().encode(string)
|
|
112
|
-
const written = Math.min(bytes.length, this.length - offset)
|
|
113
|
-
this.set(bytes.subarray(0, written), offset)
|
|
114
|
-
return written
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function inherits(ctor, superCtor) {
|
|
119
|
-
ctor.super_ = superCtor
|
|
120
|
-
ctor.prototype = Object.create(superCtor.prototype, {
|
|
121
|
-
constructor: { value: ctor, writable: true, configurable: true },
|
|
122
|
-
})
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (!globalThis.Buffer) globalThis.Buffer = Buffer
|
|
126
|
-
|
|
127
|
-
return {
|
|
128
|
-
buffer: { Buffer, SlowBuffer: Buffer, INSPECT_MAX_BYTES: 50, kMaxLength: 0x7fffffff },
|
|
129
|
-
stream: {
|
|
130
|
-
Stream: EventEmitter,
|
|
131
|
-
Readable: EventEmitter,
|
|
132
|
-
Writable: EventEmitter,
|
|
133
|
-
Duplex: EventEmitter,
|
|
134
|
-
Transform: EventEmitter,
|
|
135
|
-
PassThrough: EventEmitter,
|
|
136
|
-
},
|
|
137
|
-
events: { EventEmitter },
|
|
138
|
-
util: {
|
|
139
|
-
deprecate(fn) {
|
|
140
|
-
return fn
|
|
141
|
-
},
|
|
142
|
-
inherits,
|
|
143
|
-
inspect() {
|
|
144
|
-
return ''
|
|
145
|
-
},
|
|
146
|
-
format(value) {
|
|
147
|
-
return String(value)
|
|
148
|
-
},
|
|
149
|
-
types: { isBuffer: Buffer.isBuffer },
|
|
150
|
-
},
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
1
|
window.__ModuleLoader__.load({
|
|
155
2
|
id: "@klarkxy/dsh-web-search-manager",
|
|
156
3
|
factory: (dshRequire) => {
|
|
157
|
-
var
|
|
158
|
-
var process = globalThis.process || { env: {}, nextTick: function (fn) { var args = [].slice.call(arguments, 1); queueMicrotask(function () { fn.apply(null, args); }); } };
|
|
159
|
-
var require = function (id) {
|
|
160
|
-
if (id === 'buffer' || id === 'stream' || id === 'util' || id === 'events') {
|
|
161
|
-
if (!__nodeShims) __nodeShims = __createDshNodeShims();
|
|
162
|
-
return __nodeShims[id];
|
|
163
|
-
}
|
|
164
|
-
return dshRequire(id);
|
|
165
|
-
};
|
|
4
|
+
var require = dshRequire;
|
|
166
5
|
var module = { exports: {} };
|
|
167
6
|
var exports = module.exports;
|
|
168
7
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
169
8
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
170
9
|
let react = require("react");
|
|
171
10
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
11
|
+
//#region src/client-error.ts
|
|
12
|
+
/** Local adapter diagnostics only; third-party descriptions remain verbatim. */
|
|
13
|
+
const localErrors = {
|
|
14
|
+
"网络搜索设置格式无效。": "Invalid web search settings format.",
|
|
15
|
+
"未知操作。": "Unknown operation.",
|
|
16
|
+
"网络设置操作失败,请刷新后重试。": "The network settings operation failed. Refresh and try again.",
|
|
17
|
+
"查询须为 1 至 4000 字符。": "The query must contain 1 to 4,000 characters.",
|
|
18
|
+
"网络搜索插件已停止。": "The web search plugin has stopped.",
|
|
19
|
+
"请在设置 → 网络搜索中配置凭据并启用。": "Configure credentials and enable web search in Settings → Web search.",
|
|
20
|
+
"网络访问设置已改变,请重新发起请求。": "Network access settings changed. Start the request again.",
|
|
21
|
+
"网络请求已取消。": "The network request was cancelled.",
|
|
22
|
+
"网络请求已取消或超时。": "The network request was cancelled or timed out.",
|
|
23
|
+
"设置已被其他页面修改,请刷新后重试。": "Settings changed in another page. Refresh and try again.",
|
|
24
|
+
"供应商不支持自定义地址。": "The provider does not support a custom address.",
|
|
25
|
+
"供应商地址须为无凭据、无查询参数的 HTTPS 地址。": "The provider address must use HTTPS without credentials or query parameters.",
|
|
26
|
+
"需选择已安装的供应商并填写有效凭据。": "Choose an installed provider and enter valid credentials.",
|
|
27
|
+
"保存失败,本次运行已暂停网络访问以防意外联网。": "Saving failed. Network access is paused for this run to prevent unintended requests.",
|
|
28
|
+
"供应商未提供错误描述。": "The provider did not supply an error description.",
|
|
29
|
+
"供应商请求失败。": "Provider request failed.",
|
|
30
|
+
"搜索凭据已删除或不可用,请重新配置。": "Search credentials were deleted or are unavailable. Configure them again.",
|
|
31
|
+
"供应商配置不可用。": "Provider configuration is unavailable.",
|
|
32
|
+
"查询无效。": "Invalid query.",
|
|
33
|
+
"结果数须为 1 至 20。": "The result count must be from 1 to 20.",
|
|
34
|
+
"搜索已取消。": "Search cancelled.",
|
|
35
|
+
"Tavily 搜索已取消。": "Tavily search cancelled.",
|
|
36
|
+
"响应格式无效。": "Invalid response shape",
|
|
37
|
+
"响应缺失。": "Missing response",
|
|
38
|
+
"响应过大。": "Response too large",
|
|
39
|
+
"地址无效。": "Invalid endpoint"
|
|
40
|
+
};
|
|
41
|
+
function localErrorText(message, locale) {
|
|
42
|
+
const prefix = message.match(/^(\[(?:search|fetch):[^\]]+\] )(.*)$/s);
|
|
43
|
+
if (prefix) return prefix[1] + localErrorText(prefix[2], locale);
|
|
44
|
+
if (locale === "zh") return Object.entries(localErrors).find(([, english]) => english === message)?.[0] ?? message;
|
|
45
|
+
if (localErrors[message]) return localErrors[message];
|
|
46
|
+
const http = message.match(/^供应商请求失败(HTTP (\d{3}))。$/);
|
|
47
|
+
if (http) return `Provider request failed (HTTP ${http[1]}).`;
|
|
48
|
+
const config = message.match(/^(brave|bocha|firecrawl|serper|Tavily) 配置不可用。$/);
|
|
49
|
+
if (config) return `${config[1]} configuration is unavailable.`;
|
|
50
|
+
return message;
|
|
51
|
+
}
|
|
172
52
|
Object.values({
|
|
173
53
|
/** Surfaces, from the page up to the topmost nested card. */
|
|
174
54
|
surface: [
|
|
@@ -466,7 +346,7 @@ ${scope} {
|
|
|
466
346
|
border: 0.5px solid var(--dsw-alias-border-l3);
|
|
467
347
|
border-radius: var(--dsw-radius-md);
|
|
468
348
|
background: var(--dsw-alias-bg-layer-1);
|
|
469
|
-
font-family: var(--ds-font-family-code
|
|
349
|
+
font-family: var(--ds-font-family-code);
|
|
470
350
|
font-size: 12px;
|
|
471
351
|
line-height: 20px;
|
|
472
352
|
color: var(--dsw-alias-label-secondary);
|
|
@@ -542,6 +422,49 @@ ${scope} {
|
|
|
542
422
|
`.replace(/^& (\.dsh-ui-[a-z0-9-]+)(?![+>~])/gm, (_match, className) => `&${className}, & ${className}`);
|
|
543
423
|
}
|
|
544
424
|
//#endregion
|
|
425
|
+
//#region ../dsh-plugin-kit/src/client-utils.ts
|
|
426
|
+
/** Fixed pause for failing background reads; manual work is never replayed. */
|
|
427
|
+
function createPluginReadGate(now = Date.now) {
|
|
428
|
+
let pausedUntil = 0;
|
|
429
|
+
let epoch = 0;
|
|
430
|
+
return {
|
|
431
|
+
canRead: () => now() >= pausedUntil,
|
|
432
|
+
reset: () => {
|
|
433
|
+
epoch++;
|
|
434
|
+
pausedUntil = 0;
|
|
435
|
+
},
|
|
436
|
+
async run(read) {
|
|
437
|
+
const current = epoch;
|
|
438
|
+
try {
|
|
439
|
+
const value = await read();
|
|
440
|
+
if (current === epoch) pausedUntil = 0;
|
|
441
|
+
return value;
|
|
442
|
+
} catch (error) {
|
|
443
|
+
const message = error instanceof Error ? error.message : "";
|
|
444
|
+
if (current === epoch && (/(?:^|\s)transport failure for .+: HTTP (?:404|405|503)\b/.test(message) || error instanceof TypeError && /^(?:Failed to fetch|fetch failed|NetworkError when attempting to fetch resource\.?)$/i.test(message))) pausedUntil = now() + 3e4;
|
|
445
|
+
throw error;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
//#endregion
|
|
451
|
+
//#region src/contracts.ts
|
|
452
|
+
const WEB_SEARCH_RPC_CHANNEL = "/web-search-manager";
|
|
453
|
+
function pickActiveSearch(order, configured) {
|
|
454
|
+
return order.find(configured) ?? "";
|
|
455
|
+
}
|
|
456
|
+
function safeConfigurationUrl(value) {
|
|
457
|
+
if (typeof value !== "string" || !value || /[\u0000-\u0020\\]/.test(value)) return;
|
|
458
|
+
try {
|
|
459
|
+
const url = new URL(value, "https://dsh.invalid");
|
|
460
|
+
if (url.username || url.password) return;
|
|
461
|
+
if (value.startsWith("/") && !value.startsWith("//") || value.startsWith("#")) return url.origin === "https://dsh.invalid" ? value : void 0;
|
|
462
|
+
return /^https:\/\//i.test(value) && url.protocol === "https:" ? value : void 0;
|
|
463
|
+
} catch {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
//#endregion
|
|
545
468
|
//#region src/provider-info.ts
|
|
546
469
|
const PROVIDER_PRICING_CHECKED = "2026-09-21";
|
|
547
470
|
const providerInfo = {
|
|
@@ -586,22 +509,94 @@ const providerInfo = {
|
|
|
586
509
|
pricingUrl: "https://docs.tavily.com/documentation/api-credits"
|
|
587
510
|
}
|
|
588
511
|
};
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
512
|
+
const englishInfo = {
|
|
513
|
+
ddg: {
|
|
514
|
+
description: "Search public DuckDuckGo pages without registration or an API key.",
|
|
515
|
+
pricing: "Free to use; no search API fees."
|
|
516
|
+
},
|
|
517
|
+
bocha: {
|
|
518
|
+
description: "Search Chinese web content, returning titles, links and summaries.",
|
|
519
|
+
pricing: "Charged per request: listed price ¥0.036/request; trial credits depend on official offers."
|
|
520
|
+
},
|
|
521
|
+
brave: {
|
|
522
|
+
description: "Search web pages, news and more with Brave’s independent index.",
|
|
523
|
+
pricing: "$5 in monthly credits (about 1,000 requests); then $5/1,000 requests. A payment card is required to activate."
|
|
524
|
+
},
|
|
525
|
+
"deepseek-official": {
|
|
526
|
+
description: "Call built-in search through a DeepSeek model using the model API key.",
|
|
527
|
+
pricing: "Default Flash token pricing per million: input ¥1–2 (cache hits ¥0.02–0.04), output ¥4–8, varying by peak/off-peak hours. Platform terms govern promotional balance and tool fees."
|
|
528
|
+
},
|
|
529
|
+
exa: {
|
|
530
|
+
description: "Web search for AI with relevant pages and content snippets.",
|
|
531
|
+
pricing: "$20 on signup, then $10 monthly credits; $7/1,000 requests (up to 10 results each), plus $1/1,000 additional results beyond 10."
|
|
532
|
+
},
|
|
533
|
+
firecrawl: {
|
|
534
|
+
description: "Search the web and extract page content.",
|
|
535
|
+
pricing: "1,000 free credits/month; 2 credits per 10 search results. Hobby includes 5,000 credits/month at $16/month billed annually; extra credits cost $5/1,000."
|
|
536
|
+
},
|
|
537
|
+
serper: {
|
|
538
|
+
description: "Get Google web search results.",
|
|
539
|
+
pricing: "2,500 one-time signup requests; $50/50,000 requests ($1/1,000). Paid credits expire after 6 months."
|
|
540
|
+
},
|
|
541
|
+
tavily: {
|
|
542
|
+
description: "Web search for AI, currently using basic search.",
|
|
543
|
+
pricing: "1,000 free credits/month; basic search uses 1 credit/request. Pay as you go: $0.008/credit ($8/1,000 requests)."
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
const bundledDescriptions = {
|
|
547
|
+
ddg: ["无需注册。默认后端。", "No registration required. Default backend."],
|
|
548
|
+
"deepseek-official": ["使用已配置的 DeepSeek API Key,可能产生额外搜索费用。", "Uses the configured DeepSeek API key; additional search fees may apply."],
|
|
549
|
+
exa: ["独立 Search API。", "Independent Search API."],
|
|
550
|
+
brave: ["Brave Search API。", "Brave Search API."],
|
|
551
|
+
bocha: ["中文网页搜索 API。", "Chinese web search API."],
|
|
552
|
+
serper: ["Google 结果的 Search API。", "Search API for Google results."],
|
|
553
|
+
firecrawl: ["网页搜索与提取 API。", "Web search and extraction API."],
|
|
554
|
+
tavily: ["Search API;固定 basic 检索,不自动升级搜索深度。", "Search API using basic search; search depth is never upgraded automatically."]
|
|
555
|
+
};
|
|
556
|
+
/** Translate only manager-owned labels; externally supplied metadata stays verbatim. */
|
|
557
|
+
function providerDisplayLabel(provider, locale) {
|
|
558
|
+
if (!bundledCatalogId(provider)) return provider.label;
|
|
559
|
+
if (provider.label === "DeepSeek 搜索") return locale === "zh" ? provider.label : "DeepSeek search";
|
|
560
|
+
if (provider.label === "博查") return locale === "zh" ? provider.label : "Bocha";
|
|
561
|
+
if (provider.label === "网页读取") return locale === "zh" ? provider.label : "Read web pages";
|
|
562
|
+
return provider.label;
|
|
596
563
|
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
function resolveSearchOrder(knownIds, settings, _keyed) {
|
|
601
|
-
return [...new Set(settings.searchOrder.filter((id) => knownIds.includes(id)))];
|
|
564
|
+
function bundledCatalogId(provider) {
|
|
565
|
+
if (!(typeof provider.configurationOwned === "boolean" ? provider.configurationOwned : !provider.configurationOwner || provider.configurationOwner === "@klarkxy/dsh-web-search-manager")) return;
|
|
566
|
+
return provider.id === "deepseek-managed" ? "deepseek-official" : provider.id;
|
|
602
567
|
}
|
|
603
|
-
function
|
|
604
|
-
return
|
|
568
|
+
function missingPricing(locale) {
|
|
569
|
+
return locale === "zh" ? "未提供费用信息" : "No pricing information provided";
|
|
570
|
+
}
|
|
571
|
+
function usesBundledPricing(provider) {
|
|
572
|
+
if (provider.pricing?.trim()) return false;
|
|
573
|
+
const catalogId = bundledCatalogId(provider);
|
|
574
|
+
return Boolean(catalogId && Object.hasOwn(providerInfo, catalogId));
|
|
575
|
+
}
|
|
576
|
+
function searchProviderInfo(provider, locale = "zh") {
|
|
577
|
+
const catalogId = bundledCatalogId(provider);
|
|
578
|
+
const base = catalogId && Object.hasOwn(providerInfo, catalogId) ? providerInfo[catalogId] : void 0;
|
|
579
|
+
const known = base && locale === "en" ? {
|
|
580
|
+
...base,
|
|
581
|
+
...englishInfo[catalogId]
|
|
582
|
+
} : base;
|
|
583
|
+
const ownedCopy = catalogId ? bundledDescriptions[catalogId] : void 0;
|
|
584
|
+
const description = ownedCopy && provider.description === ownedCopy[0] ? ownedCopy[locale === "zh" ? 0 : 1] : base && provider.description === base.description ? known.description : provider.description.trim() ? provider.description : known?.description ?? provider.description;
|
|
585
|
+
if (provider.pricing?.trim()) return {
|
|
586
|
+
description,
|
|
587
|
+
pricing: provider.pricing,
|
|
588
|
+
pricingUrl: provider.pricingUrl ?? known?.pricingUrl ?? provider.signupUrl
|
|
589
|
+
};
|
|
590
|
+
if (known) return {
|
|
591
|
+
description,
|
|
592
|
+
pricing: known.pricing,
|
|
593
|
+
pricingUrl: provider.pricingUrl ?? known.pricingUrl ?? provider.signupUrl
|
|
594
|
+
};
|
|
595
|
+
return {
|
|
596
|
+
description,
|
|
597
|
+
pricing: missingPricing(locale),
|
|
598
|
+
pricingUrl: provider.pricingUrl ?? provider.signupUrl
|
|
599
|
+
};
|
|
605
600
|
}
|
|
606
601
|
//#endregion
|
|
607
602
|
//#region src/client.tsx
|
|
@@ -621,32 +616,129 @@ function editable(settings) {
|
|
|
621
616
|
const { revision: _revision, ...rest } = settings;
|
|
622
617
|
return rest;
|
|
623
618
|
}
|
|
619
|
+
const STATUS_REFRESH_MS = 3e3;
|
|
620
|
+
function isPageHidden(hidden) {
|
|
621
|
+
if (hidden) return true;
|
|
622
|
+
return typeof document !== "undefined" && document.visibilityState === "hidden";
|
|
623
|
+
}
|
|
624
|
+
function canStartStatusRead(input) {
|
|
625
|
+
if (!input.mounted) return false;
|
|
626
|
+
if (input.source === "poll") return !input.hidden && !input.busy && !input.reordering && !input.reading;
|
|
627
|
+
return true;
|
|
628
|
+
}
|
|
629
|
+
function isExternalConfigurationUrl(url) {
|
|
630
|
+
return /^https:\/\//i.test(url);
|
|
631
|
+
}
|
|
632
|
+
function shouldApplyPolledStatus(input) {
|
|
633
|
+
if (input.hidden || input.busy || input.reordering) return false;
|
|
634
|
+
if (input.requestId !== input.latestRequestId) return false;
|
|
635
|
+
if (input.currentRevision !== void 0 && input.incomingRevision < input.currentRevision) return false;
|
|
636
|
+
return true;
|
|
637
|
+
}
|
|
638
|
+
function bindStatusRefresh(options) {
|
|
639
|
+
const intervalMs = options.intervalMs ?? 3e3;
|
|
640
|
+
let inFlight = false;
|
|
641
|
+
let stopped = false;
|
|
642
|
+
const run = async () => {
|
|
643
|
+
if (stopped || inFlight || options.isHidden()) return;
|
|
644
|
+
inFlight = true;
|
|
645
|
+
try {
|
|
646
|
+
await options.tick();
|
|
647
|
+
} finally {
|
|
648
|
+
inFlight = false;
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
const timer = (options.setIntervalFn ?? setInterval)(() => {
|
|
652
|
+
run();
|
|
653
|
+
}, intervalMs);
|
|
654
|
+
const onVisible = () => {
|
|
655
|
+
if (!options.isHidden()) run();
|
|
656
|
+
};
|
|
657
|
+
const add = options.addListener ?? ((type, listener) => {
|
|
658
|
+
if (typeof document !== "undefined") document.addEventListener(type, listener);
|
|
659
|
+
});
|
|
660
|
+
const remove = options.removeListener ?? ((type, listener) => {
|
|
661
|
+
if (typeof document !== "undefined") document.removeEventListener(type, listener);
|
|
662
|
+
});
|
|
663
|
+
add("visibilitychange", onVisible);
|
|
664
|
+
return () => {
|
|
665
|
+
stopped = true;
|
|
666
|
+
(options.clearIntervalFn ?? clearInterval)(timer);
|
|
667
|
+
remove("visibilitychange", onVisible);
|
|
668
|
+
};
|
|
669
|
+
}
|
|
624
670
|
function isProviderOn(status, provider) {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
return enabled && selected === provider.id;
|
|
671
|
+
if (provider.kind === "fetch") return status.fetchActive && status.settings.fetchProvider === provider.id && provider.configured;
|
|
672
|
+
return status.searchActive && selectedSearchBackend(status)?.id === provider.id;
|
|
628
673
|
}
|
|
629
|
-
function
|
|
630
|
-
|
|
631
|
-
|
|
674
|
+
function dormantSearchPlaceholder(id) {
|
|
675
|
+
return {
|
|
676
|
+
id,
|
|
677
|
+
label: id,
|
|
678
|
+
description: "",
|
|
679
|
+
billing: "unknown",
|
|
680
|
+
kind: "search",
|
|
681
|
+
configured: false,
|
|
682
|
+
calls: 0,
|
|
683
|
+
failures: 0,
|
|
684
|
+
configurationOwned: false
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
function isRegisteredSearch(status, id) {
|
|
688
|
+
return status.providers.some((provider) => provider.kind === "search" && provider.id === id);
|
|
632
689
|
}
|
|
633
690
|
function catalogSearchBackends(status) {
|
|
634
|
-
const
|
|
635
|
-
const
|
|
636
|
-
|
|
691
|
+
const byId = new Map(status.providers.filter((provider) => provider.kind === "search").map((provider) => [provider.id, provider]));
|
|
692
|
+
const rows = [];
|
|
693
|
+
const seen = /* @__PURE__ */ new Set();
|
|
694
|
+
for (const id of status.settings.searchOrder) {
|
|
695
|
+
if (!id || seen.has(id)) continue;
|
|
696
|
+
seen.add(id);
|
|
697
|
+
rows.push(byId.get(id) ?? dormantSearchPlaceholder(id));
|
|
698
|
+
}
|
|
699
|
+
for (const provider of status.providers) {
|
|
700
|
+
if (provider.kind !== "search" || seen.has(provider.id)) continue;
|
|
701
|
+
rows.push(provider);
|
|
702
|
+
}
|
|
703
|
+
return rows;
|
|
704
|
+
}
|
|
705
|
+
function searchBackends(status) {
|
|
706
|
+
const participating = new Set(status.settings.searchOrder);
|
|
707
|
+
return catalogSearchBackends(status).filter((provider) => participating.has(provider.id));
|
|
637
708
|
}
|
|
638
709
|
function selectedSearchBackend(status) {
|
|
639
|
-
const backends = searchBackends(status);
|
|
710
|
+
const backends = searchBackends(status).filter((provider) => isRegisteredSearch(status, provider.id));
|
|
640
711
|
const id = pickActiveSearch(backends.map((provider) => provider.id), (id) => Boolean(backends.find((provider) => provider.id === id)?.configured));
|
|
641
712
|
return backends.find((provider) => provider.id === id);
|
|
642
713
|
}
|
|
643
|
-
function
|
|
714
|
+
function droppedDormantIds(settings, order, providers) {
|
|
715
|
+
const known = new Set(providers.filter((provider) => provider.kind === "search").map((provider) => provider.id));
|
|
716
|
+
return [...new Set(settings.searchOrder.filter((id) => id && !known.has(id) && !order.includes(id)))];
|
|
717
|
+
}
|
|
718
|
+
function configurationAccess(provider) {
|
|
719
|
+
if (ownsProviderConfiguration(provider)) return { kind: "owned" };
|
|
720
|
+
const url = safeConfigurationUrl(provider.configurationUrl);
|
|
721
|
+
if (url) return {
|
|
722
|
+
kind: "link",
|
|
723
|
+
url
|
|
724
|
+
};
|
|
725
|
+
if (provider.configurationOwner?.trim()) return {
|
|
726
|
+
kind: "owner",
|
|
727
|
+
name: provider.configurationOwner
|
|
728
|
+
};
|
|
729
|
+
return { kind: "unknown" };
|
|
730
|
+
}
|
|
731
|
+
function AppLink({ url, label, children }) {
|
|
732
|
+
const external = isExternalConfigurationUrl(url);
|
|
644
733
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
645
734
|
href: url,
|
|
646
|
-
|
|
647
|
-
|
|
735
|
+
...external ? {
|
|
736
|
+
target: "_blank",
|
|
737
|
+
rel: "noreferrer noopener"
|
|
738
|
+
} : {},
|
|
648
739
|
"aria-label": label,
|
|
649
740
|
onClick: (event) => {
|
|
741
|
+
if (!external) return;
|
|
650
742
|
const bridge = globalThis.dshWindow;
|
|
651
743
|
if (bridge?.openExternal) {
|
|
652
744
|
event.preventDefault();
|
|
@@ -662,9 +754,13 @@ function canEnableSearch(order, providers, keys, writable) {
|
|
|
662
754
|
if (!provider) return false;
|
|
663
755
|
if (provider.configured) return true;
|
|
664
756
|
const typed = (keys[provider.id] ?? "").trim();
|
|
665
|
-
return Boolean(provider.credentialRef && typed && writable[provider.credentialRef] !== false);
|
|
757
|
+
return Boolean(ownsProviderConfiguration(provider) && provider.credentialRef && typed && writable[provider.credentialRef] !== false);
|
|
666
758
|
});
|
|
667
759
|
}
|
|
760
|
+
function ownsProviderConfiguration(provider) {
|
|
761
|
+
if (typeof provider.configurationOwned === "boolean") return provider.configurationOwned;
|
|
762
|
+
return !provider.configurationOwner || provider.configurationOwner === "@klarkxy/dsh-web-search-manager";
|
|
763
|
+
}
|
|
668
764
|
function nextSearchEnabled(currentlyEnabled, nextOrder, providers, keys, writable) {
|
|
669
765
|
return currentlyEnabled && canEnableSearch(nextOrder, providers, keys, writable);
|
|
670
766
|
}
|
|
@@ -716,11 +812,14 @@ const copy = {
|
|
|
716
812
|
dragPrefix: "拖动排序 ",
|
|
717
813
|
dragHint: "拖动排序,或用上下方向键",
|
|
718
814
|
inUse: "当前使用",
|
|
815
|
+
available: "可用",
|
|
816
|
+
unavailable: "暂不可用",
|
|
817
|
+
dormant: "未加载,保留选择",
|
|
719
818
|
signup: "去注册",
|
|
720
819
|
pricing: "费用说明",
|
|
721
820
|
website: "官网",
|
|
821
|
+
openConfig: "打开配置",
|
|
722
822
|
needsKey: "待配置 Key",
|
|
723
|
-
unavailable: "暂不可用",
|
|
724
823
|
sharedKey: "与其他设置共用凭据",
|
|
725
824
|
keyConfigured: "已配置,留空不替换",
|
|
726
825
|
keyPlaceholder: "输入 API Key",
|
|
@@ -734,7 +833,11 @@ const copy = {
|
|
|
734
833
|
maxResults: "每条查询结果上限",
|
|
735
834
|
maxQueries: "每次调用查询上限",
|
|
736
835
|
timeoutMs: "请求超时(毫秒)",
|
|
737
|
-
maxFetchChars: "正文字符上限"
|
|
836
|
+
maxFetchChars: "正文字符上限",
|
|
837
|
+
fetchTitle: "网页读取",
|
|
838
|
+
fetchProvider: "读取服务",
|
|
839
|
+
ownerSettings: "配置入口:",
|
|
840
|
+
unknownOwner: "请在服务所属插件中配置"
|
|
738
841
|
},
|
|
739
842
|
en: {
|
|
740
843
|
tabsLabel: "Web search settings",
|
|
@@ -756,11 +859,14 @@ const copy = {
|
|
|
756
859
|
dragPrefix: "Reorder ",
|
|
757
860
|
dragHint: "Drag to reorder, or use arrow keys",
|
|
758
861
|
inUse: "In use",
|
|
862
|
+
available: "Available",
|
|
863
|
+
unavailable: "Unavailable",
|
|
864
|
+
dormant: "Not loaded; selection kept",
|
|
759
865
|
signup: "Sign up",
|
|
760
866
|
pricing: "Pricing",
|
|
761
867
|
website: "Website",
|
|
868
|
+
openConfig: "Open configuration",
|
|
762
869
|
needsKey: "Key required",
|
|
763
|
-
unavailable: "Unavailable",
|
|
764
870
|
sharedKey: "Shares a credential with other settings",
|
|
765
871
|
keyConfigured: "Configured; empty keeps it",
|
|
766
872
|
keyPlaceholder: "Enter API key",
|
|
@@ -774,14 +880,38 @@ const copy = {
|
|
|
774
880
|
maxResults: "Max results/query",
|
|
775
881
|
maxQueries: "Max queries/call",
|
|
776
882
|
timeoutMs: "Request timeout (ms)",
|
|
777
|
-
maxFetchChars: "Max text chars"
|
|
883
|
+
maxFetchChars: "Max text chars",
|
|
884
|
+
fetchTitle: "Read web pages",
|
|
885
|
+
fetchProvider: "Fetch service",
|
|
886
|
+
ownerSettings: "Configure in: ",
|
|
887
|
+
unknownOwner: "Configure this service in its plugin"
|
|
778
888
|
}
|
|
779
889
|
};
|
|
890
|
+
var UiError = class extends Error {
|
|
891
|
+
key;
|
|
892
|
+
constructor(key) {
|
|
893
|
+
super(key);
|
|
894
|
+
this.key = key;
|
|
895
|
+
}
|
|
896
|
+
};
|
|
897
|
+
function uiMessage(message, locale, providers = []) {
|
|
898
|
+
if (typeof message === "string") return localErrorText(message, locale);
|
|
899
|
+
if ("key" in message) return copy[locale][message.key];
|
|
900
|
+
if ("sources" in message) return testOkText(locale, message.sources);
|
|
901
|
+
if ("moved" in message) {
|
|
902
|
+
const provider = providers.find((item) => item.id === message.moved);
|
|
903
|
+
return movedText(locale, provider ? providerDisplayLabel(provider, locale) : message.moved, message.position);
|
|
904
|
+
}
|
|
905
|
+
return message.invalid.map((key) => {
|
|
906
|
+
const [, min, max] = LIMITS.find((item) => item[0] === key);
|
|
907
|
+
return limitInvalidText(locale, copy[locale][key], min, max);
|
|
908
|
+
}).join(" ");
|
|
909
|
+
}
|
|
780
910
|
function movedText(locale, label, position) {
|
|
781
911
|
return locale === "zh" ? `${label} 已移至第 ${position} 位` : `${label} moved to position ${position}`;
|
|
782
912
|
}
|
|
783
913
|
function pricingCheckedText(locale, date) {
|
|
784
|
-
return locale === "zh" ?
|
|
914
|
+
return locale === "zh" ? `内置适配器价格核对于 ${date},额度与费用以供应商账户为准。` : `Bundled adapter prices checked on ${date}; your provider account is authoritative.`;
|
|
785
915
|
}
|
|
786
916
|
function testOkText(locale, sources) {
|
|
787
917
|
return locale === "zh" ? `连接正常,返回 ${sources} 条来源。` : `Connection OK: ${sources} sources returned.`;
|
|
@@ -822,13 +952,25 @@ function parseLimit(raw, min, max) {
|
|
|
822
952
|
* Settings written by an immediate action (a switch or a reorder). It keeps the
|
|
823
953
|
* saved limits and never includes typed-but-unsaved keys or limit drafts.
|
|
824
954
|
*/
|
|
825
|
-
function immediateUpdate(settings, on, order) {
|
|
955
|
+
function immediateUpdate(settings, on, order, providers, remove = []) {
|
|
956
|
+
let searchOrder = order;
|
|
957
|
+
if (providers) {
|
|
958
|
+
const known = new Set(providers.filter((provider) => provider.kind === "search").map((provider) => provider.id));
|
|
959
|
+
const removed = new Set(remove);
|
|
960
|
+
const following = order.filter((id) => !removed.has(id));
|
|
961
|
+
if (following.some((id) => !known.has(id))) searchOrder = following;
|
|
962
|
+
else {
|
|
963
|
+
searchOrder = settings.searchOrder.flatMap((id) => {
|
|
964
|
+
if (removed.has(id)) return [];
|
|
965
|
+
return known.has(id) ? following.splice(0, 1) : [id];
|
|
966
|
+
});
|
|
967
|
+
searchOrder.push(...following);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
826
970
|
return {
|
|
827
971
|
...editable(settings),
|
|
828
|
-
searchOrder
|
|
829
|
-
searchEnabled: on
|
|
830
|
-
fetchProvider: "http",
|
|
831
|
-
fetchEnabled: on
|
|
972
|
+
searchOrder,
|
|
973
|
+
searchEnabled: on
|
|
832
974
|
};
|
|
833
975
|
}
|
|
834
976
|
function limitTexts(settings) {
|
|
@@ -846,7 +988,7 @@ function useHostLocale(client) {
|
|
|
846
988
|
}
|
|
847
989
|
/** Keyboard reorders are batched: persist once the arrows stop for this long. */
|
|
848
990
|
const REORDER_DEBOUNCE_MS = 500;
|
|
849
|
-
function NetworkSearchSettings({ client }) {
|
|
991
|
+
function NetworkSearchSettings({ client, hidden }) {
|
|
850
992
|
const locale = useHostLocale(client);
|
|
851
993
|
const text = copy[locale];
|
|
852
994
|
const tabsId = (0, react.useId)();
|
|
@@ -870,6 +1012,17 @@ function NetworkSearchSettings({ client }) {
|
|
|
870
1012
|
const timerRef = (0, react.useRef)(void 0);
|
|
871
1013
|
const handleRefs = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
872
1014
|
const refocusRef = (0, react.useRef)(null);
|
|
1015
|
+
const busyRef = (0, react.useRef)(false);
|
|
1016
|
+
const hiddenRef = (0, react.useRef)(hidden);
|
|
1017
|
+
hiddenRef.current = hidden;
|
|
1018
|
+
const requestSeq = (0, react.useRef)(0);
|
|
1019
|
+
const mountedRef = (0, react.useRef)(true);
|
|
1020
|
+
const readingRef = (0, react.useRef)(false);
|
|
1021
|
+
const readingRequest = (0, react.useRef)(0);
|
|
1022
|
+
const statusRead = (0, react.useRef)(Promise.resolve());
|
|
1023
|
+
const readGateRef = (0, react.useRef)(void 0);
|
|
1024
|
+
if (!readGateRef.current) readGateRef.current = createPluginReadGate();
|
|
1025
|
+
const readGate = readGateRef.current;
|
|
873
1026
|
async function call(endpoint, payload = {}) {
|
|
874
1027
|
return unwrap(await client.connection.rpc.call(WEB_SEARCH_RPC_CHANNEL, endpoint, payload));
|
|
875
1028
|
}
|
|
@@ -885,30 +1038,109 @@ function NetworkSearchSettings({ client }) {
|
|
|
885
1038
|
});
|
|
886
1039
|
if (resetDraft) setInvalid({});
|
|
887
1040
|
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
1041
|
+
function pollBlocked() {
|
|
1042
|
+
return !canStartStatusRead({
|
|
1043
|
+
source: "poll",
|
|
1044
|
+
mounted: mountedRef.current,
|
|
1045
|
+
hidden: isPageHidden(hiddenRef.current),
|
|
1046
|
+
busy: busyRef.current,
|
|
1047
|
+
reordering: pendingRef.current !== null,
|
|
1048
|
+
reading: readingRef.current
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
async function describeOwned(next, mine) {
|
|
1052
|
+
const refs = next.providers.flatMap((provider) => ownsProviderConfiguration(provider) && provider.credentialRef ? [provider.credentialRef] : []);
|
|
1053
|
+
if (!refs.length) return;
|
|
1054
|
+
const credentials = unwrap(await readGate.run(() => client.remote.credentials.describe(refs)));
|
|
1055
|
+
if (!mountedRef.current || mine !== requestSeq.current) return;
|
|
1056
|
+
setWritable(Object.fromEntries(Object.entries(credentials).map(([ref, value]) => [ref, value.writable])));
|
|
1057
|
+
}
|
|
1058
|
+
async function load(resetDraft = false, source = "user") {
|
|
1059
|
+
if (source === "poll" && !readGate.canRead()) return;
|
|
1060
|
+
if (source === "user") {
|
|
1061
|
+
readGate.reset();
|
|
1062
|
+
requestSeq.current += 1;
|
|
1063
|
+
}
|
|
1064
|
+
while (readingRef.current) {
|
|
1065
|
+
if (source === "poll" || !mountedRef.current) return;
|
|
1066
|
+
await statusRead.current;
|
|
1067
|
+
}
|
|
1068
|
+
if (!mountedRef.current) return;
|
|
1069
|
+
if (source === "poll" && pollBlocked()) return;
|
|
1070
|
+
const mine = ++requestSeq.current;
|
|
1071
|
+
readingRef.current = true;
|
|
1072
|
+
readingRequest.current = mine;
|
|
1073
|
+
let release = () => {};
|
|
1074
|
+
statusRead.current = new Promise((resolve) => {
|
|
1075
|
+
release = resolve;
|
|
1076
|
+
});
|
|
1077
|
+
try {
|
|
1078
|
+
const next = unwrap(await readGate.run(() => client.connection.rpc.call(WEB_SEARCH_RPC_CHANNEL, "status", {})));
|
|
1079
|
+
if (!mountedRef.current || mine !== requestSeq.current) return next;
|
|
1080
|
+
if (source === "poll" && !shouldApplyPolledStatus({
|
|
1081
|
+
requestId: mine,
|
|
1082
|
+
latestRequestId: requestSeq.current,
|
|
1083
|
+
busy: busyRef.current,
|
|
1084
|
+
reordering: pendingRef.current !== null,
|
|
1085
|
+
hidden: isPageHidden(hiddenRef.current),
|
|
1086
|
+
currentRevision: statusRef.current?.settings.revision,
|
|
1087
|
+
incomingRevision: next.settings.revision
|
|
1088
|
+
})) return next;
|
|
1089
|
+
adopt(next, resetDraft);
|
|
1090
|
+
setLoadError("");
|
|
1091
|
+
await describeOwned(next, mine);
|
|
1092
|
+
return next;
|
|
1093
|
+
} catch (cause) {
|
|
1094
|
+
if (!mountedRef.current || mine !== requestSeq.current) return;
|
|
1095
|
+
setLoadError(cause instanceof Error && cause.message ? cause.message : { key: "failed" });
|
|
1096
|
+
if (source === "poll") return;
|
|
1097
|
+
throw cause;
|
|
1098
|
+
} finally {
|
|
1099
|
+
if (readingRequest.current === mine) readingRef.current = false;
|
|
1100
|
+
release();
|
|
896
1101
|
}
|
|
897
|
-
return next;
|
|
898
1102
|
}
|
|
899
1103
|
(0, react.useEffect)(() => {
|
|
900
|
-
|
|
1104
|
+
mountedRef.current = true;
|
|
1105
|
+
readGate.reset();
|
|
1106
|
+
load().catch((cause) => setLoadError(cause instanceof Error && cause.message ? cause.message : { key: "failed" }));
|
|
1107
|
+
const bind = () => bindStatusRefresh({
|
|
1108
|
+
isHidden: () => isPageHidden(hiddenRef.current),
|
|
1109
|
+
tick: async () => {
|
|
1110
|
+
await load(false, "poll").catch(() => {});
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
let stop = bind();
|
|
1114
|
+
const stopConnection = client.connection.generation?.subscribe(() => {
|
|
1115
|
+
readGate.reset();
|
|
1116
|
+
requestSeq.current += 1;
|
|
1117
|
+
readingRef.current = false;
|
|
1118
|
+
statusRead.current = Promise.resolve();
|
|
1119
|
+
stop();
|
|
1120
|
+
stop = bind();
|
|
1121
|
+
load(false, "poll");
|
|
1122
|
+
});
|
|
1123
|
+
return () => {
|
|
1124
|
+
mountedRef.current = false;
|
|
1125
|
+
readGate.reset();
|
|
1126
|
+
requestSeq.current += 1;
|
|
1127
|
+
stop();
|
|
1128
|
+
stopConnection?.();
|
|
1129
|
+
if (timerRef.current === void 0) return;
|
|
1130
|
+
clearTimeout(timerRef.current);
|
|
1131
|
+
const current = statusRef.current;
|
|
1132
|
+
const next = pendingRef.current;
|
|
1133
|
+
if (current && next) call("update", {
|
|
1134
|
+
settings: immediateUpdate(current.settings, current.settings.searchEnabled, next, current.providers),
|
|
1135
|
+
expectedRevision: current.settings.revision
|
|
1136
|
+
}).catch(() => {});
|
|
1137
|
+
};
|
|
901
1138
|
}, [client]);
|
|
902
|
-
(0, react.useEffect)(() =>
|
|
903
|
-
if (
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
if (current && next) call("update", {
|
|
908
|
-
settings: immediateUpdate(current.settings, current.settings.searchEnabled, next),
|
|
909
|
-
expectedRevision: current.settings.revision
|
|
910
|
-
}).catch(() => {});
|
|
911
|
-
}, []);
|
|
1139
|
+
(0, react.useEffect)(() => {
|
|
1140
|
+
if (hidden) return;
|
|
1141
|
+
if (!statusRef.current) return;
|
|
1142
|
+
load(false, "poll").catch(() => {});
|
|
1143
|
+
}, [hidden]);
|
|
912
1144
|
(0, react.useLayoutEffect)(() => {
|
|
913
1145
|
const id = refocusRef.current;
|
|
914
1146
|
if (!id) return;
|
|
@@ -917,20 +1149,24 @@ function NetworkSearchSettings({ client }) {
|
|
|
917
1149
|
if (handle && document.activeElement !== handle) handle.focus();
|
|
918
1150
|
});
|
|
919
1151
|
async function action(run) {
|
|
1152
|
+
readGate.reset();
|
|
1153
|
+
requestSeq.current += 1;
|
|
1154
|
+
busyRef.current = true;
|
|
920
1155
|
setBusy(true);
|
|
921
1156
|
setNote("");
|
|
922
1157
|
setError("");
|
|
923
1158
|
try {
|
|
924
1159
|
await run();
|
|
925
1160
|
} catch (cause) {
|
|
926
|
-
setError(cause instanceof Error && cause.message ? cause.message :
|
|
1161
|
+
setError(cause instanceof UiError ? { key: cause.key } : cause instanceof Error && cause.message ? cause.message : { key: "failed" });
|
|
927
1162
|
await load().catch(() => {});
|
|
928
1163
|
} finally {
|
|
1164
|
+
busyRef.current = false;
|
|
929
1165
|
setBusy(false);
|
|
930
1166
|
}
|
|
931
1167
|
}
|
|
932
1168
|
/** Switches and reorders: settings only, applied at once. */
|
|
933
|
-
function runImmediate(on, order) {
|
|
1169
|
+
function runImmediate(on, order, remove = []) {
|
|
934
1170
|
clearTimeout(timerRef.current);
|
|
935
1171
|
timerRef.current = void 0;
|
|
936
1172
|
pendingRef.current = null;
|
|
@@ -938,7 +1174,7 @@ function NetworkSearchSettings({ client }) {
|
|
|
938
1174
|
const current = statusRef.current;
|
|
939
1175
|
if (!current) return;
|
|
940
1176
|
const next = await call("update", {
|
|
941
|
-
settings: immediateUpdate(current.settings, on, order),
|
|
1177
|
+
settings: immediateUpdate(current.settings, on, order, current.providers, remove),
|
|
942
1178
|
expectedRevision: current.settings.revision
|
|
943
1179
|
});
|
|
944
1180
|
setStatus(next);
|
|
@@ -954,8 +1190,8 @@ function NetworkSearchSettings({ client }) {
|
|
|
954
1190
|
if (provider.kind !== "search" || only && provider.id !== only.id) continue;
|
|
955
1191
|
const snapshot = keys[provider.id] ?? "";
|
|
956
1192
|
const typed = snapshot.trim();
|
|
957
|
-
if (!provider.credentialRef || !typed) continue;
|
|
958
|
-
if (writable[provider.credentialRef] === false) throw new
|
|
1193
|
+
if (!ownsProviderConfiguration(provider) || !provider.credentialRef || !typed) continue;
|
|
1194
|
+
if (writable[provider.credentialRef] === false) throw new UiError("readOnly");
|
|
959
1195
|
unwrap(await client.remote.credentials.set(provider.credentialRef, typed));
|
|
960
1196
|
setKeys((current) => current[provider.id] === snapshot ? {
|
|
961
1197
|
...current,
|
|
@@ -964,21 +1200,21 @@ function NetworkSearchSettings({ client }) {
|
|
|
964
1200
|
}
|
|
965
1201
|
}
|
|
966
1202
|
async function removeKey(provider) {
|
|
967
|
-
if (!provider.credentialRef || provider.credentialShared || !status) return;
|
|
1203
|
+
if (!ownsProviderConfiguration(provider) || !provider.credentialRef || provider.credentialShared || !status) return;
|
|
968
1204
|
unwrap(await client.remote.credentials.unset(provider.credentialRef));
|
|
969
1205
|
setKeys((current) => ({
|
|
970
1206
|
...current,
|
|
971
1207
|
[provider.id]: ""
|
|
972
1208
|
}));
|
|
973
1209
|
await load();
|
|
974
|
-
setNote(
|
|
1210
|
+
setNote({ key: "keyDeleted" });
|
|
975
1211
|
}
|
|
976
1212
|
if (!status || !limitDraft) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
977
1213
|
className: "web-search-settings ws-root dsh-ui-panel",
|
|
978
1214
|
children: [loadError ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
979
1215
|
role: "alert",
|
|
980
1216
|
className: "dsh-ui-error",
|
|
981
|
-
children: [text.loadFailed, loadError]
|
|
1217
|
+
children: [text.loadFailed, uiMessage(loadError, locale, status?.providers)]
|
|
982
1218
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
983
1219
|
role: "status",
|
|
984
1220
|
className: "dsh-ui-hint",
|
|
@@ -1017,7 +1253,10 @@ function NetworkSearchSettings({ client }) {
|
|
|
1017
1253
|
pendingRef.current = next;
|
|
1018
1254
|
setPendingOrder(next);
|
|
1019
1255
|
if (keyboard) refocusRef.current = id;
|
|
1020
|
-
setSortAnnouncement(
|
|
1256
|
+
setSortAnnouncement({
|
|
1257
|
+
moved: id,
|
|
1258
|
+
position: nextIndex + 1
|
|
1259
|
+
});
|
|
1021
1260
|
clearTimeout(timerRef.current);
|
|
1022
1261
|
timerRef.current = setTimeout(() => {
|
|
1023
1262
|
timerRef.current = void 0;
|
|
@@ -1038,13 +1277,13 @@ function NetworkSearchSettings({ client }) {
|
|
|
1038
1277
|
const value = parseLimit(limitDraft[key], min, max);
|
|
1039
1278
|
if (value === void 0) {
|
|
1040
1279
|
nextInvalid[key] = true;
|
|
1041
|
-
messages.push(
|
|
1280
|
+
messages.push(key);
|
|
1042
1281
|
} else parsed[key] = value;
|
|
1043
1282
|
}
|
|
1044
1283
|
setInvalid(nextInvalid);
|
|
1045
1284
|
if (messages.length) {
|
|
1046
1285
|
setNote("");
|
|
1047
|
-
setError(messages
|
|
1286
|
+
setError({ invalid: messages });
|
|
1048
1287
|
return;
|
|
1049
1288
|
}
|
|
1050
1289
|
action(async () => {
|
|
@@ -1053,14 +1292,12 @@ function NetworkSearchSettings({ client }) {
|
|
|
1053
1292
|
adopt(await call("update", {
|
|
1054
1293
|
settings: {
|
|
1055
1294
|
...editable(current.settings),
|
|
1056
|
-
...parsed
|
|
1057
|
-
fetchProvider: "http",
|
|
1058
|
-
fetchEnabled: current.settings.searchEnabled
|
|
1295
|
+
...parsed
|
|
1059
1296
|
},
|
|
1060
1297
|
expectedRevision: current.settings.revision
|
|
1061
1298
|
}), true);
|
|
1062
1299
|
await load();
|
|
1063
|
-
setNote(
|
|
1300
|
+
setNote({ key: "saved" });
|
|
1064
1301
|
});
|
|
1065
1302
|
}
|
|
1066
1303
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
@@ -1087,30 +1324,46 @@ function NetworkSearchSettings({ client }) {
|
|
|
1087
1324
|
className: "ws-sr-only",
|
|
1088
1325
|
role: "status",
|
|
1089
1326
|
"aria-live": "polite",
|
|
1090
|
-
children: sortAnnouncement
|
|
1327
|
+
children: uiMessage(sortAnnouncement, locale, saved.providers)
|
|
1091
1328
|
}),
|
|
1092
1329
|
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1093
1330
|
role: "alert",
|
|
1094
1331
|
className: "dsh-ui-error",
|
|
1095
|
-
children: error
|
|
1332
|
+
children: uiMessage(error, locale, saved.providers)
|
|
1096
1333
|
}),
|
|
1334
|
+
loadError ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1335
|
+
className: "dsh-ui-stack",
|
|
1336
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1337
|
+
role: "alert",
|
|
1338
|
+
className: "dsh-ui-error",
|
|
1339
|
+
children: [text.loadFailed, uiMessage(loadError, locale, status?.providers)]
|
|
1340
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1341
|
+
variant: "outline",
|
|
1342
|
+
size: "sm",
|
|
1343
|
+
disabled: busy,
|
|
1344
|
+
onClick: () => void action(async () => {
|
|
1345
|
+
await load();
|
|
1346
|
+
}),
|
|
1347
|
+
children: text.reconnect
|
|
1348
|
+
})]
|
|
1349
|
+
}) : null,
|
|
1097
1350
|
note && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1098
1351
|
role: "status",
|
|
1099
1352
|
className: "dsh-ui-notice",
|
|
1100
|
-
children: note
|
|
1353
|
+
children: uiMessage(note, locale, saved.providers)
|
|
1101
1354
|
}),
|
|
1102
1355
|
saved.storageFailed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1103
1356
|
role: "alert",
|
|
1104
1357
|
className: "dsh-ui-banner dsh-ui-banner--danger",
|
|
1105
1358
|
children: text.storageFailed
|
|
1106
1359
|
}),
|
|
1107
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1360
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1108
1361
|
role: "tabpanel",
|
|
1109
1362
|
id: tabsId + "-providers-panel",
|
|
1110
1363
|
"aria-labelledby": tabsId + "-providers-tab",
|
|
1111
1364
|
hidden: tab !== "providers",
|
|
1112
1365
|
tabIndex: 0,
|
|
1113
|
-
children: backends.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1366
|
+
children: [backends.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1114
1367
|
className: "dsh-ui-empty",
|
|
1115
1368
|
children: text.empty
|
|
1116
1369
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
@@ -1139,17 +1392,23 @@ function NetworkSearchSettings({ client }) {
|
|
|
1139
1392
|
children: backends.map((provider) => {
|
|
1140
1393
|
const participating = order.includes(provider.id);
|
|
1141
1394
|
const rank = order.indexOf(provider.id);
|
|
1142
|
-
const
|
|
1143
|
-
const
|
|
1144
|
-
const
|
|
1395
|
+
const dormant = !isRegisteredSearch(saved, provider.id);
|
|
1396
|
+
const active = !dormant && saved.searchActive && selected?.id === provider.id;
|
|
1397
|
+
const info = searchProviderInfo(provider, locale);
|
|
1398
|
+
const owned = ownsProviderConfiguration(provider);
|
|
1399
|
+
const access = configurationAccess(provider);
|
|
1400
|
+
const needsKey = owned && Boolean(provider.credentialRef);
|
|
1145
1401
|
const shared = Boolean(provider.credentialShared);
|
|
1146
|
-
const showKey = needsKey && participating && (!shared || !provider.configured);
|
|
1402
|
+
const showKey = !dormant && needsKey && participating && (!shared || !provider.configured);
|
|
1147
1403
|
const canSort = participating && order.length > 1;
|
|
1148
1404
|
const readOnly = writable[provider.credentialRef ?? ""] === false;
|
|
1149
1405
|
const typedKey = (keys[provider.id] ?? "").trim();
|
|
1406
|
+
const availability = dormant ? "dormant" : provider.configured ? "available" : "unavailable";
|
|
1150
1407
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
1151
1408
|
className: active ? "is-active" : void 0,
|
|
1152
1409
|
"data-testid": `web-search-rank-${provider.id}`,
|
|
1410
|
+
"data-availability": availability,
|
|
1411
|
+
"data-in-use": active || void 0,
|
|
1153
1412
|
"data-dragging": dragSource === provider.id || void 0,
|
|
1154
1413
|
"data-drop": dropTarget === provider.id && dragSource ? order.indexOf(dragSource) < rank ? "after" : "before" : void 0,
|
|
1155
1414
|
onDragOver: (event) => {
|
|
@@ -1217,7 +1476,7 @@ function NetworkSearchSettings({ client }) {
|
|
|
1217
1476
|
}),
|
|
1218
1477
|
draggable: !busy,
|
|
1219
1478
|
"aria-disabled": busy,
|
|
1220
|
-
"aria-label": text.dragPrefix + provider
|
|
1479
|
+
"aria-label": text.dragPrefix + providerDisplayLabel(provider, locale),
|
|
1221
1480
|
title: text.dragHint,
|
|
1222
1481
|
onDragStart: (event) => {
|
|
1223
1482
|
if (busy) {
|
|
@@ -1249,35 +1508,49 @@ function NetworkSearchSettings({ client }) {
|
|
|
1249
1508
|
children: [
|
|
1250
1509
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
|
|
1251
1510
|
className: "dsh-ui-heading",
|
|
1252
|
-
children: provider
|
|
1511
|
+
children: providerDisplayLabel(provider, locale)
|
|
1512
|
+
}),
|
|
1513
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1514
|
+
className: "ws-provider-note",
|
|
1515
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: dormant ? "idle" : provider.configured ? "done" : "warning" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: dormant ? text.dormant : provider.configured ? text.available : text.unavailable })]
|
|
1253
1516
|
}),
|
|
1254
1517
|
active && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
|
|
1255
1518
|
tone: "success",
|
|
1256
1519
|
children: text.inUse
|
|
1257
1520
|
}),
|
|
1258
|
-
provider.signupUrl && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1521
|
+
provider.signupUrl && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppLink, {
|
|
1259
1522
|
url: provider.signupUrl,
|
|
1260
|
-
label: provider
|
|
1523
|
+
label: providerDisplayLabel(provider, locale) + " " + text.signup,
|
|
1261
1524
|
children: text.signup
|
|
1262
1525
|
})
|
|
1263
1526
|
]
|
|
1264
1527
|
}),
|
|
1265
1528
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1266
1529
|
className: "web-search-provider-info",
|
|
1267
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: info.description }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [info.pricing, info.pricingUrl && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [" ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1530
|
+
children: [info.description ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: info.description }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [info.pricing, info.pricingUrl && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [" ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppLink, {
|
|
1268
1531
|
url: info.pricingUrl,
|
|
1269
|
-
label: provider
|
|
1532
|
+
label: providerDisplayLabel(provider, locale) + " " + text.pricing,
|
|
1270
1533
|
children: provider.id === "ddg" ? text.website : text.pricing
|
|
1271
1534
|
})] })] })]
|
|
1272
1535
|
}),
|
|
1273
|
-
|
|
1274
|
-
className: "
|
|
1275
|
-
children:
|
|
1536
|
+
access.kind === "link" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1537
|
+
className: "dsh-ui-hint",
|
|
1538
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppLink, {
|
|
1539
|
+
url: access.url,
|
|
1540
|
+
label: text.openConfig,
|
|
1541
|
+
children: text.openConfig
|
|
1542
|
+
})
|
|
1543
|
+
}) : access.kind === "owner" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1544
|
+
className: "dsh-ui-hint",
|
|
1545
|
+
children: [text.ownerSettings, access.name]
|
|
1546
|
+
}) : access.kind === "unknown" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1547
|
+
className: "dsh-ui-hint",
|
|
1548
|
+
children: text.unknownOwner
|
|
1276
1549
|
}) : null,
|
|
1277
|
-
shared && participating && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", {
|
|
1550
|
+
shared && participating && owned ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", {
|
|
1278
1551
|
className: "dsh-ui-meta",
|
|
1279
|
-
children: provider.credentialHint ?? text.sharedKey
|
|
1280
|
-
}),
|
|
1552
|
+
children: provider.credentialHint === "与模型设置共用 Key" ? locale === "zh" ? provider.credentialHint : "Shares a key with model settings" : provider.credentialHint ?? text.sharedKey
|
|
1553
|
+
}) : null,
|
|
1281
1554
|
showKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1282
1555
|
className: "dsh-ui-field",
|
|
1283
1556
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -1306,7 +1579,7 @@ function NetworkSearchSettings({ client }) {
|
|
|
1306
1579
|
onClick: () => void action(async () => {
|
|
1307
1580
|
await saveKeys(provider);
|
|
1308
1581
|
await load();
|
|
1309
|
-
setNote(
|
|
1582
|
+
setNote({ key: "keySaved" });
|
|
1310
1583
|
}),
|
|
1311
1584
|
children: text.saveKey
|
|
1312
1585
|
}) : null, needsKey && !shared && provider.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmButton, {
|
|
@@ -1322,11 +1595,11 @@ function NetworkSearchSettings({ client }) {
|
|
|
1322
1595
|
className: "web-search-rank-move",
|
|
1323
1596
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
1324
1597
|
checked: participating,
|
|
1325
|
-
label: provider
|
|
1598
|
+
label: providerDisplayLabel(provider, locale),
|
|
1326
1599
|
disabled: busy,
|
|
1327
1600
|
onChange: (next) => {
|
|
1328
1601
|
const following = next ? [...order, provider.id] : order.filter((id) => id !== provider.id);
|
|
1329
|
-
runImmediate(nextSearchEnabled(saved.settings.searchEnabled, following, saved.providers, {}, writable), following);
|
|
1602
|
+
runImmediate(nextSearchEnabled(saved.settings.searchEnabled, following, saved.providers, {}, writable), following, droppedDormantIds(saved.settings, following, saved.providers));
|
|
1330
1603
|
}
|
|
1331
1604
|
})
|
|
1332
1605
|
})
|
|
@@ -1334,10 +1607,10 @@ function NetworkSearchSettings({ client }) {
|
|
|
1334
1607
|
}, provider.id);
|
|
1335
1608
|
})
|
|
1336
1609
|
}),
|
|
1337
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1610
|
+
backends.some(usesBundledPricing) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1338
1611
|
className: "dsh-ui-hint",
|
|
1339
1612
|
children: pricingCheckedText(locale, PROVIDER_PRICING_CHECKED)
|
|
1340
|
-
}),
|
|
1613
|
+
}) : null,
|
|
1341
1614
|
on ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1342
1615
|
className: "dsh-ui-actions",
|
|
1343
1616
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
@@ -1348,13 +1621,110 @@ function NetworkSearchSettings({ client }) {
|
|
|
1348
1621
|
await saveKeys();
|
|
1349
1622
|
const result = await call("test");
|
|
1350
1623
|
await load();
|
|
1351
|
-
setNote(
|
|
1624
|
+
setNote({ sources: result.sources });
|
|
1352
1625
|
}),
|
|
1353
1626
|
children: text.test
|
|
1354
1627
|
})
|
|
1355
1628
|
}) : null
|
|
1356
1629
|
]
|
|
1357
|
-
})
|
|
1630
|
+
}), (saved.providers.some((provider) => provider.kind === "fetch") || saved.settings.fetchProvider) && (() => {
|
|
1631
|
+
const fetchId = saved.settings.fetchProvider;
|
|
1632
|
+
const fetchSelected = saved.providers.find((provider) => provider.kind === "fetch" && provider.id === fetchId);
|
|
1633
|
+
const fetchDormant = Boolean(fetchId) && !fetchSelected;
|
|
1634
|
+
const fetchActive = Boolean(fetchSelected && saved.fetchActive && fetchSelected.configured);
|
|
1635
|
+
const fetchAccess = fetchSelected ? configurationAccess(fetchSelected) : fetchDormant ? { kind: "unknown" } : void 0;
|
|
1636
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
1637
|
+
className: "dsh-ui-card dsh-ui-card--flat",
|
|
1638
|
+
"data-testid": "web-search-fetch",
|
|
1639
|
+
"data-availability": fetchDormant ? "dormant" : fetchSelected?.configured ? "available" : "unavailable",
|
|
1640
|
+
"data-in-use": fetchActive || void 0,
|
|
1641
|
+
children: [
|
|
1642
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
1643
|
+
className: "ws-card-head",
|
|
1644
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1645
|
+
className: "dsh-ui-title",
|
|
1646
|
+
children: text.fetchTitle
|
|
1647
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1648
|
+
className: "ws-provider-note",
|
|
1649
|
+
children: [
|
|
1650
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: fetchDormant ? "idle" : fetchSelected?.configured ? "done" : "warning" }),
|
|
1651
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: fetchDormant ? text.dormant : fetchSelected?.configured ? text.available : text.unavailable }),
|
|
1652
|
+
fetchActive ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tag, {
|
|
1653
|
+
tone: "success",
|
|
1654
|
+
children: text.inUse
|
|
1655
|
+
}) : null
|
|
1656
|
+
]
|
|
1657
|
+
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
1658
|
+
checked: saved.settings.fetchEnabled,
|
|
1659
|
+
label: text.fetchTitle,
|
|
1660
|
+
disabled: busy || reordering,
|
|
1661
|
+
onChange: (next) => void action(async () => {
|
|
1662
|
+
const current = statusRef.current;
|
|
1663
|
+
adopt(await call("update", {
|
|
1664
|
+
settings: {
|
|
1665
|
+
...editable(current.settings),
|
|
1666
|
+
fetchEnabled: next
|
|
1667
|
+
},
|
|
1668
|
+
expectedRevision: current.settings.revision
|
|
1669
|
+
}));
|
|
1670
|
+
await load();
|
|
1671
|
+
})
|
|
1672
|
+
})]
|
|
1673
|
+
}),
|
|
1674
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1675
|
+
className: "dsh-ui-field",
|
|
1676
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1677
|
+
className: "dsh-ui-label",
|
|
1678
|
+
children: text.fetchProvider
|
|
1679
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1680
|
+
className: "dsh-ui-select",
|
|
1681
|
+
"aria-label": text.fetchProvider,
|
|
1682
|
+
value: fetchId,
|
|
1683
|
+
disabled: busy || reordering,
|
|
1684
|
+
onChange: (event) => {
|
|
1685
|
+
const id = event.target.value;
|
|
1686
|
+
action(async () => {
|
|
1687
|
+
const current = statusRef.current;
|
|
1688
|
+
adopt(await call("update", {
|
|
1689
|
+
settings: {
|
|
1690
|
+
...editable(current.settings),
|
|
1691
|
+
fetchProvider: id
|
|
1692
|
+
},
|
|
1693
|
+
expectedRevision: current.settings.revision
|
|
1694
|
+
}));
|
|
1695
|
+
await load();
|
|
1696
|
+
});
|
|
1697
|
+
},
|
|
1698
|
+
children: [fetchDormant && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
1699
|
+
value: fetchId,
|
|
1700
|
+
children: [
|
|
1701
|
+
fetchId,
|
|
1702
|
+
" · ",
|
|
1703
|
+
text.dormant
|
|
1704
|
+
]
|
|
1705
|
+
}), saved.providers.filter((provider) => provider.kind === "fetch").map((provider) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
1706
|
+
value: provider.id,
|
|
1707
|
+
children: [providerDisplayLabel(provider, locale), provider.configured ? "" : " · " + text.unavailable]
|
|
1708
|
+
}, provider.id))]
|
|
1709
|
+
})]
|
|
1710
|
+
}),
|
|
1711
|
+
fetchAccess?.kind === "link" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1712
|
+
className: "dsh-ui-hint",
|
|
1713
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppLink, {
|
|
1714
|
+
url: fetchAccess.url,
|
|
1715
|
+
label: text.openConfig,
|
|
1716
|
+
children: text.openConfig
|
|
1717
|
+
})
|
|
1718
|
+
}) : fetchAccess?.kind === "owner" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1719
|
+
className: "dsh-ui-hint",
|
|
1720
|
+
children: [text.ownerSettings, fetchAccess.name]
|
|
1721
|
+
}) : fetchAccess?.kind === "unknown" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1722
|
+
className: "dsh-ui-hint",
|
|
1723
|
+
children: text.unknownOwner
|
|
1724
|
+
}) : null
|
|
1725
|
+
]
|
|
1726
|
+
});
|
|
1727
|
+
})()]
|
|
1358
1728
|
}),
|
|
1359
1729
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1360
1730
|
role: "tabpanel",
|
|
@@ -1496,21 +1866,35 @@ function apply(ctx) {
|
|
|
1496
1866
|
ctx.effect(() => client.slots.inject("plugins.bundle.config", () => client.slots.register({
|
|
1497
1867
|
name: "plugins.bundle.config",
|
|
1498
1868
|
key: "@klarkxy/dsh-web-search-manager"
|
|
1499
|
-
}, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NetworkSearchSettings, {
|
|
1869
|
+
}, (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NetworkSearchSettings, {
|
|
1870
|
+
client,
|
|
1871
|
+
hidden: props?.hidden
|
|
1872
|
+
}))), "web-search-manager.settings");
|
|
1500
1873
|
}
|
|
1501
1874
|
//#endregion
|
|
1502
1875
|
exports.NetworkSearchSettings = NetworkSearchSettings;
|
|
1876
|
+
exports.STATUS_REFRESH_MS = STATUS_REFRESH_MS;
|
|
1503
1877
|
exports.apply = apply;
|
|
1878
|
+
exports.bindStatusRefresh = bindStatusRefresh;
|
|
1504
1879
|
exports.canEnableSearch = canEnableSearch;
|
|
1880
|
+
exports.canStartStatusRead = canStartStatusRead;
|
|
1505
1881
|
exports.catalogSearchBackends = catalogSearchBackends;
|
|
1882
|
+
exports.configurationAccess = configurationAccess;
|
|
1883
|
+
exports.dormantSearchPlaceholder = dormantSearchPlaceholder;
|
|
1884
|
+
exports.droppedDormantIds = droppedDormantIds;
|
|
1506
1885
|
exports.immediateUpdate = immediateUpdate;
|
|
1507
1886
|
exports.inject = inject;
|
|
1887
|
+
exports.isExternalConfigurationUrl = isExternalConfigurationUrl;
|
|
1888
|
+
exports.isPageHidden = isPageHidden;
|
|
1508
1889
|
exports.isProviderOn = isProviderOn;
|
|
1890
|
+
exports.isRegisteredSearch = isRegisteredSearch;
|
|
1509
1891
|
exports.name = name;
|
|
1510
1892
|
exports.nextSearchEnabled = nextSearchEnabled;
|
|
1893
|
+
exports.ownsProviderConfiguration = ownsProviderConfiguration;
|
|
1511
1894
|
exports.parseLimit = parseLimit;
|
|
1512
1895
|
exports.searchBackends = searchBackends;
|
|
1513
1896
|
exports.selectedSearchBackend = selectedSearchBackend;
|
|
1897
|
+
exports.shouldApplyPolledStatus = shouldApplyPolledStatus;
|
|
1514
1898
|
|
|
1515
1899
|
//# sourceMappingURL=client.inner.cjs.map
|
|
1516
1900
|
return module.exports;
|