@240xu/dsh-websearch 2.0.4 → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +37 -6
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -39,6 +39,10 @@ window.__ModuleLoader__.load({
|
|
|
39
39
|
|
|
40
40
|
const zh = {
|
|
41
41
|
nav: "搜索",
|
|
42
|
+
statusEnabled: "已启用后端",
|
|
43
|
+
statusKeys: "已配置密钥",
|
|
44
|
+
keyMissing: "缺密钥",
|
|
45
|
+
unsavedHint: "有未保存的修改", savedOk: "已保存 ✓",
|
|
42
46
|
title: "统一搜索 (Unified Search)",
|
|
43
47
|
description: "聚合网络搜索:11 个后端并发 fan-out、URL 去重合并。开关后端、填 API Key、调参数,保存即生效。",
|
|
44
48
|
save: "保存",
|
|
@@ -56,9 +60,14 @@ window.__ModuleLoader__.load({
|
|
|
56
60
|
keyUnset: "未配置",
|
|
57
61
|
togglesTitle: "启用后端",
|
|
58
62
|
globalsTitle: "全局参数",
|
|
63
|
+
keysTitle: "API 密钥(保存后写入凭证库,立即生效)",
|
|
59
64
|
};
|
|
60
65
|
const en = {
|
|
61
66
|
nav: "Web Search",
|
|
67
|
+
statusEnabled: "Backends enabled",
|
|
68
|
+
statusKeys: "Keys configured",
|
|
69
|
+
keyMissing: "key missing",
|
|
70
|
+
unsavedHint: "You have unsaved changes", savedOk: "Saved ✓",
|
|
62
71
|
title: "Unified Search",
|
|
63
72
|
description: "Aggregated web search: 11 backends fan out concurrently, URL-deduped. Toggle backends, set API keys, tune globals — saves apply immediately.",
|
|
64
73
|
save: "Save",
|
|
@@ -76,6 +85,7 @@ window.__ModuleLoader__.load({
|
|
|
76
85
|
keyUnset: "not set",
|
|
77
86
|
togglesTitle: "Enabled backends",
|
|
78
87
|
globalsTitle: "Global parameters",
|
|
88
|
+
keysTitle: "API keys (saved to the credential store, effective immediately)",
|
|
79
89
|
};
|
|
80
90
|
|
|
81
91
|
//#region styles
|
|
@@ -114,6 +124,7 @@ window.__ModuleLoader__.load({
|
|
|
114
124
|
this.keyState = new Map(); // envRef -> {configured}
|
|
115
125
|
this.saving = false;
|
|
116
126
|
this.failed = false;
|
|
127
|
+
this.savedAt = 0;
|
|
117
128
|
this.store = createStore(this.projection());
|
|
118
129
|
this.scope.subscribe(() => this.republish());
|
|
119
130
|
}
|
|
@@ -138,6 +149,7 @@ window.__ModuleLoader__.load({
|
|
|
138
149
|
keyState: Object.fromEntries(this.keyState),
|
|
139
150
|
stagedKeys: Object.fromEntries([...this.stagedKeys].map(([k, v]) => [k, v.text])),
|
|
140
151
|
stagedCount: this.staged.size + this.stagedKeys.size,
|
|
152
|
+
savedAt: this.savedAt,
|
|
141
153
|
stagedText: (field) => {
|
|
142
154
|
const s = this.staged.get(field);
|
|
143
155
|
return s ? (s.clear ? "" : s.text) : undefined;
|
|
@@ -150,13 +162,14 @@ window.__ModuleLoader__.load({
|
|
|
150
162
|
|
|
151
163
|
republish() { this.store.set(this.projection()); }
|
|
152
164
|
|
|
153
|
-
edit(field, text) { this.staged.set(field, { text }); this.republish(); }
|
|
165
|
+
edit(field, text) { this.savedAt = 0; this.staged.set(field, { text }); this.republish(); }
|
|
154
166
|
reset(field) { this.staged.delete(field); this.republish(); }
|
|
155
|
-
editKey(envRef, text) { this.stagedKeys.set(envRef, { text }); this.republish(); }
|
|
167
|
+
editKey(envRef, text) { this.savedAt = 0; this.stagedKeys.set(envRef, { text }); this.republish(); }
|
|
156
168
|
|
|
157
169
|
/** Toggles write immediately (no staging); globals/keys stage + save. */
|
|
158
170
|
async setToggle(id, checked) {
|
|
159
171
|
await this.scope.set("enable" + id.charAt(0).toUpperCase() + id.slice(1), checked);
|
|
172
|
+
|
|
160
173
|
this.republish();
|
|
161
174
|
}
|
|
162
175
|
|
|
@@ -211,6 +224,7 @@ window.__ModuleLoader__.load({
|
|
|
211
224
|
}
|
|
212
225
|
for (const f of doneFields) this.staged.delete(f);
|
|
213
226
|
for (const k of doneKeys) this.stagedKeys.delete(k);
|
|
227
|
+
if (!this.failed && (doneFields.length > 0 || doneKeys.length > 0)) this.savedAt = Date.now();
|
|
214
228
|
} catch { this.failed = true; }
|
|
215
229
|
this.saving = false;
|
|
216
230
|
this.republish();
|
|
@@ -237,13 +251,18 @@ window.__ModuleLoader__.load({
|
|
|
237
251
|
// The Host serves schema-resolved values, so defaults arrive materialized;
|
|
238
252
|
// no client-side default list to drift against the server.
|
|
239
253
|
const checked = props.value === true;
|
|
240
|
-
|
|
254
|
+
// Status dot: green keyless-or-keyed, amber enabled-but-key-missing.
|
|
255
|
+
const needsKey = !!b.keyEnv && !props.keyConfigured;
|
|
256
|
+
const dotColor = needsKey ? (checked ? "#e6a700" : "#888") : "#2ea043";
|
|
257
|
+
return react_jsx_runtime.jsxs("label", { className: "wsc-toggle", style: { display: "flex", alignItems: "center", gap: 8, cursor: "pointer" }, children: [
|
|
241
258
|
react_jsx_runtime.jsx("input", {
|
|
242
259
|
type: "checkbox",
|
|
243
260
|
checked,
|
|
244
261
|
onChange: (e) => props.onChange(e.target.checked),
|
|
245
262
|
}),
|
|
263
|
+
react_jsx_runtime.jsx("span", { style: { width: 8, height: 8, borderRadius: "50%", background: dotColor, display: "inline-block" } }),
|
|
246
264
|
react_jsx_runtime.jsx("span", { children: b.label }),
|
|
265
|
+
needsKey && checked && react_jsx_runtime.jsx("span", { style: { fontSize: 10, opacity: .65 }, children: props.t("keyMissing") }),
|
|
247
266
|
] });
|
|
248
267
|
}
|
|
249
268
|
|
|
@@ -258,13 +277,23 @@ window.__ModuleLoader__.load({
|
|
|
258
277
|
|
|
259
278
|
const toggleField = (id) => "enable" + id.charAt(0).toUpperCase() + id.slice(1);
|
|
260
279
|
|
|
280
|
+
// Status summary: enabled backends + configured keys at a glance.
|
|
281
|
+
const enabledCount = BACKENDS.filter((b) => state.value[toggleField(b.id)] === true).length;
|
|
282
|
+
const keyedBackends = BACKENDS.filter((b) => b.keyEnv);
|
|
283
|
+
const keysReady = keyedBackends.filter((b) => state.keyState[b.keyEnv]?.configured).length;
|
|
284
|
+
|
|
261
285
|
return react_jsx_runtime.jsxs("div", { className: "wsc-card", children: [
|
|
286
|
+
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: [
|
|
287
|
+
react_jsx_runtime.jsxs("span", { children: [props.t("statusEnabled"), ": ", react_jsx_runtime.jsx("b", { children: enabledCount + "/" + BACKENDS.length })] }),
|
|
288
|
+
react_jsx_runtime.jsxs("span", { children: [props.t("statusKeys"), ": ", react_jsx_runtime.jsx("b", { children: keysReady + "/" + keyedBackends.length })] }),
|
|
289
|
+
] }),
|
|
262
290
|
react_jsx_runtime.jsx("p", { style: { margin: 0, fontSize: 12, opacity: .65 }, children: props.t("description") }),
|
|
263
291
|
react_jsx_runtime.jsx("h4", { className: "wsc-sectionTitle", children: props.t("togglesTitle") }),
|
|
264
292
|
react_jsx_runtime.jsx("div", { className: "wsc-toggles", children: BACKENDS.map((b) =>
|
|
265
293
|
react_jsx_runtime.jsx(ToggleRow, {
|
|
266
294
|
backend: b,
|
|
267
295
|
value: state.value[toggleField(b.id)],
|
|
296
|
+
keyConfigured: !b.keyEnv || !!state.keyState[b.keyEnv]?.configured,
|
|
268
297
|
onChange: (checked) => card.setToggle(b.id, checked),
|
|
269
298
|
}, b.id)
|
|
270
299
|
) }),
|
|
@@ -283,7 +312,7 @@ window.__ModuleLoader__.load({
|
|
|
283
312
|
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
313
|
] }),
|
|
285
314
|
] }),
|
|
286
|
-
react_jsx_runtime.jsx("h4", { className: "wsc-sectionTitle", children: "
|
|
315
|
+
react_jsx_runtime.jsx("h4", { className: "wsc-sectionTitle", children: props.t("keysTitle") }),
|
|
287
316
|
react_jsx_runtime.jsx("div", { className: "wsc-grid", children: BACKENDS.filter((b) => b.keyEnv).map((b) =>
|
|
288
317
|
react_jsx_runtime.jsxs("div", { children: [
|
|
289
318
|
react_jsx_runtime.jsxs("div", { className: "wsc-fieldHead", children: [
|
|
@@ -306,10 +335,12 @@ window.__ModuleLoader__.load({
|
|
|
306
335
|
react_jsx_runtime.jsx("p", { className: "wsc-hint", children: props.t("keyHint") }),
|
|
307
336
|
] }, b.id)
|
|
308
337
|
) }),
|
|
338
|
+
state.stagedCount > 0 && react_jsx_runtime.jsx("p", { style: { margin: 0, fontSize: 12, opacity: .6 }, children: props.t("unsavedHint") }),
|
|
309
339
|
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") }),
|
|
340
|
+
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
341
|
(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") }),
|
|
342
|
+
state.failed && react_jsx_runtime.jsx("span", { className: "wsc-status", style: { color: "#e5534b" }, children: props.t("failed") }),
|
|
343
|
+
!state.failed && !state.saving && state.savedAt > 0 && react_jsx_runtime.jsx("span", { className: "wsc-status", style: { color: "#2ea043" }, children: props.t("savedOk") }),
|
|
313
344
|
] }),
|
|
314
345
|
] });
|
|
315
346
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@240xu/dsh-websearch",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.5",
|
|
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",
|