@klarkxy/dsh-web-search-manager 0.1.0
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/LICENSE +41 -0
- package/README.md +73 -0
- package/cordis.patch.yml +13 -0
- package/lib/client.inner.cjs +676 -0
- package/lib/client.inner.cjs.map +1 -0
- package/lib/client.js +846 -0
- package/lib/contracts.d.ts +79 -0
- package/lib/contracts.js +41 -0
- package/lib/contracts.js.map +1 -0
- package/lib/index.d.ts +60 -0
- package/lib/index.js +1070 -0
- package/lib/index.js.map +1 -0
- package/lib/tools.d.ts +9 -0
- package/lib/tools.js +56 -0
- package/lib/tools.js.map +1 -0
- package/package.json +118 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,846 @@
|
|
|
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
|
+
window.__ModuleLoader__.load({
|
|
155
|
+
id: "@klarkxy/dsh-web-search-manager",
|
|
156
|
+
factory: (dshRequire) => {
|
|
157
|
+
var __nodeShims;
|
|
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
|
+
};
|
|
166
|
+
var module = { exports: {} };
|
|
167
|
+
var exports = module.exports;
|
|
168
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
169
|
+
let react = require("react");
|
|
170
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
171
|
+
//#region src/provider-info.ts
|
|
172
|
+
const PROVIDER_PRICING_CHECKED = "2026-09-21";
|
|
173
|
+
const providerInfo = {
|
|
174
|
+
ddg: {
|
|
175
|
+
description: "搜索 DuckDuckGo 公开网页,无需注册或 API Key。",
|
|
176
|
+
pricing: "免费使用,不产生搜索 API 费用。",
|
|
177
|
+
pricingUrl: "https://duckduckgo.com/"
|
|
178
|
+
},
|
|
179
|
+
"zhihu-global": {
|
|
180
|
+
description: "融合知乎问答与全网内容,返回可溯源的搜索结果。",
|
|
181
|
+
pricing: "注册可获 5,000 次/天试用额度;超额价格需向平台咨询。",
|
|
182
|
+
pricingUrl: "https://developer.zhihu.com/"
|
|
183
|
+
},
|
|
184
|
+
bocha: {
|
|
185
|
+
description: "面向中文内容的网页搜索,返回标题、链接和摘要。",
|
|
186
|
+
pricing: "按调用次数计费,目录价 ¥0.036/次;试用额度以官方活动为准。",
|
|
187
|
+
pricingUrl: "https://aq6ky2b8nql.feishu.cn/wiki/JYSbwzdPIiFnz4kDYPXcHSDrnZb"
|
|
188
|
+
},
|
|
189
|
+
brave: {
|
|
190
|
+
description: "使用 Brave 独立网页索引,搜索网页、新闻等内容。",
|
|
191
|
+
pricing: "每月赠 $5(约 1,000 次);超出 $5/千次,激活需绑卡。",
|
|
192
|
+
pricingUrl: "https://brave.com/search/api/"
|
|
193
|
+
},
|
|
194
|
+
"deepseek-official": {
|
|
195
|
+
description: "由 DeepSeek 模型调用内置搜索,复用模型 API Key。",
|
|
196
|
+
pricing: "默认 Flash 按 tokens 计费:每百万输入 ¥1–2(缓存命中 ¥0.02–0.04)、输出 ¥4–8,随峰谷时段变化;赠送余额和搜索工具费用以平台为准。",
|
|
197
|
+
pricingUrl: "https://api-docs.deepseek.com/zh-cn/quick_start/pricing/"
|
|
198
|
+
},
|
|
199
|
+
exa: {
|
|
200
|
+
description: "面向 AI 的网页搜索,提供相关网页与内容片段。",
|
|
201
|
+
pricing: "注册赠 $20,此后每月赠 $10;$7/千次(每次最多 10 条),超过 10 条每多一条加 $1/千次。",
|
|
202
|
+
pricingUrl: "https://exa.ai/pricing"
|
|
203
|
+
},
|
|
204
|
+
firecrawl: {
|
|
205
|
+
description: "搜索网页,并可配合网页内容提取。",
|
|
206
|
+
pricing: "每月免费 1,000 积分,搜索每 10 条结果扣 2 积分;Hobby 含 5,000 积分/月,年付折合 $16/月,额外 $5/千积分。",
|
|
207
|
+
pricingUrl: "https://www.firecrawl.dev/pricing"
|
|
208
|
+
},
|
|
209
|
+
serper: {
|
|
210
|
+
description: "获取 Google 的网页搜索结果。",
|
|
211
|
+
pricing: "注册赠 2,500 次(一次性);充值 $50/5万次($1/千次),付费额度有效期 6 个月。",
|
|
212
|
+
pricingUrl: "https://serper.dev/"
|
|
213
|
+
},
|
|
214
|
+
tavily: {
|
|
215
|
+
description: "面向 AI 的网页搜索,当前使用基础搜索。",
|
|
216
|
+
pricing: "每月免费 1,000 积分,基础搜索 1 积分/次;按量付费 $0.008/积分($8/千次)。",
|
|
217
|
+
pricingUrl: "https://docs.tavily.com/documentation/api-credits"
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
function searchProviderInfo(provider) {
|
|
221
|
+
return (Object.hasOwn(providerInfo, provider.id) ? providerInfo[provider.id] : void 0) ?? {
|
|
222
|
+
description: provider.description,
|
|
223
|
+
pricing: provider.billing === "none" ? "此后端不收取搜索费用。" : provider.billing === "model-and-tools" ? "按模型用量和工具调用计费;免费额度及单价请查看供应商说明。" : "按 API 调用计费;免费额度及单价请查看供应商说明。",
|
|
224
|
+
pricingUrl: provider.signupUrl
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
//#endregion
|
|
228
|
+
//#region src/contracts.ts
|
|
229
|
+
const WEB_SEARCH_RPC_CHANNEL = "/web-search-manager";
|
|
230
|
+
function resolveSearchOrder(knownIds, settings, _keyed) {
|
|
231
|
+
return [...new Set(settings.searchOrder.filter((id) => knownIds.includes(id)))];
|
|
232
|
+
}
|
|
233
|
+
function pickActiveSearch(order, configured) {
|
|
234
|
+
return order.find(configured) ?? "";
|
|
235
|
+
}
|
|
236
|
+
//#endregion
|
|
237
|
+
//#region src/client.tsx
|
|
238
|
+
const name = "dsh-web-search-manager-client";
|
|
239
|
+
const inject = [
|
|
240
|
+
"slots",
|
|
241
|
+
"connection",
|
|
242
|
+
"remote",
|
|
243
|
+
"remote.credentials"
|
|
244
|
+
];
|
|
245
|
+
function unwrap(result) {
|
|
246
|
+
if (!result.ok) throw new Error(result.error.message);
|
|
247
|
+
return result.value;
|
|
248
|
+
}
|
|
249
|
+
function editable(settings) {
|
|
250
|
+
const { revision: _revision, ...rest } = settings;
|
|
251
|
+
return rest;
|
|
252
|
+
}
|
|
253
|
+
function isProviderOn(status, provider) {
|
|
254
|
+
const enabled = provider.kind === "search" ? status.searchActive : status.fetchActive;
|
|
255
|
+
const selected = status.settings[provider.kind === "search" ? "searchProvider" : "fetchProvider"];
|
|
256
|
+
return enabled && selected === provider.id;
|
|
257
|
+
}
|
|
258
|
+
function searchBackends(status) {
|
|
259
|
+
const all = status.providers.filter((provider) => provider.kind === "search");
|
|
260
|
+
return resolveSearchOrder(all.map((provider) => provider.id), status.settings, (id) => Boolean(all.find((provider) => provider.id === id)?.credentialRef)).flatMap((id) => all.find((provider) => provider.id === id) ?? []);
|
|
261
|
+
}
|
|
262
|
+
function catalogSearchBackends(status) {
|
|
263
|
+
const enabled = searchBackends(status);
|
|
264
|
+
const rest = status.providers.filter((provider) => provider.kind === "search" && !enabled.some((row) => row.id === provider.id));
|
|
265
|
+
return [...enabled, ...rest];
|
|
266
|
+
}
|
|
267
|
+
function selectedSearchBackend(status) {
|
|
268
|
+
const backends = searchBackends(status);
|
|
269
|
+
const id = pickActiveSearch(backends.map((provider) => provider.id), (id) => Boolean(backends.find((provider) => provider.id === id)?.configured));
|
|
270
|
+
return backends.find((provider) => provider.id === id);
|
|
271
|
+
}
|
|
272
|
+
function ExternalLink({ url, label, children }) {
|
|
273
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
274
|
+
href: url,
|
|
275
|
+
target: "_blank",
|
|
276
|
+
rel: "noreferrer noopener",
|
|
277
|
+
"aria-label": label,
|
|
278
|
+
onClick: (event) => {
|
|
279
|
+
const bridge = globalThis.dshWindow;
|
|
280
|
+
if (bridge?.openExternal) {
|
|
281
|
+
event.preventDefault();
|
|
282
|
+
bridge.openExternal(url);
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
children
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
function canEnableSearch(order, providers, keys, writable) {
|
|
289
|
+
return order.some((id) => {
|
|
290
|
+
const provider = providers.find((item) => item.kind === "search" && item.id === id);
|
|
291
|
+
if (!provider) return false;
|
|
292
|
+
if (provider.configured) return true;
|
|
293
|
+
const typed = (keys[provider.id] ?? "").trim();
|
|
294
|
+
return Boolean(provider.credentialRef && typed && writable[provider.credentialRef] !== false);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
function nextSearchEnabled(currentlyEnabled, nextOrder, providers, keys, writable) {
|
|
298
|
+
return currentlyEnabled && canEnableSearch(nextOrder, providers, keys, writable);
|
|
299
|
+
}
|
|
300
|
+
function NetworkSearchSettings({ client }) {
|
|
301
|
+
const tabsId = (0, react.useId)();
|
|
302
|
+
const [tab, setTab] = (0, react.useState)("providers");
|
|
303
|
+
const [status, setStatus] = (0, react.useState)();
|
|
304
|
+
const [draft, setDraft] = (0, react.useState)();
|
|
305
|
+
const [keys, setKeys] = (0, react.useState)({});
|
|
306
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
307
|
+
const [dragSource, setDragSource] = (0, react.useState)(null);
|
|
308
|
+
const [dropTarget, setDropTarget] = (0, react.useState)(null);
|
|
309
|
+
const [sortAnnouncement, setSortAnnouncement] = (0, react.useState)("");
|
|
310
|
+
const [note, setNote] = (0, react.useState)("");
|
|
311
|
+
const [error, setError] = (0, react.useState)("");
|
|
312
|
+
const [writable, setWritable] = (0, react.useState)({});
|
|
313
|
+
async function call(endpoint, payload = {}) {
|
|
314
|
+
return unwrap(await client.connection.rpc.call(WEB_SEARCH_RPC_CHANNEL, endpoint, payload));
|
|
315
|
+
}
|
|
316
|
+
async function load() {
|
|
317
|
+
const next = await call("status");
|
|
318
|
+
setStatus(next);
|
|
319
|
+
setDraft(next.settings);
|
|
320
|
+
const refs = next.providers.flatMap((provider) => provider.credentialRef ? [provider.credentialRef] : []);
|
|
321
|
+
if (refs.length) {
|
|
322
|
+
const credentials = unwrap(await client.remote.credentials.describe(refs));
|
|
323
|
+
setWritable(Object.fromEntries(Object.entries(credentials).map(([ref, value]) => [ref, value.writable])));
|
|
324
|
+
}
|
|
325
|
+
return next;
|
|
326
|
+
}
|
|
327
|
+
(0, react.useEffect)(() => {
|
|
328
|
+
load().catch(() => setError("无法读取网络搜索设置,请检查 Host 连接后重试。"));
|
|
329
|
+
}, [client]);
|
|
330
|
+
async function action(run) {
|
|
331
|
+
setBusy(true);
|
|
332
|
+
setNote("");
|
|
333
|
+
setError("");
|
|
334
|
+
try {
|
|
335
|
+
await run();
|
|
336
|
+
} catch (cause) {
|
|
337
|
+
setError(cause instanceof Error ? cause.message : "操作失败。");
|
|
338
|
+
await load().catch(() => {});
|
|
339
|
+
} finally {
|
|
340
|
+
setBusy(false);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
async function update(settings, expectedRevision) {
|
|
344
|
+
const next = await call("update", {
|
|
345
|
+
settings,
|
|
346
|
+
expectedRevision
|
|
347
|
+
});
|
|
348
|
+
setStatus(next);
|
|
349
|
+
setDraft(next.settings);
|
|
350
|
+
return next;
|
|
351
|
+
}
|
|
352
|
+
async function persist(on, order) {
|
|
353
|
+
if (!status || !draft) return;
|
|
354
|
+
for (const provider of status.providers) {
|
|
355
|
+
if (provider.kind !== "search") continue;
|
|
356
|
+
const snapshot = keys[provider.id] ?? "";
|
|
357
|
+
const typed = snapshot.trim();
|
|
358
|
+
if (!provider.credentialRef || !typed) continue;
|
|
359
|
+
if (writable[provider.credentialRef] === false) throw new Error("当前凭据为只读,无法写入。");
|
|
360
|
+
unwrap(await client.remote.credentials.set(provider.credentialRef, typed));
|
|
361
|
+
setKeys((current) => current[provider.id] === snapshot ? {
|
|
362
|
+
...current,
|
|
363
|
+
[provider.id]: ""
|
|
364
|
+
} : current);
|
|
365
|
+
}
|
|
366
|
+
await update({
|
|
367
|
+
...editable(status.settings),
|
|
368
|
+
maxResults: draft.maxResults,
|
|
369
|
+
maxQueries: draft.maxQueries,
|
|
370
|
+
timeoutMs: draft.timeoutMs,
|
|
371
|
+
maxFetchChars: draft.maxFetchChars,
|
|
372
|
+
endpoints: draft.endpoints,
|
|
373
|
+
searchOrder: order,
|
|
374
|
+
searchEnabled: on,
|
|
375
|
+
fetchProvider: "http",
|
|
376
|
+
fetchEnabled: on
|
|
377
|
+
}, status.settings.revision);
|
|
378
|
+
await load();
|
|
379
|
+
}
|
|
380
|
+
async function removeKey(provider) {
|
|
381
|
+
if (!provider.credentialRef || provider.credentialShared || !status) return;
|
|
382
|
+
unwrap(await client.remote.credentials.unset(provider.credentialRef));
|
|
383
|
+
setKeys((current) => ({
|
|
384
|
+
...current,
|
|
385
|
+
[provider.id]: ""
|
|
386
|
+
}));
|
|
387
|
+
await load();
|
|
388
|
+
setNote("已删除 Key。");
|
|
389
|
+
}
|
|
390
|
+
if (!status || !draft) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
391
|
+
className: "web-search-settings",
|
|
392
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
393
|
+
role: "status",
|
|
394
|
+
children: error || "正在读取设置…"
|
|
395
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
396
|
+
type: "button",
|
|
397
|
+
onClick: () => void action(async () => {
|
|
398
|
+
await load();
|
|
399
|
+
}),
|
|
400
|
+
children: "重新连接"
|
|
401
|
+
})]
|
|
402
|
+
});
|
|
403
|
+
const enabled = searchBackends(status);
|
|
404
|
+
const backends = catalogSearchBackends(status);
|
|
405
|
+
const selected = selectedSearchBackend(status);
|
|
406
|
+
const on = status.settings.searchEnabled;
|
|
407
|
+
const order = enabled.map((provider) => provider.id);
|
|
408
|
+
const canEnable = canEnableSearch(order, status.providers, keys, writable);
|
|
409
|
+
const limits = [
|
|
410
|
+
[
|
|
411
|
+
"maxResults",
|
|
412
|
+
"每条查询的结果上限",
|
|
413
|
+
1,
|
|
414
|
+
20
|
|
415
|
+
],
|
|
416
|
+
[
|
|
417
|
+
"maxQueries",
|
|
418
|
+
"每次工具调用的查询上限",
|
|
419
|
+
1,
|
|
420
|
+
5
|
|
421
|
+
],
|
|
422
|
+
[
|
|
423
|
+
"timeoutMs",
|
|
424
|
+
"请求超时(毫秒)",
|
|
425
|
+
1e3,
|
|
426
|
+
12e4
|
|
427
|
+
],
|
|
428
|
+
[
|
|
429
|
+
"maxFetchChars",
|
|
430
|
+
"网页正文字符上限",
|
|
431
|
+
1e3,
|
|
432
|
+
2e5
|
|
433
|
+
]
|
|
434
|
+
];
|
|
435
|
+
function move(id, nextIndex) {
|
|
436
|
+
const index = order.indexOf(id);
|
|
437
|
+
if (busy || index < 0 || nextIndex < 0 || nextIndex >= order.length || index === nextIndex) return;
|
|
438
|
+
const next = [...order];
|
|
439
|
+
const [item] = next.splice(index, 1);
|
|
440
|
+
next.splice(nextIndex, 0, item);
|
|
441
|
+
action(async () => {
|
|
442
|
+
await persist(on, next);
|
|
443
|
+
setSortAnnouncement((backends.find((provider) => provider.id === id)?.label ?? id) + " 已移至第 " + (nextIndex + 1) + " 位");
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
function endDrag() {
|
|
447
|
+
setDragSource(null);
|
|
448
|
+
setDropTarget(null);
|
|
449
|
+
}
|
|
450
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
451
|
+
className: "web-search-settings",
|
|
452
|
+
"data-testid": "web-search-settings",
|
|
453
|
+
children: [
|
|
454
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
455
|
+
className: "web-search-tabs",
|
|
456
|
+
role: "tablist",
|
|
457
|
+
"aria-label": "网络搜索设置",
|
|
458
|
+
onKeyDown: (event) => {
|
|
459
|
+
if (![
|
|
460
|
+
"ArrowLeft",
|
|
461
|
+
"ArrowRight",
|
|
462
|
+
"Home",
|
|
463
|
+
"End"
|
|
464
|
+
].includes(event.key)) return;
|
|
465
|
+
const buttons = [...event.currentTarget.querySelectorAll("[role=\"tab\"]")];
|
|
466
|
+
const index = buttons.indexOf(event.target);
|
|
467
|
+
if (index < 0) return;
|
|
468
|
+
event.preventDefault();
|
|
469
|
+
const next = event.key === "Home" ? 0 : event.key === "End" ? buttons.length - 1 : (index + (event.key === "ArrowRight" ? 1 : -1) + buttons.length) % buttons.length;
|
|
470
|
+
buttons[next]?.focus();
|
|
471
|
+
buttons[next]?.click();
|
|
472
|
+
},
|
|
473
|
+
children: [["providers", "搜索服务"], ["limits", "请求限制"]].map(([key, label]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
474
|
+
type: "button",
|
|
475
|
+
role: "tab",
|
|
476
|
+
id: tabsId + "-" + key + "-tab",
|
|
477
|
+
"aria-controls": tabsId + "-" + key + "-panel",
|
|
478
|
+
"aria-selected": tab === key,
|
|
479
|
+
tabIndex: tab === key ? 0 : -1,
|
|
480
|
+
onClick: () => setTab(key),
|
|
481
|
+
children: label
|
|
482
|
+
}, key))
|
|
483
|
+
}),
|
|
484
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
485
|
+
className: "web-search-sr-only",
|
|
486
|
+
role: "status",
|
|
487
|
+
"aria-live": "polite",
|
|
488
|
+
children: sortAnnouncement
|
|
489
|
+
}),
|
|
490
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
491
|
+
role: "alert",
|
|
492
|
+
className: "web-search-error",
|
|
493
|
+
children: error
|
|
494
|
+
}),
|
|
495
|
+
note && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
496
|
+
role: "status",
|
|
497
|
+
children: note
|
|
498
|
+
}),
|
|
499
|
+
status.storageFailed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
500
|
+
role: "alert",
|
|
501
|
+
children: "配置保存异常,当前运行已暂停网络访问。请重新保存。"
|
|
502
|
+
}),
|
|
503
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
504
|
+
role: "tabpanel",
|
|
505
|
+
id: tabsId + "-providers-panel",
|
|
506
|
+
"aria-labelledby": tabsId + "-providers-tab",
|
|
507
|
+
hidden: tab !== "providers",
|
|
508
|
+
tabIndex: 0,
|
|
509
|
+
children: backends.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
510
|
+
className: "web-search-empty",
|
|
511
|
+
children: "还没有可用的搜索后端。"
|
|
512
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
513
|
+
className: "web-search-card",
|
|
514
|
+
"data-testid": "web-search-tool",
|
|
515
|
+
"data-on": on ? "true" : "false",
|
|
516
|
+
children: [
|
|
517
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "联网搜索" }), !canEnable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
518
|
+
className: "web-search-meta",
|
|
519
|
+
children: "请选择搜索服务并配置 Key。"
|
|
520
|
+
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
521
|
+
type: "button",
|
|
522
|
+
role: "switch",
|
|
523
|
+
className: `web-search-switch${on ? " is-on" : ""}`,
|
|
524
|
+
"aria-checked": on,
|
|
525
|
+
"aria-label": on ? "关闭联网搜索" : "启用联网搜索",
|
|
526
|
+
disabled: busy || !on && !canEnable,
|
|
527
|
+
onClick: () => void action(() => persist(!on, order)),
|
|
528
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
529
|
+
className: "web-search-switch-thumb",
|
|
530
|
+
"aria-hidden": "true"
|
|
531
|
+
})
|
|
532
|
+
})] }),
|
|
533
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
|
|
534
|
+
className: "web-search-rank",
|
|
535
|
+
"aria-label": "搜索后端",
|
|
536
|
+
children: backends.map((provider) => {
|
|
537
|
+
const participating = order.includes(provider.id);
|
|
538
|
+
const rank = order.indexOf(provider.id);
|
|
539
|
+
const active = status.searchActive && selected?.id === provider.id;
|
|
540
|
+
const info = searchProviderInfo(provider);
|
|
541
|
+
const needsKey = Boolean(provider.credentialRef);
|
|
542
|
+
const shared = Boolean(provider.credentialShared);
|
|
543
|
+
const showKey = needsKey && participating && (!shared || !provider.configured);
|
|
544
|
+
const canSort = participating && order.length > 1;
|
|
545
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
546
|
+
className: active ? "is-active" : void 0,
|
|
547
|
+
"data-testid": `web-search-rank-${provider.id}`,
|
|
548
|
+
"data-dragging": dragSource === provider.id || void 0,
|
|
549
|
+
"data-drop": dropTarget === provider.id && dragSource ? order.indexOf(dragSource) < rank ? "after" : "before" : void 0,
|
|
550
|
+
onDragOver: (event) => {
|
|
551
|
+
if (busy || !canSort || !dragSource || dragSource === provider.id) return;
|
|
552
|
+
event.preventDefault();
|
|
553
|
+
event.dataTransfer.dropEffect = "move";
|
|
554
|
+
setDropTarget(provider.id);
|
|
555
|
+
},
|
|
556
|
+
onDragLeave: (event) => {
|
|
557
|
+
if (!event.currentTarget.contains(event.relatedTarget)) setDropTarget(null);
|
|
558
|
+
},
|
|
559
|
+
onDrop: (event) => {
|
|
560
|
+
if (busy || !canSort || !dragSource) return;
|
|
561
|
+
event.preventDefault();
|
|
562
|
+
move(dragSource, rank);
|
|
563
|
+
endDrag();
|
|
564
|
+
},
|
|
565
|
+
children: [
|
|
566
|
+
canSort ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
567
|
+
type: "button",
|
|
568
|
+
className: "web-search-drag-handle",
|
|
569
|
+
draggable: !busy,
|
|
570
|
+
"aria-disabled": busy,
|
|
571
|
+
"aria-label": "拖动排序 " + provider.label,
|
|
572
|
+
title: "拖动排序,或用上下方向键调整",
|
|
573
|
+
onDragStart: (event) => {
|
|
574
|
+
if (busy) {
|
|
575
|
+
event.preventDefault();
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
event.dataTransfer.effectAllowed = "move";
|
|
579
|
+
event.dataTransfer.setData("application/x-dsh-search-provider", provider.id);
|
|
580
|
+
const row = event.currentTarget.closest("li");
|
|
581
|
+
if (row) event.dataTransfer.setDragImage(row, 16, 16);
|
|
582
|
+
setDragSource(provider.id);
|
|
583
|
+
setSortAnnouncement("");
|
|
584
|
+
},
|
|
585
|
+
onDragEnd: endDrag,
|
|
586
|
+
onKeyDown: (event) => {
|
|
587
|
+
if (event.key !== "ArrowUp" && event.key !== "ArrowDown") return;
|
|
588
|
+
event.preventDefault();
|
|
589
|
+
move(provider.id, rank + (event.key === "ArrowUp" ? -1 : 1));
|
|
590
|
+
},
|
|
591
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
592
|
+
width: "16",
|
|
593
|
+
height: "20",
|
|
594
|
+
viewBox: "0 0 16 20",
|
|
595
|
+
fill: "currentColor",
|
|
596
|
+
"aria-hidden": "true",
|
|
597
|
+
children: [
|
|
598
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
599
|
+
cx: "5",
|
|
600
|
+
cy: "5",
|
|
601
|
+
r: "1.5"
|
|
602
|
+
}),
|
|
603
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
604
|
+
cx: "11",
|
|
605
|
+
cy: "5",
|
|
606
|
+
r: "1.5"
|
|
607
|
+
}),
|
|
608
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
609
|
+
cx: "5",
|
|
610
|
+
cy: "10",
|
|
611
|
+
r: "1.5"
|
|
612
|
+
}),
|
|
613
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
614
|
+
cx: "11",
|
|
615
|
+
cy: "10",
|
|
616
|
+
r: "1.5"
|
|
617
|
+
}),
|
|
618
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
619
|
+
cx: "5",
|
|
620
|
+
cy: "15",
|
|
621
|
+
r: "1.5"
|
|
622
|
+
}),
|
|
623
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
624
|
+
cx: "11",
|
|
625
|
+
cy: "15",
|
|
626
|
+
r: "1.5"
|
|
627
|
+
})
|
|
628
|
+
]
|
|
629
|
+
})
|
|
630
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
631
|
+
className: "web-search-rank-index",
|
|
632
|
+
children: participating ? rank + 1 : "—"
|
|
633
|
+
}),
|
|
634
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
635
|
+
className: "web-search-provider",
|
|
636
|
+
children: [
|
|
637
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
638
|
+
className: "web-search-provider-heading",
|
|
639
|
+
children: [
|
|
640
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: provider.label }),
|
|
641
|
+
active && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
642
|
+
className: "web-search-meta",
|
|
643
|
+
children: "当前使用"
|
|
644
|
+
}),
|
|
645
|
+
provider.signupUrl && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExternalLink, {
|
|
646
|
+
url: provider.signupUrl,
|
|
647
|
+
label: provider.label + " 去注册",
|
|
648
|
+
children: "去注册"
|
|
649
|
+
})
|
|
650
|
+
]
|
|
651
|
+
}),
|
|
652
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
653
|
+
className: "web-search-provider-info",
|
|
654
|
+
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)(ExternalLink, {
|
|
655
|
+
url: info.pricingUrl,
|
|
656
|
+
label: provider.label + " 费用说明",
|
|
657
|
+
children: provider.id === "ddg" ? "官网" : "费用说明"
|
|
658
|
+
})] })] })]
|
|
659
|
+
}),
|
|
660
|
+
participating && !provider.configured && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
661
|
+
className: "web-search-meta",
|
|
662
|
+
children: needsKey ? "待配置 Key" : "暂不可用"
|
|
663
|
+
}),
|
|
664
|
+
shared && participating && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: provider.id === "zhihu-global" ? "与「知乎资料」共用 Access Secret" : "与模型设置共用 Key" }),
|
|
665
|
+
showKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: ["API Key", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
666
|
+
type: "password",
|
|
667
|
+
autoComplete: "off",
|
|
668
|
+
spellCheck: false,
|
|
669
|
+
disabled: busy || writable[provider.credentialRef ?? ""] === false,
|
|
670
|
+
value: keys[provider.id] ?? "",
|
|
671
|
+
placeholder: provider.configured ? "已配置,留空不替换" : "请输入 API Key",
|
|
672
|
+
onChange: (event) => setKeys((current) => ({
|
|
673
|
+
...current,
|
|
674
|
+
[provider.id]: event.target.value
|
|
675
|
+
}))
|
|
676
|
+
})] }) : null,
|
|
677
|
+
needsKey && !shared && provider.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
678
|
+
type: "button",
|
|
679
|
+
disabled: busy || writable[provider.credentialRef ?? ""] === false,
|
|
680
|
+
onClick: () => void action(() => removeKey(provider)),
|
|
681
|
+
children: "删除 Key"
|
|
682
|
+
}) : null
|
|
683
|
+
]
|
|
684
|
+
}),
|
|
685
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
686
|
+
className: "web-search-rank-move",
|
|
687
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
688
|
+
type: "button",
|
|
689
|
+
role: "switch",
|
|
690
|
+
className: `web-search-switch${participating ? " is-on" : ""}`,
|
|
691
|
+
"aria-checked": participating,
|
|
692
|
+
"aria-label": `${participating ? "关闭" : "开启"} ${provider.label}`,
|
|
693
|
+
disabled: busy,
|
|
694
|
+
onClick: () => {
|
|
695
|
+
const next = participating ? order.filter((id) => id !== provider.id) : [...order, provider.id];
|
|
696
|
+
action(() => persist(nextSearchEnabled(status.settings.searchEnabled, next, status.providers, keys, writable), next));
|
|
697
|
+
},
|
|
698
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
699
|
+
className: "web-search-switch-thumb",
|
|
700
|
+
"aria-hidden": "true"
|
|
701
|
+
})
|
|
702
|
+
})
|
|
703
|
+
})
|
|
704
|
+
]
|
|
705
|
+
}, provider.id);
|
|
706
|
+
})
|
|
707
|
+
}),
|
|
708
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
709
|
+
className: "web-search-meta",
|
|
710
|
+
children: [
|
|
711
|
+
"价格核对于 ",
|
|
712
|
+
PROVIDER_PRICING_CHECKED,
|
|
713
|
+
",实际额度与费用以供应商账户为准。"
|
|
714
|
+
]
|
|
715
|
+
}),
|
|
716
|
+
on ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
717
|
+
className: "web-search-card-actions",
|
|
718
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
719
|
+
type: "button",
|
|
720
|
+
disabled: busy,
|
|
721
|
+
onClick: () => void action(async () => {
|
|
722
|
+
await persist(status.settings.searchEnabled, order);
|
|
723
|
+
const result = await call("test");
|
|
724
|
+
await load();
|
|
725
|
+
setNote(`连接正常,返回 ${result.sources} 条来源。`);
|
|
726
|
+
}),
|
|
727
|
+
children: "测试连接(可能计费)"
|
|
728
|
+
})
|
|
729
|
+
}) : null
|
|
730
|
+
]
|
|
731
|
+
})
|
|
732
|
+
}),
|
|
733
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
734
|
+
role: "tabpanel",
|
|
735
|
+
id: tabsId + "-limits-panel",
|
|
736
|
+
"aria-labelledby": tabsId + "-limits-tab",
|
|
737
|
+
hidden: tab !== "limits",
|
|
738
|
+
tabIndex: 0,
|
|
739
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("fieldset", {
|
|
740
|
+
className: "web-search-limits",
|
|
741
|
+
disabled: busy,
|
|
742
|
+
"aria-label": "请求限制",
|
|
743
|
+
children: limits.map(([key, label, min, max]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [label, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
744
|
+
type: "number",
|
|
745
|
+
min,
|
|
746
|
+
max,
|
|
747
|
+
step: 1,
|
|
748
|
+
value: draft[key],
|
|
749
|
+
onChange: (event) => setDraft({
|
|
750
|
+
...draft,
|
|
751
|
+
[key]: Number(event.target.value)
|
|
752
|
+
})
|
|
753
|
+
})] }, key))
|
|
754
|
+
})
|
|
755
|
+
}),
|
|
756
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
757
|
+
type: "button",
|
|
758
|
+
disabled: busy,
|
|
759
|
+
onClick: () => void action(async () => {
|
|
760
|
+
await persist(status.settings.searchEnabled, order);
|
|
761
|
+
setNote("已保存。");
|
|
762
|
+
}),
|
|
763
|
+
children: "保存"
|
|
764
|
+
})
|
|
765
|
+
]
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
const styles = `
|
|
769
|
+
.web-search-settings{max-width:760px;display:grid;gap:24px;color:inherit;font:400 var(--font-size-2,14px)/1.5 var(--default-font-family,system-ui,sans-serif)}
|
|
770
|
+
.web-search-settings p{margin:0;line-height:1.5}
|
|
771
|
+
.web-search-settings small,.web-search-meta,.web-search-empty{font-size:var(--font-size-1,13px);color:var(--gray-11,inherit)}
|
|
772
|
+
.web-search-error{color:var(--red-11,#b42318)}
|
|
773
|
+
.web-search-card{display:grid;gap:12px}
|
|
774
|
+
.web-search-card header{display:flex;justify-content:space-between;gap:12px;align-items:center;padding-bottom:8px}
|
|
775
|
+
.web-search-card h3{margin:0;font-size:var(--font-size-3,16px);font-weight:600}
|
|
776
|
+
.web-search-provider{display:grid;gap:8px;min-width:0}
|
|
777
|
+
.web-search-provider-info{display:grid;gap:3px;font-size:var(--font-size-1,13px);color:var(--gray-11,inherit);overflow-wrap:anywhere}
|
|
778
|
+
.web-search-provider-heading{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;min-height:24px}
|
|
779
|
+
.web-search-provider-heading strong{font-weight:500}
|
|
780
|
+
.web-search-card label{display:grid;gap:6px}
|
|
781
|
+
.web-search-settings input{box-sizing:border-box;width:100%;min-width:0;padding:8px 10px;border:1px solid var(--gray-6,color-mix(in srgb,currentColor 22%,transparent));border-radius:6px;background:var(--color-surface,transparent);color:inherit;font:inherit}
|
|
782
|
+
.web-search-settings button:not([role="switch"]){min-height:34px;padding:6px 12px;border:1px solid color-mix(in srgb,currentColor 25%,transparent);border-radius:6px;background:transparent;color:inherit;cursor:pointer;font:inherit;justify-self:start}
|
|
783
|
+
.web-search-settings button:disabled{opacity:.45;cursor:not-allowed}
|
|
784
|
+
.web-search-tabs{display:flex;gap:20px;border-bottom:1px solid var(--gray-6,color-mix(in srgb,currentColor 15%,transparent))}
|
|
785
|
+
.web-search-tabs button[role="tab"]{padding:8px 0;border:0;border-bottom:2px solid transparent;border-radius:0;color:var(--gray-11,inherit)}
|
|
786
|
+
.web-search-tabs button[aria-selected="true"]{border-bottom-color:var(--accent-9,#3b82f6);color:var(--accent-11,inherit);font-weight:600}
|
|
787
|
+
.web-search-settings .web-search-switch{all:unset;box-sizing:border-box;position:relative;display:inline-block;width:36px;height:20px;flex:none;border-radius:999px;background:var(--gray-7,color-mix(in srgb,currentColor 28%,transparent));cursor:pointer}
|
|
788
|
+
.web-search-switch.is-on{background:var(--accent-9,#3b82f6)}
|
|
789
|
+
.web-search-switch:disabled{cursor:not-allowed;opacity:.7}
|
|
790
|
+
.web-search-switch-thumb{position:absolute;top:3px;left:3px;width:14px;height:14px;border-radius:999px;background:#fff;transition:transform 150ms ease}
|
|
791
|
+
.web-search-switch.is-on .web-search-switch-thumb{transform:translateX(16px)}
|
|
792
|
+
.web-search-settings :focus-visible{outline:2px solid currentColor;outline-offset:3px}
|
|
793
|
+
.web-search-card-actions{display:flex;flex-wrap:wrap;gap:8px}
|
|
794
|
+
.web-search-rank{margin:0;padding:0;list-style:none}
|
|
795
|
+
.web-search-rank li{position:relative;display:grid;grid-template-columns:24px minmax(0,1fr) auto;gap:10px;align-items:start;padding:14px 0;border-top:1px solid var(--gray-6,color-mix(in srgb,currentColor 15%,transparent))}
|
|
796
|
+
.web-search-settings button.web-search-drag-handle{display:grid;place-items:center;width:24px;height:24px;min-height:24px;padding:0;border:0;cursor:grab;color:inherit;opacity:.65}
|
|
797
|
+
.web-search-settings button.web-search-drag-handle:hover{opacity:1;background:var(--gray-3,color-mix(in srgb,currentColor 8%,transparent))}
|
|
798
|
+
.web-search-settings button.web-search-drag-handle:active{cursor:grabbing}
|
|
799
|
+
.web-search-settings button.web-search-drag-handle[aria-disabled="true"]{cursor:wait;opacity:.4}
|
|
800
|
+
.web-search-rank li[data-dragging]{opacity:.45}
|
|
801
|
+
.web-search-rank li[data-drop]::after{content:'';position:absolute;left:0;right:0;height:2px;background:var(--accent-9,#3b82f6);pointer-events:none}
|
|
802
|
+
.web-search-rank li[data-drop="before"]::after{top:-1px}
|
|
803
|
+
.web-search-rank li[data-drop="after"]::after{bottom:-1px}
|
|
804
|
+
.web-search-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}
|
|
805
|
+
.web-search-rank-index{opacity:.6;font-variant-numeric:tabular-nums;line-height:24px}
|
|
806
|
+
.web-search-rank-move{display:flex;flex-wrap:wrap;gap:6px;align-items:center;justify-content:flex-end;padding-top:2px}
|
|
807
|
+
.web-search-settings a{color:var(--accent-11,currentColor);font-size:var(--font-size-1,13px);text-underline-offset:3px}
|
|
808
|
+
.web-search-limits{margin:0;border:0;padding:0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px 20px}
|
|
809
|
+
.web-search-limits label{display:grid;gap:6px;font-size:var(--font-size-2,14px)}
|
|
810
|
+
@media(max-width:560px){.web-search-rank-move{flex-direction:column;align-items:flex-end}.web-search-limits{grid-template-columns:minmax(0,1fr)}}
|
|
811
|
+
@media(prefers-reduced-motion:reduce){.web-search-switch-thumb{transition:none}}
|
|
812
|
+
`;
|
|
813
|
+
function apply(ctx) {
|
|
814
|
+
const client = ctx;
|
|
815
|
+
ctx.effect(() => {
|
|
816
|
+
if (typeof document === "undefined") return () => {};
|
|
817
|
+
const style = document.createElement("style");
|
|
818
|
+
style.setAttribute("data-plugin", "@klarkxy/dsh-web-search-manager");
|
|
819
|
+
style.textContent = styles;
|
|
820
|
+
style.dataset.dshWebSearch = "";
|
|
821
|
+
document.head.appendChild(style);
|
|
822
|
+
return () => style.remove();
|
|
823
|
+
}, "web-search-manager.styles");
|
|
824
|
+
ctx.effect(() => client.slots.inject("settings.section", () => client.slots.register({
|
|
825
|
+
name: "settings.section",
|
|
826
|
+
id: "web-search",
|
|
827
|
+
order: 70,
|
|
828
|
+
label: "网络搜索"
|
|
829
|
+
}, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NetworkSearchSettings, { client }))), "web-search-manager.settings");
|
|
830
|
+
}
|
|
831
|
+
//#endregion
|
|
832
|
+
exports.NetworkSearchSettings = NetworkSearchSettings;
|
|
833
|
+
exports.apply = apply;
|
|
834
|
+
exports.canEnableSearch = canEnableSearch;
|
|
835
|
+
exports.catalogSearchBackends = catalogSearchBackends;
|
|
836
|
+
exports.inject = inject;
|
|
837
|
+
exports.isProviderOn = isProviderOn;
|
|
838
|
+
exports.name = name;
|
|
839
|
+
exports.nextSearchEnabled = nextSearchEnabled;
|
|
840
|
+
exports.searchBackends = searchBackends;
|
|
841
|
+
exports.selectedSearchBackend = selectedSearchBackend;
|
|
842
|
+
|
|
843
|
+
//# sourceMappingURL=client.inner.cjs.map
|
|
844
|
+
return module.exports;
|
|
845
|
+
}
|
|
846
|
+
});
|