dsh-search-plugin 0.2.1 → 0.2.2

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 (3) hide show
  1. package/README.md +6 -5
  2. package/lib/client.js +100 -29
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -12,20 +12,21 @@ web_fetch → ctx.web → search-http → 官方 HttpFetchProvider + CIDR resol
12
12
  ```sh
13
13
  pnpm install
14
14
  pnpm check
15
- dsh plugin --profile <name> add ./dsh-search-plugin
15
+ dsh plugin --profile <name> add dsh-search-plugin
16
16
  ```
17
17
 
18
- 本地 checkout 安装需要保留目录及构建产物。更新已经安装的包后需要重启 DSH profile,再刷新网页。仅构建本仓库不会更新已经运行的 GUI。bundle 选择 `searchProvider: codex`、`fetchProvider: search-http`,标准 base 需已有 web 服务与工具消费者;旧 `http` provider 保留,其配置不被修改,但默认路由改由本插件接管。
18
+ 通过 npm 发布版本安装和更新;修复不直接覆盖本机已安装插件。更新已经安装的包后需要重启 DSH profile,再刷新网页。仅构建本仓库不会更新已经运行的 GUI。bundle 选择 `searchProvider: codex`、`fetchProvider: search-http`,标准 base 需已有 web 服务与工具消费者;旧 `http` provider 保留,其配置不被修改,但默认路由改由本插件接管。
19
19
 
20
20
  ## 配置 UI
21
21
 
22
- 进入 **插件 → 联网搜索 → 配置**,或组件行的配置页面。
22
+ 进入 **插件 → 联网搜索 → 配置**。仅保留插件包这一处配置入口,内部组件行不重复提供相同表单。
23
23
 
24
24
  - 搜索协议选择器动态显示对应协议字段;切换协议保留各自配置,保存后下一次操作生效。
25
25
  - Alpha 可复用 OpenAI provider,或使用独立端点与凭据。仅 Alpha 显示超时、响应和答案上限。
26
26
  - DeepSeek 显示 API 基础地址、独立凭据引用和结果数量;使用官方 Search API 实现和传输限制。
27
- - 网页抓取独立分组:CIDR 多行授权、超时与正文限制。可将常见代理网段填入草稿,再保存生效。
28
- - 保存使用官方 ConfigForms 与 revision 冲突检查,支持放弃编辑、重置覆盖、只读与失败提示。
27
+ - 搜索与网页抓取使用独立表单,各自管理草稿、校验、保存、放弃编辑和重置覆盖;操作只修改所属字段。
28
+ - 网页抓取配置包含 CIDR 多行授权、超时与正文限制。按实际网络环境手动填写 CIDR,不提供预设网段按钮。
29
+ - 保存使用官方 ConfigForms 与 revision 冲突检查,支持只读与失败提示。
29
30
  - 密钥走官方 Credentials API 单独保存、删除和刷新状态;不回显、不放入公开配置和浏览器持久存储。删除托管值后,环境变量等其他来源仍可能提供同名凭据。
30
31
 
31
32
  ## 搜索配置
package/lib/client.js CHANGED
@@ -30,14 +30,12 @@ module.exports = __toCommonJS(index_exports);
30
30
  // src/client/SearchConfig.tsx
31
31
  var import_react = require("react");
32
32
  var import_jsx_runtime = require("react/jsx-runtime");
33
- var defaults = { protocol: "codex-alpha", authMode: "openai", openaiProvider: "openai", credentialRef: "DSH_CODEX_API_KEY", endpoint: "", timeoutMs: "60000", maxResponseBytes: "2000000", maxAnswerChars: "16000", deepseekEndpoint: "https://api.deepseek.com/v1/tools", deepseekCredentialRef: "DEEPSEEK_API_KEY", deepseekCount: "10", fetchAllowedCidrs: "", fetchTimeoutMs: "30000", fetchMaxResponseBytes: "5000000", fetchMaxContentChars: "100000" };
33
+ var defaults = { protocol: "codex-alpha", authMode: "openai", openaiProvider: "openai", credentialRef: "DSH_CODEX_API_KEY", endpoint: "", timeoutMs: "60000", maxResponseBytes: "2000000", maxAnswerChars: "16000", deepseekEndpoint: "https://api.deepseek.com/v1/tools", deepseekCredentialRef: "DEEPSEEK_API_KEY", deepseekCount: "10" };
34
34
  var fields = Object.keys(defaults);
