@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.
Files changed (2) hide show
  1. package/lib/client.js +37 -6
  2. 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
- return react_jsx_runtime.jsxs("label", { className: "wsc-toggle", children: [
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: "API Keys" }),
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.4",
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",