dsh-web-search-plugin 0.1.1 → 0.2.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/CHANGELOG.md +60 -52
- package/README.md +93 -68
- package/cordis.patch.yml +26 -0
- package/lib/brave.js +157 -0
- package/lib/client.js +248 -93
- package/lib/deepseek.js +148 -0
- package/lib/index.js +104 -223
- package/lib/shared.js +100 -0
- package/lib/tavily.js +132 -0
- package/package.json +16 -4
package/lib/client.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Browser half of `dsh-web-search-plugin`: a
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Browser half of `dsh-web-search-plugin`: a top-level "Web search" settings
|
|
3
|
+
* section (settings → Web search) that edits the `dsh-web-search-plugin`
|
|
4
|
+
* settings namespace. The card switches the DeepSeek / Tavily / Brave backend
|
|
5
|
+
* and writes each engine's credential through the credentials domain
|
|
6
|
+
* (`DEEPSEEK_API_KEY` / `TAVILY_API_KEY` / `BRAVE_API_KEY` by default).
|
|
5
7
|
*
|
|
6
8
|
* This bundle is a self-contained hand-written module in the exact wire format
|
|
7
9
|
* the client module system expects: one `window.__ModuleLoader__.load` call
|
|
@@ -21,28 +23,45 @@ window.__ModuleLoader__.load({
|
|
|
21
23
|
let react = require("react");
|
|
22
24
|
let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
|
|
23
25
|
|
|
24
|
-
/** Settings namespace of
|
|
26
|
+
/** Settings namespace of this plugin. */
|
|
25
27
|
const NS = "dsh-web-search-plugin";
|
|
26
|
-
/** Credential reference
|
|
27
|
-
const
|
|
28
|
-
/**
|
|
29
|
-
const
|
|
28
|
+
/** Credential reference Tavily keyed mode resolves when the section names none. */
|
|
29
|
+
const DEFAULT_TAVILY_KEY_REF = "TAVILY_API_KEY";
|
|
30
|
+
/** Credential reference Brave resolves when the section names none. */
|
|
31
|
+
const DEFAULT_BRAVE_KEY_REF = "BRAVE_API_KEY";
|
|
32
|
+
/** Credential reference DeepSeek resolves when the section names none. */
|
|
33
|
+
const DEFAULT_DEEPSEEK_KEY_REF = "DEEPSEEK_API_KEY";
|
|
34
|
+
/** Form field the Tavily write-only credential control stages under. */
|
|
35
|
+
const TAVILY_API_KEY_FIELD = "apiKey";
|
|
36
|
+
/** Form field the Brave write-only credential control stages under. */
|
|
37
|
+
const BRAVE_API_KEY_FIELD = "braveApiKey";
|
|
38
|
+
/** Form field the DeepSeek write-only credential control stages under. */
|
|
39
|
+
const DEEPSEEK_API_KEY_FIELD = "deepseekApiKey";
|
|
40
|
+
const SECRET_FIELDS = new Set([TAVILY_API_KEY_FIELD, BRAVE_API_KEY_FIELD, DEEPSEEK_API_KEY_FIELD]);
|
|
30
41
|
|
|
31
42
|
/** Locale bundles for the card. */
|
|
32
43
|
const en = {
|
|
33
|
-
nav: "Web search
|
|
34
|
-
description: "
|
|
44
|
+
nav: "Web search",
|
|
45
|
+
description: "Tavily (keyless by default) or Brave Search. Switching engines here is enough — web.searchProvider stays dsh-web-search.",
|
|
46
|
+
provider: "Search engine",
|
|
47
|
+
providerTavily: "Tavily",
|
|
48
|
+
providerBrave: "Brave Search",
|
|
49
|
+
providerDeepseek: "DeepSeek (official)",
|
|
35
50
|
mode: "Auth mode",
|
|
36
51
|
modeKeyless: "Keyless (free, rate-limited)",
|
|
37
52
|
modeKeyed: "API key",
|
|
38
53
|
apiKey: "API key",
|
|
39
54
|
apiKeyHint: "Stored outside the settings file. Leave blank to keep the current key.",
|
|
40
55
|
apiKeySet: "A key is configured.",
|
|
41
|
-
apiKeyUnset: "No key is configured; keyed search is unavailable until one is.",
|
|
56
|
+
apiKeyUnset: "No key is configured; keyed / Brave search is unavailable until one is.",
|
|
42
57
|
apiKeyEnv: "Credential reference",
|
|
43
|
-
apiKeyEnvHint: "Name of the credential/env var resolved for keyed search.",
|
|
58
|
+
apiKeyEnvHint: "Name of the credential/env var resolved for keyed Tavily search.",
|
|
59
|
+
braveApiKeyEnv: "Credential reference",
|
|
60
|
+
braveApiKeyEnvHint: "Name of the credential/env var resolved for Brave search.",
|
|
44
61
|
baseURL: "Endpoint",
|
|
45
62
|
baseURLHint: "Leave blank to use the provider default.",
|
|
63
|
+
braveBaseURL: "Endpoint",
|
|
64
|
+
braveBaseURLHint: "Leave blank to use the Brave Search API default.",
|
|
46
65
|
maxResults: "Max results",
|
|
47
66
|
maxResultsHint: "Sources returned per search (1-20).",
|
|
48
67
|
searchDepth: "Search depth",
|
|
@@ -53,6 +72,30 @@ window.__ModuleLoader__.load({
|
|
|
53
72
|
topic: "Topic",
|
|
54
73
|
topicGeneral: "General",
|
|
55
74
|
topicNews: "News",
|
|
75
|
+
country: "Country",
|
|
76
|
+
countryHint: "ISO 2-letter code (e.g. cn). Leave blank for Brave's default.",
|
|
77
|
+
searchLang: "Search language",
|
|
78
|
+
searchLangHint: "e.g. zh-hans or en. Leave blank for Brave's default.",
|
|
79
|
+
freshness: "Freshness",
|
|
80
|
+
freshnessAny: "Any time",
|
|
81
|
+
freshnessDay: "Past day",
|
|
82
|
+
freshnessWeek: "Past week",
|
|
83
|
+
freshnessMonth: "Past month",
|
|
84
|
+
freshnessYear: "Past year",
|
|
85
|
+
proxy: "Proxy",
|
|
86
|
+
proxyHint: "HTTP(S) proxy URL. Leave blank to use HTTPS_PROXY / HTTP_PROXY.",
|
|
87
|
+
deepseekApiKeyEnv: "Credential reference",
|
|
88
|
+
deepseekApiKeyEnvHint: "Name of the credential/env var resolved for DeepSeek search.",
|
|
89
|
+
deepseekBaseURL: "Endpoint",
|
|
90
|
+
deepseekBaseURLHint: "Leave blank to use the DeepSeek Anthropic-compatible API default.",
|
|
91
|
+
model: "Model",
|
|
92
|
+
modelHint: "Anthropic-format model name for the search request.",
|
|
93
|
+
apiVersion: "API version",
|
|
94
|
+
apiVersionHint: "anthropic-version header value.",
|
|
95
|
+
maxTokens: "Max tokens",
|
|
96
|
+
maxTokensHint: "Upper bound on generated tokens for the search request.",
|
|
97
|
+
maxUses: "Max uses",
|
|
98
|
+
maxUsesHint: "Maximum web_search server-tool uses per request.",
|
|
56
99
|
expand: "Show settings",
|
|
57
100
|
collapse: "Hide settings",
|
|
58
101
|
save: "Save",
|
|
@@ -65,19 +108,27 @@ window.__ModuleLoader__.load({
|
|
|
65
108
|
};
|
|
66
109
|
/** Simplified Chinese copy. */
|
|
67
110
|
const zh = {
|
|
68
|
-
nav: "
|
|
69
|
-
description: "Tavily
|
|
111
|
+
nav: "网页搜索",
|
|
112
|
+
description: "Tavily(默认 keyless)或 Brave Search。在此切换引擎即可,不必改 web.searchProvider。",
|
|
113
|
+
provider: "搜索引擎",
|
|
114
|
+
providerTavily: "Tavily",
|
|
115
|
+
providerBrave: "Brave Search",
|
|
116
|
+
providerDeepseek: "DeepSeek(官方)",
|
|
70
117
|
mode: "认证模式",
|
|
71
118
|
modeKeyless: "Keyless(免费、限流)",
|
|
72
119
|
modeKeyed: "API key",
|
|
73
120
|
apiKey: "API Key",
|
|
74
121
|
apiKeyHint: "不写入设置文件。留空表示保持当前密钥。",
|
|
75
122
|
apiKeySet: "已配置密钥。",
|
|
76
|
-
apiKeyUnset: "未配置密钥;keyed 模式下搜索不可用。",
|
|
123
|
+
apiKeyUnset: "未配置密钥;keyed / Brave 模式下搜索不可用。",
|
|
77
124
|
apiKeyEnv: "凭据引用名",
|
|
78
125
|
apiKeyEnvHint: "keyed 模式解析的凭据/环境变量名。",
|
|
126
|
+
braveApiKeyEnv: "凭据引用名",
|
|
127
|
+
braveApiKeyEnvHint: "Brave 搜索解析的凭据/环境变量名。",
|
|
79
128
|
baseURL: "接口地址",
|
|
80
129
|
baseURLHint: "留空则使用提供方默认地址。",
|
|
130
|
+
braveBaseURL: "接口地址",
|
|
131
|
+
braveBaseURLHint: "留空则使用 Brave Search API 默认地址。",
|
|
81
132
|
maxResults: "结果数量",
|
|
82
133
|
maxResultsHint: "每次搜索返回的源数量(1-20)。",
|
|
83
134
|
searchDepth: "搜索深度",
|
|
@@ -88,6 +139,30 @@ window.__ModuleLoader__.load({
|
|
|
88
139
|
topic: "主题",
|
|
89
140
|
topicGeneral: "通用",
|
|
90
141
|
topicNews: "新闻",
|
|
142
|
+
country: "国家/地区",
|
|
143
|
+
countryHint: "ISO 两位码(如 cn)。留空使用 Brave 默认。",
|
|
144
|
+
searchLang: "搜索语言",
|
|
145
|
+
searchLangHint: "如 zh-hans、en。留空使用 Brave 默认。",
|
|
146
|
+
freshness: "时效",
|
|
147
|
+
freshnessAny: "不限",
|
|
148
|
+
freshnessDay: "过去一天",
|
|
149
|
+
freshnessWeek: "过去一周",
|
|
150
|
+
freshnessMonth: "过去一月",
|
|
151
|
+
freshnessYear: "过去一年",
|
|
152
|
+
proxy: "代理",
|
|
153
|
+
proxyHint: "HTTP(S) 代理 URL。留空则使用 HTTPS_PROXY / HTTP_PROXY。",
|
|
154
|
+
deepseekApiKeyEnv: "凭据引用名",
|
|
155
|
+
deepseekApiKeyEnvHint: "DeepSeek 搜索解析的凭据/环境变量名。",
|
|
156
|
+
deepseekBaseURL: "接口地址",
|
|
157
|
+
deepseekBaseURLHint: "留空则使用 DeepSeek Anthropic 兼容 API 默认地址。",
|
|
158
|
+
model: "模型",
|
|
159
|
+
modelHint: "搜索请求使用的 Anthropic 格式模型名。",
|
|
160
|
+
apiVersion: "API 版本",
|
|
161
|
+
apiVersionHint: "anthropic-version 请求头值。",
|
|
162
|
+
maxTokens: "最大 token",
|
|
163
|
+
maxTokensHint: "搜索请求生成 token 的上限。",
|
|
164
|
+
maxUses: "最大调用次数",
|
|
165
|
+
maxUsesHint: "每次请求 web_search 服务端工具的最大调用次数。",
|
|
91
166
|
expand: "展开设置",
|
|
92
167
|
collapse: "收起设置",
|
|
93
168
|
save: "保存",
|
|
@@ -123,7 +198,7 @@ window.__ModuleLoader__.load({
|
|
|
123
198
|
}
|
|
124
199
|
};
|
|
125
200
|
}
|
|
126
|
-
/** A choice field spec
|
|
201
|
+
/** A choice field spec: staged as its raw string. */
|
|
127
202
|
function choiceField(field) {
|
|
128
203
|
return {
|
|
129
204
|
field,
|
|
@@ -141,16 +216,36 @@ window.__ModuleLoader__.load({
|
|
|
141
216
|
}
|
|
142
217
|
|
|
143
218
|
/**
|
|
144
|
-
* Staged form over the `dsh-web-search-plugin` settings namespace.
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
* domain addressed by the reference the section names.
|
|
219
|
+
* Staged form over the `dsh-web-search-plugin` settings namespace.
|
|
220
|
+
* Write-only credential fields (`apiKey`, `braveApiKey`) have no section
|
|
221
|
+
* spec and are written through the credentials domain.
|
|
148
222
|
*/
|
|
149
|
-
var
|
|
223
|
+
var SearchCardForm = class {
|
|
150
224
|
constructor(scope, api) {
|
|
151
225
|
this.scope = scope;
|
|
152
226
|
this.api = api;
|
|
153
|
-
this.specs = new Map([
|
|
227
|
+
this.specs = new Map([
|
|
228
|
+
choiceField("provider"),
|
|
229
|
+
choiceField("mode"),
|
|
230
|
+
numberField("maxResults"),
|
|
231
|
+
textField("apiKeyEnv"),
|
|
232
|
+
textField("baseURL"),
|
|
233
|
+
choiceField("searchDepth"),
|
|
234
|
+
choiceField("topic"),
|
|
235
|
+
booleanField("includeAnswer"),
|
|
236
|
+
textField("braveApiKeyEnv"),
|
|
237
|
+
textField("braveBaseURL"),
|
|
238
|
+
textField("country"),
|
|
239
|
+
textField("searchLang"),
|
|
240
|
+
choiceField("freshness"),
|
|
241
|
+
textField("proxy"),
|
|
242
|
+
textField("deepseekApiKeyEnv"),
|
|
243
|
+
textField("deepseekBaseURL"),
|
|
244
|
+
textField("model"),
|
|
245
|
+
textField("apiVersion"),
|
|
246
|
+
numberField("maxTokens"),
|
|
247
|
+
numberField("maxUses")
|
|
248
|
+
].map((spec) => [spec.field, spec]));
|
|
154
249
|
this.staged = new Map();
|
|
155
250
|
this.saving = false;
|
|
156
251
|
this.failed = false;
|
|
@@ -182,10 +277,7 @@ window.__ModuleLoader__.load({
|
|
|
182
277
|
}
|
|
183
278
|
field(field) {
|
|
184
279
|
const staged = this.staged.get(field);
|
|
185
|
-
|
|
186
|
-
// report only their staged draft (mirroring the official
|
|
187
|
-
// cards' secret-field branch in CardForm.field).
|
|
188
|
-
if (field === API_KEY_FIELD) return {
|
|
280
|
+
if (SECRET_FIELDS.has(field)) return {
|
|
189
281
|
text: staged?.text ?? "",
|
|
190
282
|
overridden: false,
|
|
191
283
|
invalid: false
|
|
@@ -206,9 +298,9 @@ window.__ModuleLoader__.load({
|
|
|
206
298
|
plan() {
|
|
207
299
|
const plan = [];
|
|
208
300
|
for (const [field, staged] of this.staged) {
|
|
209
|
-
if (field
|
|
301
|
+
if (SECRET_FIELDS.has(field)) {
|
|
210
302
|
const value = staged.text.trim();
|
|
211
|
-
if (value !== "") plan.push({ field, run: () => this.writeKey(value) });
|
|
303
|
+
if (value !== "") plan.push({ field, run: () => this.writeKey(field, value) });
|
|
212
304
|
continue;
|
|
213
305
|
}
|
|
214
306
|
const spec = this.specs.get(field);
|
|
@@ -269,6 +361,7 @@ window.__ModuleLoader__.load({
|
|
|
269
361
|
this.staged.set(field, edit);
|
|
270
362
|
this.failed = false;
|
|
271
363
|
this.publish();
|
|
364
|
+
if (field === "provider" || field === "apiKeyEnv" || field === "braveApiKeyEnv") this.readCredential();
|
|
272
365
|
}
|
|
273
366
|
snapshotOf() {
|
|
274
367
|
return this.scope.getSnapshot();
|
|
@@ -289,12 +382,30 @@ window.__ModuleLoader__.load({
|
|
|
289
382
|
publish() {
|
|
290
383
|
for (const listener of this.listeners) listener();
|
|
291
384
|
}
|
|
292
|
-
/**
|
|
293
|
-
|
|
385
|
+
/** Draft-aware engine: staged `provider` wins over the saved section. */
|
|
386
|
+
providerOf() {
|
|
387
|
+
const staged = this.staged.get("provider");
|
|
388
|
+
if (staged !== void 0 && !staged.clear && staged.text.trim() !== "") return staged.text.trim();
|
|
389
|
+
const declared = this.sectionValue("provider");
|
|
390
|
+
return declared === "brave" || declared === "deepseek-official" ? declared : "tavily";
|
|
391
|
+
}
|
|
392
|
+
tavilyRef() {
|
|
294
393
|
const declared = this.sectionValue("apiKeyEnv");
|
|
295
|
-
return declared !== void 0 && declared.length > 0 ? declared :
|
|
394
|
+
return declared !== void 0 && declared.length > 0 ? declared : DEFAULT_TAVILY_KEY_REF;
|
|
395
|
+
}
|
|
396
|
+
braveRef() {
|
|
397
|
+
const declared = this.sectionValue("braveApiKeyEnv");
|
|
398
|
+
return declared !== void 0 && declared.length > 0 ? declared : DEFAULT_BRAVE_KEY_REF;
|
|
399
|
+
}
|
|
400
|
+
deepseekRef() {
|
|
401
|
+
const declared = this.sectionValue("deepseekApiKeyEnv");
|
|
402
|
+
return declared !== void 0 && declared.length > 0 ? declared : DEFAULT_DEEPSEEK_KEY_REF;
|
|
403
|
+
}
|
|
404
|
+
/** The credential reference the currently visible engine uses. */
|
|
405
|
+
refOf() {
|
|
406
|
+
const provider = this.providerOf();
|
|
407
|
+
return provider === "brave" ? this.braveRef() : provider === "deepseek-official" ? this.deepseekRef() : this.tavilyRef();
|
|
296
408
|
}
|
|
297
|
-
/** Ask the credentials domain about the reference the section currently names. */
|
|
298
409
|
async readCredential() {
|
|
299
410
|
const ref = this.refOf();
|
|
300
411
|
if (ref !== this.credential.ref) {
|
|
@@ -318,27 +429,26 @@ window.__ModuleLoader__.load({
|
|
|
318
429
|
this.credential = next;
|
|
319
430
|
this.publish();
|
|
320
431
|
}
|
|
321
|
-
/** Re-read after the Host reports a change to the reference this card watches. */
|
|
322
432
|
refreshCredential(ref) {
|
|
323
433
|
if (ref !== this.credential.ref) return;
|
|
324
434
|
this.readCredential();
|
|
325
435
|
}
|
|
326
|
-
|
|
327
|
-
|
|
436
|
+
async writeKey(field, value) {
|
|
437
|
+
const ref = field === BRAVE_API_KEY_FIELD ? this.braveRef() : field === DEEPSEEK_API_KEY_FIELD ? this.deepseekRef() : this.tavilyRef();
|
|
328
438
|
try {
|
|
329
|
-
await this.api.credentials.set({ ref
|
|
439
|
+
await this.api.credentials.set({ ref, value });
|
|
330
440
|
} catch (_credentialWriteFailure) {}
|
|
331
441
|
await this.readCredential();
|
|
332
|
-
return this.credential.configured;
|
|
442
|
+
return this.credential.ref === ref ? this.credential.configured : true;
|
|
333
443
|
}
|
|
334
444
|
};
|
|
335
445
|
|
|
336
|
-
/** Bridges the
|
|
337
|
-
var
|
|
446
|
+
/** Bridges the plugin scope and the credentials domain onto the card. */
|
|
447
|
+
var SearchCardController = class {
|
|
338
448
|
constructor(scope, api) {
|
|
339
449
|
this.scope = scope;
|
|
340
450
|
this.api = api;
|
|
341
|
-
this.form = new
|
|
451
|
+
this.form = new SearchCardForm(scope, api);
|
|
342
452
|
this.store = this.form.bind(() => this.projection());
|
|
343
453
|
scope.subscribe(() => {
|
|
344
454
|
this.form.readCredential();
|
|
@@ -347,21 +457,36 @@ window.__ModuleLoader__.load({
|
|
|
347
457
|
projection() {
|
|
348
458
|
return {
|
|
349
459
|
...this.form.shell(),
|
|
460
|
+
provider: this.form.field("provider"),
|
|
350
461
|
mode: this.form.field("mode"),
|
|
351
|
-
apiKey: this.form.field(
|
|
462
|
+
apiKey: this.form.field(TAVILY_API_KEY_FIELD),
|
|
352
463
|
apiKeyEnv: this.form.field("apiKeyEnv"),
|
|
353
464
|
baseURL: this.form.field("baseURL"),
|
|
354
465
|
maxResults: this.form.field("maxResults"),
|
|
355
466
|
searchDepth: this.form.field("searchDepth"),
|
|
356
467
|
includeAnswer: this.form.field("includeAnswer"),
|
|
357
468
|
topic: this.form.field("topic"),
|
|
469
|
+
braveApiKey: this.form.field(BRAVE_API_KEY_FIELD),
|
|
470
|
+
braveApiKeyEnv: this.form.field("braveApiKeyEnv"),
|
|
471
|
+
braveBaseURL: this.form.field("braveBaseURL"),
|
|
472
|
+
country: this.form.field("country"),
|
|
473
|
+
searchLang: this.form.field("searchLang"),
|
|
474
|
+
freshness: this.form.field("freshness"),
|
|
475
|
+
proxy: this.form.field("proxy"),
|
|
476
|
+
deepseekApiKey: this.form.field(DEEPSEEK_API_KEY_FIELD),
|
|
477
|
+
deepseekApiKeyEnv: this.form.field("deepseekApiKeyEnv"),
|
|
478
|
+
deepseekBaseURL: this.form.field("deepseekBaseURL"),
|
|
479
|
+
model: this.form.field("model"),
|
|
480
|
+
apiVersion: this.form.field("apiVersion"),
|
|
481
|
+
maxTokens: this.form.field("maxTokens"),
|
|
482
|
+
maxUses: this.form.field("maxUses"),
|
|
358
483
|
apiKeyConfigured: this.form.credential.configured,
|
|
359
484
|
apiKeyWritable: this.form.credential.writable
|
|
360
485
|
};
|
|
361
486
|
}
|
|
362
487
|
inject() {
|
|
363
488
|
return {
|
|
364
|
-
hooks: {
|
|
489
|
+
hooks: { pluginSearchCard: this.store },
|
|
365
490
|
...this.form.actions()
|
|
366
491
|
};
|
|
367
492
|
}
|
|
@@ -372,75 +497,106 @@ window.__ModuleLoader__.load({
|
|
|
372
497
|
react.createElement("label", { style: { color: "var(--dsw-alias-label-primary)", fontSize: "13px", fontWeight: 500, lineHeight: "1.5" } }, label),
|
|
373
498
|
children,
|
|
374
499
|
hint ? react.createElement("p", { style: { color: "var(--dsw-alias-label-tertiary)", margin: 0, fontSize: "12px", lineHeight: "1.5" } }, hint) : null);
|
|
375
|
-
/** Text/number input. */
|
|
376
500
|
const textInput = (props) => react.createElement("input", { style: { border: "1px solid var(--dsw-alias-border-l2)", background: "var(--dsw-alias-bg-layer-3)", height: "34px", font: "inherit", color: "var(--dsw-alias-label-primary)", borderRadius: "8px", padding: "0 12px", fontSize: "13px", lineHeight: "1.5" }, type: "text", ...props.numeric ? { inputMode: "numeric" } : {}, ...props });
|
|
377
|
-
/** Password input for the write-only credential. */
|
|
378
501
|
const secretInput = (props) => react.createElement("input", { style: { border: "1px solid var(--dsw-alias-border-l2)", background: "var(--dsw-alias-bg-layer-3)", height: "34px", font: "inherit", color: "var(--dsw-alias-label-primary)", borderRadius: "8px", padding: "0 12px", fontSize: "13px", lineHeight: "1.5" }, type: "password", autoComplete: "off", ...props });
|
|
379
|
-
/** Choice select. */
|
|
380
502
|
const selectInput = (props) => react.createElement("select", { style: { border: "1px solid var(--dsw-alias-border-l2)", background: "var(--dsw-alias-bg-layer-3)", height: "34px", font: "inherit", color: "var(--dsw-alias-label-primary)", borderRadius: "8px", padding: "0 12px", fontSize: "13px", lineHeight: "1.5" }, ...props });
|
|
381
|
-
/** Checkbox for booleans. */
|
|
382
503
|
const checkboxInput = (props) => react.createElement("input", { type: "checkbox", style: { width: "16px", height: "16px", accentColor: "var(--dsw-alias-brand-primary)" }, ...props });
|
|
383
504
|
|
|
384
|
-
/** Render
|
|
385
|
-
function
|
|
386
|
-
const { t } = props;
|
|
387
|
-
const state = props.useTavilySearchCard((snapshot) => snapshot);
|
|
388
|
-
const [open, setOpen] = react.useState(false);
|
|
505
|
+
/** Render the provider selector plus the backend-specific staged form. */
|
|
506
|
+
function WebSearchCard(props) {
|
|
507
|
+
const { t, state, actions } = props;
|
|
389
508
|
if (!state.available) return null;
|
|
390
509
|
const disabled = !state.writable;
|
|
391
510
|
const blocked = !state.dirty || state.invalid || state.saving;
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
511
|
+
const provider = state.provider.text === "brave" ? "brave" : state.provider.text === "deepseek-official" ? "deepseek-official" : "tavily";
|
|
512
|
+
const brave = provider === "brave";
|
|
513
|
+
const deepseek = provider === "deepseek-official";
|
|
514
|
+
const keyed = state.mode.text === "keyed";
|
|
515
|
+
return react.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "4px" } },
|
|
516
|
+
!state.writable ? react.createElement("p", { style: { color: "var(--dsw-alias-label-tertiary)", margin: "0", fontSize: "12px", lineHeight: "1.5" } }, t("readOnly")) : null,
|
|
517
|
+
fieldRow(t("provider"), null, selectInput({ id: "ws-provider", value: provider, disabled, onChange: (e) => actions.edit("provider", e.target.value), children: [
|
|
518
|
+
react.createElement("option", { value: "tavily", key: "tavily" }, t("providerTavily")),
|
|
519
|
+
react.createElement("option", { value: "brave", key: "brave" }, t("providerBrave")),
|
|
520
|
+
react.createElement("option", { value: "deepseek-official", key: "deepseek-official" }, t("providerDeepseek"))
|
|
521
|
+
] })),
|
|
522
|
+
deepseek ? null : brave ? null : fieldRow(t("mode"), null, selectInput({ id: "ws-mode", value: state.mode.text, disabled, onChange: (e) => actions.edit("mode", e.target.value), children: [
|
|
523
|
+
react.createElement("option", { value: "keyless", key: "keyless" }, t("modeKeyless")),
|
|
524
|
+
react.createElement("option", { value: "keyed", key: "keyed" }, t("modeKeyed"))
|
|
525
|
+
] })),
|
|
526
|
+
// API key + credential reference for the visible backend.
|
|
527
|
+
deepseek ? fieldRow(t("apiKey"), t("apiKeyHint"), secretInput({ id: "ws-deepseek-key", value: state.deepseekApiKey.text, disabled: !state.apiKeyWritable, placeholder: state.apiKeyConfigured ? t("apiKeySet") : t("apiKeyUnset"), onChange: (e) => actions.edit(DEEPSEEK_API_KEY_FIELD, e.target.value) })) : null,
|
|
528
|
+
deepseek ? fieldRow(t("deepseekApiKeyEnv"), t("deepseekApiKeyEnvHint"), textInput({ id: "ws-deepseek-keyref", value: state.deepseekApiKeyEnv.text, disabled, onChange: (e) => actions.edit("deepseekApiKeyEnv", e.target.value), onReset: () => actions.resetField("deepseekApiKeyEnv") })) : null,
|
|
529
|
+
brave ? fieldRow(t("apiKey"), t("apiKeyHint"), secretInput({ id: "ws-brave-key", value: state.braveApiKey.text, disabled: !state.apiKeyWritable, placeholder: state.apiKeyConfigured ? t("apiKeySet") : t("apiKeyUnset"), onChange: (e) => actions.edit(BRAVE_API_KEY_FIELD, e.target.value) })) : null,
|
|
530
|
+
brave ? fieldRow(t("braveApiKeyEnv"), t("braveApiKeyEnvHint"), textInput({ id: "ws-brave-keyref", value: state.braveApiKeyEnv.text, disabled, onChange: (e) => actions.edit("braveApiKeyEnv", e.target.value), onReset: () => actions.resetField("braveApiKeyEnv") })) : null,
|
|
531
|
+
(!brave && !deepseek && keyed) ? fieldRow(t("apiKey"), t("apiKeyHint"), secretInput({ id: "ws-tavily-key", value: state.apiKey.text, disabled: !state.apiKeyWritable, placeholder: state.apiKeyConfigured ? t("apiKeySet") : t("apiKeyUnset"), onChange: (e) => actions.edit(TAVILY_API_KEY_FIELD, e.target.value) })) : null,
|
|
532
|
+
(!brave && !deepseek && keyed) ? fieldRow(t("apiKeyEnv"), t("apiKeyEnvHint"), textInput({ id: "ws-tavily-keyref", value: state.apiKeyEnv.text, disabled, onChange: (e) => actions.edit("apiKeyEnv", e.target.value), onReset: () => actions.resetField("apiKeyEnv") })) : null,
|
|
533
|
+
// Endpoint.
|
|
534
|
+
deepseek ? fieldRow(t("deepseekBaseURL"), t("deepseekBaseURLHint"), textInput({ id: "ws-deepseek-base", value: state.deepseekBaseURL.text, disabled, onChange: (e) => actions.edit("deepseekBaseURL", e.target.value), onReset: () => actions.resetField("deepseekBaseURL") })) : null,
|
|
535
|
+
brave ? fieldRow(t("braveBaseURL"), t("braveBaseURLHint"), textInput({ id: "ws-brave-base", value: state.braveBaseURL.text, disabled, onChange: (e) => actions.edit("braveBaseURL", e.target.value), onReset: () => actions.resetField("braveBaseURL") })) : null,
|
|
536
|
+
(!brave && !deepseek) ? fieldRow(t("baseURL"), t("baseURLHint"), textInput({ id: "ws-tavily-base", value: state.baseURL.text, disabled, onChange: (e) => actions.edit("baseURL", e.target.value), onReset: () => actions.resetField("baseURL") })) : null,
|
|
537
|
+
// DeepSeek options.
|
|
538
|
+
deepseek ? fieldRow(t("model"), t("modelHint"), textInput({ id: "ws-model", value: state.model.text, disabled, onChange: (e) => actions.edit("model", e.target.value), onReset: () => actions.resetField("model") })) : null,
|
|
539
|
+
deepseek ? fieldRow(t("apiVersion"), t("apiVersionHint"), textInput({ id: "ws-apiversion", value: state.apiVersion.text, disabled, onChange: (e) => actions.edit("apiVersion", e.target.value), onReset: () => actions.resetField("apiVersion") })) : null,
|
|
540
|
+
deepseek ? fieldRow(t("maxTokens"), t("maxTokensHint"), textInput({ id: "ws-maxtokens", numeric: true, value: state.maxTokens.text, disabled, onChange: (e) => actions.edit("maxTokens", e.target.value), onReset: () => actions.resetField("maxTokens") })) : null,
|
|
541
|
+
deepseek ? fieldRow(t("maxUses"), t("maxUsesHint"), textInput({ id: "ws-maxuses", numeric: true, value: state.maxUses.text, disabled, onChange: (e) => actions.edit("maxUses", e.target.value), onReset: () => actions.resetField("maxUses") })) : null,
|
|
542
|
+
// Tavily options.
|
|
543
|
+
(!brave && !deepseek) ? fieldRow(t("searchDepth"), null, selectInput({ id: "ws-depth", value: state.searchDepth.text, disabled, onChange: (e) => actions.edit("searchDepth", e.target.value), children: [
|
|
544
|
+
react.createElement("option", { value: "basic", key: "basic" }, t("searchDepthBasic")),
|
|
545
|
+
react.createElement("option", { value: "advanced", key: "advanced" }, t("searchDepthAdvanced"))
|
|
546
|
+
] })) : null,
|
|
547
|
+
(!brave && !deepseek) ? fieldRow(t("topic"), null, selectInput({ id: "ws-topic", value: state.topic.text, disabled, onChange: (e) => actions.edit("topic", e.target.value), children: [
|
|
548
|
+
react.createElement("option", { value: "general", key: "general" }, t("topicGeneral")),
|
|
549
|
+
react.createElement("option", { value: "news", key: "news" }, t("topicNews"))
|
|
550
|
+
] })) : null,
|
|
551
|
+
(!brave && !deepseek) ? fieldRow(t("includeAnswer"), t("includeAnswerHint"), checkboxInput({ id: "ws-answer", checked: state.includeAnswer.text === "true", disabled, onChange: (e) => actions.edit("includeAnswer", e.target.checked ? "true" : "") })) : null,
|
|
552
|
+
// Brave options.
|
|
553
|
+
brave ? fieldRow(t("country"), t("countryHint"), textInput({ id: "ws-country", value: state.country.text, disabled, onChange: (e) => actions.edit("country", e.target.value), onReset: () => actions.resetField("country") })) : null,
|
|
554
|
+
brave ? fieldRow(t("searchLang"), t("searchLangHint"), textInput({ id: "ws-lang", value: state.searchLang.text, disabled, onChange: (e) => actions.edit("searchLang", e.target.value), onReset: () => actions.resetField("searchLang") })) : null,
|
|
555
|
+
brave ? fieldRow(t("freshness"), null, selectInput({ id: "ws-freshness", value: state.freshness.text, disabled, onChange: (e) => actions.edit("freshness", e.target.value), children: [
|
|
556
|
+
react.createElement("option", { value: "", key: "any" }, t("freshnessAny")),
|
|
557
|
+
react.createElement("option", { value: "pd", key: "pd" }, t("freshnessDay")),
|
|
558
|
+
react.createElement("option", { value: "pw", key: "pw" }, t("freshnessWeek")),
|
|
559
|
+
react.createElement("option", { value: "pm", key: "pm" }, t("freshnessMonth")),
|
|
560
|
+
react.createElement("option", { value: "py", key: "py" }, t("freshnessYear"))
|
|
561
|
+
] })) : null,
|
|
562
|
+
brave ? fieldRow(t("proxy"), t("proxyHint"), textInput({ id: "ws-proxy", value: state.proxy.text, disabled, onChange: (e) => actions.edit("proxy", e.target.value), onReset: () => actions.resetField("proxy") })) : null,
|
|
563
|
+
fieldRow(t("maxResults"), t("maxResultsHint"), textInput({ id: "ws-max", numeric: true, value: state.maxResults.text, disabled, onChange: (e) => actions.edit("maxResults", e.target.value), onReset: () => actions.resetField("maxResults") })),
|
|
564
|
+
react.createElement("div", { style: { borderTop: "1px solid var(--dsw-alias-border-l2)", justifyContent: "flex-end", alignItems: "center", gap: "8px", padding: "12px 0 4px", display: "flex" } },
|
|
565
|
+
state.failed ? react.createElement("p", { role: "status", style: { minWidth: 0, color: "var(--dsw-alias-label-error)", flex: 1, margin: 0, fontSize: "12px", lineHeight: "1.5" } }, t("saveFailed")) : null,
|
|
566
|
+
react.createElement("button", { type: "button", disabled: !state.dirty || state.saving, onClick: actions.discard, style: { appearance: "none", font: "inherit", cursor: "pointer", border: "1px solid var(--dsw-alias-border-l2)", borderRadius: "8px", padding: "5px 14px", fontSize: "13px", lineHeight: "1.5", color: "var(--dsw-alias-label-secondary)", background: "0 0" } }, t("discard")),
|
|
567
|
+
react.createElement("button", { type: "button", disabled: blocked, onClick: actions.save, style: { appearance: "none", font: "inherit", cursor: "pointer", border: "1px solid transparent", borderRadius: "8px", padding: "5px 14px", fontSize: "13px", lineHeight: "1.5", color: "var(--dsw-alias-label-on-brand)", background: "var(--dsw-alias-bg-brand-solid)" } }, t(state.saving ? "saving" : "save"))));
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/** Section component: the "Web search" page body, driven by useSyncExternalStore. */
|
|
571
|
+
function makeSection(ctx, controller) {
|
|
572
|
+
return function WebSearchSection() {
|
|
573
|
+
const state = react.useSyncExternalStore(controller.store.subscribe, controller.store.getSnapshot);
|
|
574
|
+
const t = ctx.locale.bind(NS);
|
|
575
|
+
if (!state.available) return react.createElement("p", { style: { padding: "16px" } }, t("readOnly"));
|
|
576
|
+
return react.createElement("div", { style: { padding: "16px" } },
|
|
577
|
+
react.createElement("div", { style: { border: "1px solid var(--dsw-alias-border-l2)", background: "var(--dsw-alias-bg-layer-3)", borderRadius: "12px", padding: "4px 16px" } },
|
|
578
|
+
react.createElement(WebSearchCard, { t: t, state: state, actions: controller.form.actions() })));
|
|
579
|
+
};
|
|
422
580
|
}
|
|
423
581
|
|
|
424
|
-
/** Mount the
|
|
582
|
+
/** Mount the top-level "Web search" settings section. */
|
|
425
583
|
function apply(ctx) {
|
|
426
584
|
const { api } = ctx.get("connection");
|
|
427
585
|
const t = ctx.locale.bind(NS);
|
|
428
586
|
ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-web-search-plugin: card dictionaries");
|
|
429
|
-
const controller = new
|
|
587
|
+
const controller = new SearchCardController(ctx.settingsScope.bind({ namespace: NS }), api);
|
|
430
588
|
ctx.effect(() => ctx.remote.$on("credentials/updated", (ref) => {
|
|
431
589
|
controller.form.refreshCredential(ref);
|
|
432
590
|
}), "dsh-web-search-plugin: credential invalidations");
|
|
433
|
-
ctx.slots.inject("settings.
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
});
|
|
591
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
592
|
+
name: "settings.section",
|
|
593
|
+
id: "web-search",
|
|
594
|
+
order: 10,
|
|
595
|
+
label: () => t("nav"),
|
|
596
|
+
locale: NS
|
|
597
|
+
}, makeSection(ctx, controller)));
|
|
441
598
|
}
|
|
442
599
|
|
|
443
|
-
/** Cordis service names this browser plugin's apply reads (fiber inject). */
|
|
444
600
|
const inject = ["slots", "locale", "connection", "remote", "settingsScope"];
|
|
445
601
|
|
|
446
602
|
exports.apply = apply;
|
|
@@ -448,4 +604,3 @@ window.__ModuleLoader__.load({
|
|
|
448
604
|
return module.exports;
|
|
449
605
|
}
|
|
450
606
|
});
|
|
451
|
-
//# sourceMappingURL=client.js.map
|