35
35
  var alphaLimits = ["timeoutMs", "maxResponseBytes", "maxAnswerChars"];
36
- var fetchLimits = ["fetchTimeoutMs", "fetchMaxResponseBytes", "fetchMaxContentChars"];
37
- var numeric = /* @__PURE__ */ new Set([...alphaLimits, ...fetchLimits, "deepseekCount"]);
38
- var cidrsOf = (text) => text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
36
+ var numeric = /* @__PURE__ */ new Set([...alphaLimits, "deepseekCount"]);
39
37
  function valuesOf(value) {
40
- return Object.fromEntries(fields.map((field) => [field, field === "fetchAllowedCidrs" ? value[field]?.join("\n") ?? "" : String(value[field] ?? defaults[field])]));
38
+ return Object.fromEntries(fields.map((field) => [field, String(value[field] ?? defaults[field])]));
41
39
  }
42
40
  function validEndpoint(value) {
43
41
  try {
@@ -88,7 +86,7 @@ function SearchConfig(props) {
88
86
  return;
89
87
  }
90
88
  if (!reset) {
91
- const activeNumeric = [...fetchLimits, ...deepseek ? ["deepseekCount"] : alphaLimits];
89
+ const activeNumeric = deepseek ? ["deepseekCount"] : alphaLimits;
92
90
  let valid = activeNumeric.every((field) => Number.isSafeInteger(Number(values[field])) && Number(values[field]) > 0);
93
91
  valid &&= deepseek ? Number(values.deepseekCount) <= 50 && validRef && validEndpoint(values.deepseekEndpoint) : independent ? validRef && validEndpoint(values.endpoint) : Boolean(values.openaiProvider.trim());
94
92
  if (!valid) {
@@ -96,7 +94,7 @@ function SearchConfig(props) {
96
94
  return;
97
95
  }
98
96
  }
99
- const ops = reset ? fields.map((field) => ({ op: "unset", path: [field] })) : fields.filter((field) => values[field] !== accepted[field]).map((field) => ({ op: "set", path: [field], value: numeric.has(field) ? Number(values[field]) : field === "fetchAllowedCidrs" ? cidrsOf(values[field]) : values[field] }));
97
+ const ops = reset ? fields.map((field) => ({ op: "unset", path: [field] })) : fields.filter((field) => values[field] !== accepted[field]).map((field) => ({ op: "set", path: [field], value: numeric.has(field) ? Number(values[field]) : values[field] }));
100
98
  setBusy(true);
101
99
  setMessage(null);
102
100
  try {
@@ -130,7 +128,7 @@ function SearchConfig(props) {
130
128
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { name: field, type: numeric.has(field) ? "number" : "text", min: numeric.has(field) ? 1 : void 0, max: field === "deepseekCount" ? 50 : void 0, step: numeric.has(field) ? 1 : void 0, value: values[field], onChange: (event) => edit(field, event.target.value), disabled: field === refField && keyBusy })
131
129
  ] }, field);
132
130
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-search-config", children: [
133
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: (event) => {
131
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { "aria-label": t("searchSettings"), onSubmit: (event) => {
134
132
  event.preventDefault();
135
133
  void save(false);
136
134
  }, children: [
@@ -158,16 +156,6 @@ function SearchConfig(props) {
158
156
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("legend", { children: t("searchLimits") }),
159
157
  alphaLimits.map(input)
160
158
  ] }),
161
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("fieldset", { disabled, children: [
162
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("legend", { children: t("fetchSettings") }),
163
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "dsh-search-wide", children: [
164
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("fetchAllowedCidrs") }),
165
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", { name: "fetchAllowedCidrs", rows: 4, value: values.fetchAllowedCidrs, onChange: (event) => edit("fetchAllowedCidrs", event.target.value), "aria-describedby": "dsh-search-cidr-hint" })
166
- ] }),
167
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { id: "dsh-search-cidr-hint", className: "dsh-search-hint dsh-search-wide", children: t("cidrHint") }),
168
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "dsh-search-wide", onClick: () => edit("fetchAllowedCidrs", [.../* @__PURE__ */ new Set([...cidrsOf(values.fetchAllowedCidrs), "198.18.0.0/15", "192.168.96.0/20"])].join("\n")), children: t("cidrPreset") }),
169
- fetchLimits.map(input)
170
- ] }),
171
159
  !state.writable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", children: t("readOnly") }),
