@240xu/dsh-websearch 2.0.4 → 2.0.6
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/lib/client.js +55 -9
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -25,6 +25,8 @@ window.__ModuleLoader__.load({
|
|
|
25
25
|
{ id: "openai", keyEnv: "OPENAI_API_KEY", label: "OpenAI" },
|
|
26
26
|
];
|
|
27
27
|
const GLOBAL_FIELDS = ["numResults", "concurrency", "backendTimeoutMs"];
|
|
28
|
+
const KEYLESS = BACKENDS.filter((b) => !b.keyEnv);
|
|
29
|
+
const KEYED = BACKENDS.filter((b) => b.keyEnv);
|
|
28
30
|
|
|
29
31
|
/** Where the user goes to get/rotate each key. */
|
|
30
32
|
const KEY_URLS = {
|
|
@@ -39,6 +41,12 @@ window.__ModuleLoader__.load({
|
|
|
39
41
|
|
|
40
42
|
const zh = {
|
|
41
43
|
nav: "搜索",
|
|
44
|
+
groupFree: "免密钥(开箱即用)",
|
|
45
|
+
groupKeyed: "需要 API Key",
|
|
46
|
+
statusEnabled: "已启用后端",
|
|
47
|
+
statusKeys: "已配置密钥",
|
|
48
|
+
keyMissing: "缺密钥",
|
|
49
|
+
unsavedHint: "有未保存的修改", savedOk: "已保存 ✓",
|
|
42
50
|
title: "统一搜索 (Unified Search)",
|
|
43
51
|
description: "聚合网络搜索:11 个后端并发 fan-out、URL 去重合并。开关后端、填 API Key、调参数,保存即生效。",
|
|
44
52
|
save: "保存",
|
|
@@ -56,9 +64,16 @@ window.__ModuleLoader__.load({
|
|
|
56
64
|
keyUnset: "未配置",
|
|
57
65
|
togglesTitle: "启用后端",
|
|
58
66
|
globalsTitle: "全局参数",
|
|
67
|
+
keysTitle: "API 密钥(保存后写入凭证库,立即生效)",
|
|
59
68
|
};
|
|
60
69
|
const en = {
|
|
61
70
|
nav: "Web Search",
|
|
71
|
+
groupFree: "Keyless (ready to use)",
|
|
72
|
+
groupKeyed: "Requires API key",
|
|
73
|
+
statusEnabled: "Backends enabled",
|
|
74
|
+
statusKeys: "Keys configured",
|
|
75
|
+
keyMissing: "key missing",
|
|
76
|
+
unsavedHint: "You have unsaved changes", savedOk: "Saved ✓",
|
|
62
77
|
title: "Unified Search",
|
|
63
78
|
description: "Aggregated web search: 11 backends fan out concurrently, URL-deduped. Toggle backends, set API keys, tune globals — saves apply immediately.",
|
|
64
79
|
save: "Save",
|
|
@@ -76,6 +91,7 @@ window.__ModuleLoader__.load({
|
|
|
76
91
|
keyUnset: "not set",
|
|
77
92
|
togglesTitle: "Enabled backends",
|
|
78
93
|
globalsTitle: "Global parameters",
|
|
94
|
+
keysTitle: "API keys (saved to the credential store, effective immediately)",
|
|
79
95
|
};
|
|
80
96
|
|
|
81
97
|
//#region styles
|
|
@@ -114,6 +130,7 @@ window.__ModuleLoader__.load({
|
|
|
114
130
|
this.keyState = new Map(); // envRef -> {configured}
|
|
115
131
|
this.saving = false;
|
|
116
132
|
this.failed = false;
|
|
133
|
+
this.savedAt = 0;
|
|
117
134
|
this.store = createStore(this.projection());
|
|
118
135
|
this.scope.subscribe(() => this.republish());
|
|
119
136
|
}
|
|
@@ -138,6 +155,7 @@ window.__ModuleLoader__.load({
|
|
|
138
155
|
keyState: Object.fromEntries(this.keyState),
|
|
139
156
|
stagedKeys: Object.fromEntries([...this.stagedKeys].map(([k, v]) => [k, v.text])),
|
|
140
157
|
stagedCount: this.staged.size + this.stagedKeys.size,
|
|
158
|
+
savedAt: this.savedAt,
|
|
141
159
|
stagedText: (field) => {
|
|
142
160
|
const s = this.staged.get(field);
|
|
143
161
|
return s ? (s.clear ? "" : s.text) : undefined;
|
|
@@ -150,13 +168,14 @@ window.__ModuleLoader__.load({
|
|
|
150
168
|
|
|
151
169
|
republish() { this.store.set(this.projection()); }
|
|
152
170
|
|
|
153
|
-
edit(field, text) { this.staged.set(field, { text }); this.republish(); }
|
|
171
|
+
edit(field, text) { this.savedAt = 0; this.staged.set(field, { text }); this.republish(); }
|
|
154
172
|
reset(field) { this.staged.delete(field); this.republish(); }
|
|
155
|
-
editKey(envRef, text) { this.stagedKeys.set(envRef, { text }); this.republish(); }
|
|
173
|
+
editKey(envRef, text) { this.savedAt = 0; this.stagedKeys.set(envRef, { text }); this.republish(); }
|
|
156
174
|
|
|
157
175
|
/** Toggles write immediately (no staging); globals/keys stage + save. */
|
|
158
176
|
async setToggle(id, checked) {
|
|
159
177
|
await this.scope.set("enable" + id.charAt(0).toUpperCase() + id.slice(1), checked);
|
|
178
|
+
|
|
160
179
|
this.republish();
|
|
161
180
|
}
|
|
162
181
|
|
|
@@ -211,6 +230,7 @@ window.__ModuleLoader__.load({
|
|
|
211
230
|
}
|
|
212
231
|
for (const f of doneFields) this.staged.delete(f);
|
|
213
232
|
for (const k of doneKeys) this.stagedKeys.delete(k);
|
|
233
|
+
if (!this.failed && (doneFields.length > 0 || doneKeys.length > 0)) this.savedAt = Date.now();
|
|
214
234
|
} catch { this.failed = true; }
|
|
215
235
|
this.saving = false;
|
|
216
236
|
this.republish();
|
|
@@ -237,13 +257,18 @@ window.__ModuleLoader__.load({
|
|
|
237
257
|
// The Host serves schema-resolved values, so defaults arrive materialized;
|
|
238
258
|
// no client-side default list to drift against the server.
|
|
239
259
|
const checked = props.value === true;
|
|
240
|
-
|
|
260
|
+
// Status dot: green keyless-or-keyed, amber enabled-but-key-missing.
|
|
261
|
+
const needsKey = !!b.keyEnv && !props.keyConfigured;
|
|
262
|
+
const dotColor = needsKey ? (checked ? "#e6a700" : "#888") : "#2ea043";
|
|
263
|
+
return react_jsx_runtime.jsxs("label", { className: "wsc-toggle", style: { display: "flex", alignItems: "center", gap: 8, cursor: "pointer" }, children: [
|
|
241
264
|
react_jsx_runtime.jsx("input", {
|
|
242
265
|
type: "checkbox",
|
|
243
266
|
checked,
|
|
244
267
|
onChange: (e) => props.onChange(e.target.checked),
|
|
245
268
|
}),
|
|
269
|
+
react_jsx_runtime.jsx("span", { style: { width: 8, height: 8, borderRadius: "50%", background: dotColor, display: "inline-block" } }),
|
|
246
270
|
react_jsx_runtime.jsx("span", { children: b.label }),
|
|
271
|
+
needsKey && checked && react_jsx_runtime.jsx("span", { style: { fontSize: 10, opacity: .65 }, children: props.t("keyMissing") }),
|
|
247
272
|
] });
|
|
248
273
|
}
|
|
249
274
|
|
|
@@ -258,13 +283,32 @@ window.__ModuleLoader__.load({
|
|
|
258
283
|
|
|
259
284
|
const toggleField = (id) => "enable" + id.charAt(0).toUpperCase() + id.slice(1);
|
|
260
285
|
|
|
286
|
+
// Status summary: enabled backends + configured keys at a glance.
|
|
287
|
+
const enabledCount = BACKENDS.filter((b) => state.value[toggleField(b.id)] === true).length;
|
|
288
|
+
const keyedBackends = BACKENDS.filter((b) => b.keyEnv);
|
|
289
|
+
const keysReady = keyedBackends.filter((b) => state.keyState[b.keyEnv]?.configured).length;
|
|
290
|
+
|
|
261
291
|
return react_jsx_runtime.jsxs("div", { className: "wsc-card", children: [
|
|
292
|
+
react_jsx_runtime.jsx("div", { style: { display: "flex", gap: 14, alignItems: "center", padding: "10px 12px", marginBottom: 10, borderRadius: 8, background: "rgba(127,127,127,.08)", fontSize: 13 }, children: [
|
|
293
|
+
react_jsx_runtime.jsxs("span", { children: [props.t("statusEnabled"), ": ", react_jsx_runtime.jsx("b", { children: enabledCount + "/" + BACKENDS.length })] }),
|
|
294
|
+
react_jsx_runtime.jsxs("span", { children: [props.t("statusKeys"), ": ", react_jsx_runtime.jsx("b", { children: keysReady + "/" + keyedBackends.length })] }),
|
|
295
|
+
] }),
|
|
262
296
|
react_jsx_runtime.jsx("p", { style: { margin: 0, fontSize: 12, opacity: .65 }, children: props.t("description") }),
|
|
263
|
-
react_jsx_runtime.jsx("
|
|
264
|
-
react_jsx_runtime.jsx("div", { className: "wsc-toggles", children:
|
|
297
|
+
react_jsx_runtime.jsx("h5", { className: "wsc-sectionTitle", children: props.t("groupFree") }),
|
|
298
|
+
react_jsx_runtime.jsx("div", { className: "wsc-toggles", children: KEYLESS.map((b) =>
|
|
299
|
+
react_jsx_runtime.jsx(ToggleRow, {
|
|
300
|
+
backend: b,
|
|
301
|
+
value: state.value[toggleField(b.id)],
|
|
302
|
+
keyConfigured: true,
|
|
303
|
+
onChange: (checked) => card.setToggle(b.id, checked),
|
|
304
|
+
}, b.id)
|
|
305
|
+
) }),
|
|
306
|
+
react_jsx_runtime.jsx("h5", { className: "wsc-sectionTitle", children: props.t("groupKeyed") }),
|
|
307
|
+
react_jsx_runtime.jsx("div", { className: "wsc-toggles", children: KEYED.map((b) =>
|
|
265
308
|
react_jsx_runtime.jsx(ToggleRow, {
|
|
266
309
|
backend: b,
|
|
267
310
|
value: state.value[toggleField(b.id)],
|
|
311
|
+
keyConfigured: !!state.keyState[b.keyEnv]?.configured,
|
|
268
312
|
onChange: (checked) => card.setToggle(b.id, checked),
|
|
269
313
|
}, b.id)
|
|
270
314
|
) }),
|
|
@@ -283,7 +327,7 @@ window.__ModuleLoader__.load({
|
|
|
283
327
|
react_jsx_runtime.jsx("input", { className: "wsc-input", value: state.stagedText("backendTimeoutMs") ?? String(state.value.backendTimeoutMs ?? ""), onChange: e => card.edit("backendTimeoutMs", e.target.value), inputMode: "numeric" }),
|
|
284
328
|
] }),
|
|
285
329
|
] }),
|
|
286
|
-
react_jsx_runtime.jsx("h4", { className: "wsc-sectionTitle", children: "
|
|
330
|
+
react_jsx_runtime.jsx("h4", { className: "wsc-sectionTitle", children: props.t("keysTitle") }),
|
|
287
331
|
react_jsx_runtime.jsx("div", { className: "wsc-grid", children: BACKENDS.filter((b) => b.keyEnv).map((b) =>
|
|
288
332
|
react_jsx_runtime.jsxs("div", { children: [
|
|
289
333
|
react_jsx_runtime.jsxs("div", { className: "wsc-fieldHead", children: [
|
|
@@ -306,10 +350,12 @@ window.__ModuleLoader__.load({
|
|
|
306
350
|
react_jsx_runtime.jsx("p", { className: "wsc-hint", children: props.t("keyHint") }),
|
|
307
351
|
] }, b.id)
|
|
308
352
|
) }),
|
|
353
|
+
state.stagedCount > 0 && react_jsx_runtime.jsx("p", { style: { margin: 0, fontSize: 12, opacity: .6 }, children: props.t("unsavedHint") }),
|
|
309
354
|
react_jsx_runtime.jsxs("div", { className: "wsc-actions", children: [
|
|
310
|
-
react_jsx_runtime.jsx("button", { className: "wsc-btn", disabled: state.saving, onClick: () => card.save(), children: state.saving ? props.t("saving") : props.t("save") }),
|
|
355
|
+
react_jsx_runtime.jsx("button", { className: "wsc-btn", disabled: state.saving || state.stagedCount === 0, onClick: () => card.save(), children: state.saving ? props.t("saving") : props.t("save") }),
|
|
311
356
|
(state.stagedCount > 0) && react_jsx_runtime.jsx("button", { className: "wsc-btn wsc-btnGhost", onClick: () => card.discard(), children: props.t("discard") }),
|
|
312
|
-
state.failed && react_jsx_runtime.jsx("span", { className: "wsc-status", children: props.t("failed") }),
|
|
357
|
+
state.failed && react_jsx_runtime.jsx("span", { className: "wsc-status", style: { color: "#e5534b" }, children: props.t("failed") }),
|
|
358
|
+
!state.failed && !state.saving && state.savedAt > 0 && react_jsx_runtime.jsx("span", { className: "wsc-status", style: { color: "#2ea043" }, children: props.t("savedOk") }),
|
|
313
359
|
] }),
|
|
314
360
|
] });
|
|
315
361
|
}
|
|
@@ -328,7 +374,7 @@ window.__ModuleLoader__.load({
|
|
|
328
374
|
name: "settings.section",
|
|
329
375
|
id: "websearch",
|
|
330
376
|
order: 16,
|
|
331
|
-
label: () =>
|
|
377
|
+
label: () => ctx.locale.bind(NS)("nav"),
|
|
332
378
|
locale: NS,
|
|
333
379
|
inject: () => controller.inject(),
|
|
334
380
|
}, UnifiedSearchSection));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@240xu/dsh-websearch",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
4
4
|
"description": "Unified web search provider for DeepSeek Harness (ctx.web): 11 backends — Exa + Parallel + DDG + SearXNG (keyless), DeepSeek + Anthropic + OpenAI + Brave + Tavily + Serper + Mojeek (key-gated via Settings credentials). Registers provider id \"unified\" for zero-config web_search tool.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|