172
160
  conflict && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", children: t("conflict") }),
173
161
  message && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", children: t(message) }),
@@ -216,6 +204,95 @@ function SearchConfig(props) {
216
204
  ] });
217
205
  }
218
206
 
207
+ // src/client/FetchConfig.tsx
208
+ var import_react2 = require("react");
209
+ var import_jsx_runtime2 = require("react/jsx-runtime");
210
+ var defaults2 = { fetchAllowedCidrs: "", fetchTimeoutMs: "30000", fetchMaxResponseBytes: "5000000", fetchMaxContentChars: "100000" };
211
+ var fields2 = Object.keys(defaults2);
212
+ var limits = ["fetchTimeoutMs", "fetchMaxResponseBytes", "fetchMaxContentChars"];
213
+ function valuesOf2(value) {
214
+ return Object.fromEntries(fields2.map((field) => [field, field === "fetchAllowedCidrs" ? value[field]?.join("\n") ?? "" : String(value[field] ?? defaults2[field])]));
215
+ }
216
+ function FetchConfig(props) {
217
+ const { t } = props;
218
+ const state = props.useConfig((snapshot) => snapshot);
219
+ const [draft, setDraft] = (0, import_react2.useState)(null);
220
+ const [busy, setBusy] = (0, import_react2.useState)(false);
221
+ const [message, setMessage] = (0, import_react2.useState)(null);
222
+ if (state.status !== "ready" || !state.value) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "status", children: t(state.status === "loading" ? "loading" : "unavailable") });
223
+ const accepted = valuesOf2(state.value);
224
+ const values = draft?.values ?? accepted;
225
+ const conflict = draft !== null && draft.revision !== state.revision;
226
+ const disabled = !state.writable || busy;
227
+ const edit = (field, value) => {
228
+ setDraft({ values: { ...values, [field]: value }, revision: draft ? draft.revision : state.revision });
229
+ setMessage(null);
230
+ };
231
+ const save = async (reset) => {
232
+ if (conflict) {
233
+ setMessage("conflict");
234
+ return;
235
+ }
236
+ if (!reset && !limits.every((field) => Number.isSafeInteger(Number(values[field])) && Number(values[field]) > 0)) {
237
+ setMessage("fetchInvalid");
238
+ return;
239
+ }
240
+ const ops = reset ? fields2.map((field) => ({ op: "unset", path: [field] })) : fields2.filter((field) => values[field] !== accepted[field]).map((field) => ({ op: "set", path: [field], value: field === "fetchAllowedCidrs" ? values[field].split(/\r?\n/).map((line) => line.trim()).filter(Boolean) : Number(values[field]) }));
241
+ setBusy(true);
242
+ setMessage(null);
243
+ try {
244
+ if (await props.mutateConfig(ops, draft ? draft.revision : state.revision)) {
245
+ setDraft(null);
246
+ setMessage("saved");
247
+ } else setMessage("failed");
248
+ } catch {
249
+ setMessage("failed");
250
+ } finally {
251
+ setBusy(false);
252
+ }
253
+ };
254
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-search-config", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { "aria-label": t("fetchSettings"), onSubmit: (event) => {
255
+ event.preventDefault();
256
+ void save(false);
257
+ }, children: [
258
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("fieldset", { disabled, children: [
259
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("legend", { children: t("fetchSettings") }),
260
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "dsh-search-wide", children: [
261
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: t("fetchAllowedCidrs") }),
262
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("textarea", { name: "fetchAllowedCidrs", rows: 4, value: values.fetchAllowedCidrs, onChange: (event) => edit("fetchAllowedCidrs", event.target.value), "aria-describedby": "dsh-search-cidr-hint" })
263
+ ] }),
264
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { id: "dsh-search-cidr-hint", className: "dsh-search-hint dsh-search-wide", children: t("cidrHint") }),
265
+ limits.map((field) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { children: [
266
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: t(field) }),
267
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { name: field, type: "number", min: 1, step: 1, value: values[field], onChange: (event) => edit(field, event.target.value) })
268
+ ] }, field))
269
+ ] }),
270
+ !state.writable && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "status", children: t("readOnly") }),
271
+ conflict && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "alert", children: t("conflict") }),
272
+ message && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "status", children: t(message) }),
273
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-search-actions", children: [
274
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "submit", disabled: disabled || conflict || !draft, children: t(busy ? "saving" : "save") }),
275
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", disabled: disabled || conflict, onClick: () => {
276
+ void save(true);
277
+ }, children: t("reset") }),
278
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", disabled: busy || !draft, onClick: () => {
279
+ setDraft(null);
280
+ setMessage(null);
281
+ }, children: t("discard") })
282
+ ] })
283
+ ] }) });
284
+ }
285
+
286
+ // src/client/BundleConfig.tsx
287
+ var import_jsx_runtime3 = require("react/jsx-runtime");
288
+ function BundleConfig(props) {
289
+ if (props.view === "summary") return props.t("description");
290
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
291
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SearchConfig, { ...props }),
292
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FetchConfig, { ...props })
293
+ ] });
294
+ }
295
+
219
296
  // src/client/locales.ts
220
297
  var en = {
221
298
  title: "Web search",
@@ -236,8 +313,7 @@ var en = {
236
313
  fetchTimeoutMs: "Fetch timeout (ms)",
237
314
  fetchMaxResponseBytes: "Fetch maximum response bytes",
238
315
  fetchMaxContentChars: "Fetch maximum content characters",
239
- cidrHint: "These ranges explicitly allow web_fetch to reach otherwise blocked network addresses. An empty list adds no exceptions. Enter one IPv4 or IPv6 CIDR per line; other URL, DNS, redirect and transport safety checks still apply. The shortcut only updates this draft; save configuration to apply it.",
240
- cidrPreset: "Add 198.18.0.0/15 and 192.168.96.0/20 to draft",
316
+ cidrHint: "These ranges explicitly allow web_fetch to reach otherwise blocked network addresses. An empty list adds no exceptions. Enter one IPv4 or IPv6 CIDR per line; other URL, DNS, redirect and transport safety checks still apply.",
241
317
  authMode: "Authentication",
242
318
  openai: "Reuse OpenAI provider (default)",
243
319
  credential: "Independent credential",
@@ -271,6 +347,7 @@ var en = {
271
347
  keySaved: "Credential updated. Environment or other source layers may still supply a value after removal.",
272
348
  keyFailed: "Credential operation failed. Check the credential provider and retry.",
273
349
  invalidRef: "Use an environment-style reference name (letters, digits, underscore; not starting with a digit).",
350
+ fetchInvalid: "Use positive integer fetch limits.",
274
351
  invalid: "Use positive integer limits and 1\u201350 DeepSeek results. Reuse requires a provider route; independent authentication and DeepSeek require a valid credential reference and a full HTTP(S) URL without credentials or a fragment."
275
352
  };
276
353
  var zh = {
@@ -292,8 +369,7 @@ var zh = {
292
369
  fetchTimeoutMs: "\u6293\u53D6\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",
293
370
  fetchMaxResponseBytes: "\u6293\u53D6\u54CD\u5E94\u5B57\u8282\u4E0A\u9650",
294
371
  fetchMaxContentChars: "\u6293\u53D6\u5185\u5BB9\u5B57\u7B26\u4E0A\u9650",
295
- cidrHint: "\u8FD9\u4E9B\u7F51\u6BB5\u660E\u786E\u5141\u8BB8 web_fetch \u8BBF\u95EE\u539F\u672C\u53D7\u963B\u7684\u7F51\u7EDC\u5730\u5740\u3002\u7A7A\u5217\u8868\u4E0D\u589E\u52A0\u653E\u884C\u4F8B\u5916\u3002\u6BCF\u884C\u586B\u5199\u4E00\u4E2A IPv4 \u6216 IPv6 CIDR\uFF1B\u5176\u4ED6 URL\u3001DNS\u3001\u91CD\u5B9A\u5411\u548C\u4F20\u8F93\u5B89\u5168\u68C0\u67E5\u4ECD\u7136\u9002\u7528\u3002\u5FEB\u6377\u6309\u94AE\u4EC5\u4FEE\u6539\u8349\u7A3F\uFF0C\u4FDD\u5B58\u914D\u7F6E\u540E\u751F\u6548\u3002",
296
- cidrPreset: "\u5C06 198.18.0.0/15 \u548C 192.168.96.0/20 \u52A0\u5165\u8349\u7A3F",
372
+ cidrHint: "\u8FD9\u4E9B\u7F51\u6BB5\u660E\u786E\u5141\u8BB8 web_fetch \u8BBF\u95EE\u539F\u672C\u53D7\u963B\u7684\u7F51\u7EDC\u5730\u5740\u3002\u7A7A\u5217\u8868\u4E0D\u589E\u52A0\u653E\u884C\u4F8B\u5916\u3002\u6BCF\u884C\u586B\u5199\u4E00\u4E2A IPv4 \u6216 IPv6 CIDR\uFF1B\u5176\u4ED6 URL\u3001DNS\u3001\u91CD\u5B9A\u5411\u548C\u4F20\u8F93\u5B89\u5168\u68C0\u67E5\u4ECD\u7136\u9002\u7528\u3002",
297
373
  authMode: "\u8BA4\u8BC1\u65B9\u5F0F",
298
374
  openai: "\u590D\u7528 OpenAI Provider\uFF08\u9ED8\u8BA4\uFF09",
299
375
  credential: "\u72EC\u7ACB\u51ED\u636E",
@@ -327,6 +403,7 @@ var zh = {
327
403
  keySaved: "\u51ED\u636E\u5DF2\u66F4\u65B0\uFF1B\u79FB\u9664\u540E\u73AF\u5883\u53D8\u91CF\u6216\u5176\u4ED6\u6765\u6E90\u4ECD\u53EF\u80FD\u63D0\u4F9B\u51ED\u636E\u3002",
328
404
  keyFailed: "\u51ED\u636E\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u51ED\u636E\u63D0\u4F9B\u65B9\u540E\u91CD\u8BD5\u3002",
329
405
  invalidRef: "\u8BF7\u8F93\u5165\u73AF\u5883\u53D8\u91CF\u683C\u5F0F\u7684\u5F15\u7528\u540D\uFF08\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\uFF0C\u4E0D\u80FD\u4EE5\u6570\u5B57\u5F00\u5934\uFF09\u3002",
406
+ fetchInvalid: "\u8BF7\u586B\u5199\u6B63\u6574\u6570\u6293\u53D6\u4E0A\u9650\u3002",
330
407
  invalid: "\u8BF7\u586B\u5199\u6B63\u6574\u6570\u4E0A\u9650\uFF0CDeepSeek \u7ED3\u679C\u6570\u91CF\u987B\u4E3A 1\u201350\u3002\u590D\u7528\u6A21\u5F0F\u9700\u8981 Provider \u8DEF\u7531\uFF1B\u72EC\u7ACB\u8BA4\u8BC1\u548C DeepSeek \u9700\u8981\u6709\u6548\u51ED\u636E\u5F15\u7528\u540D\u4E0E\u5B8C\u6574 HTTP(S) \u5730\u5740\uFF0CURL \u4E0D\u5F97\u5305\u542B\u7528\u6237\u540D\u3001\u5BC6\u7801\u6216\u7247\u6BB5\u3002"
331
408
  };
332
409
 
@@ -412,13 +489,7 @@ function apply(ctx) {
412
489
  key: "dsh-search-plugin",
413
490
  locale: "searchPlugin",
414
491
  inject: () => face
415
- }, SearchConfig)));
416
- ctx.effect(() => ctx.slots.inject("plugins.row.config", () => ctx.slots.register({
417
- name: "plugins.row.config",
418
- key: "dsh-search-plugin#dsh-search-plugin",
419
- locale: "searchPlugin",
420
- inject: () => face
421
- }, SearchConfig)));
492
+ }, BundleConfig)));
422
493
  }
423
494
 
424
495
  return module.exports;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-search-plugin",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/urzz/dsh-search-plugin.git"