@lynn123411/dsh-chat-translate 1.8.0 → 1.9.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/README.md CHANGED
@@ -14,7 +14,7 @@ DeepSeek Harness Web 界面的工具调用与思考链智能翻译插件。自
14
14
  - **智能内容脱敏与占位符保护**:翻译前对多行代码块、内联代码、URL 链接、文件路径及 CLI 命令行参数进行占位符脱敏与鲁棒还原,杜绝代码与路径被误翻译。占位符为 `⟦<随机短码><序号>⟧` 形式的独立片段,构建时会在紧邻拉丁字母的一侧补一个空格,避免译回时与相邻单词粘连。译文只有在每个占位符都按原顺序被完整还原时才被采用:占位符被改写、遗失或多个重复时整条译文作废并换通道,退回原文,且绝不写入缓存;升级前旧格式写入的污染译文在读取时被驱逐。
15
15
  - **当前会话作用域**:仅翻译当前查看的会话,切换会话自动跟随新内容;视口懒加载(150px 缓冲)与文档顺序排队,译文按阅读顺序出现。
16
16
  - **智能调度与熔断保护**:工具标题走 1–100 动态并发限流队列、AI 30s / Bing 2s 超时、连续失败熔断自愈、在途请求合并去重、7 天 LRU 磁盘持久化缓存(工具标题 `~/.dsh/dsh-chat-translate/cache.json`,思考链 `~/.dsh/dsh-chat-translate/think-cache.json`)。
17
- - **DSH 原生配置接入**:配置走 DSH `settings` 服务(用户层写入 `~/.dsh/settings.yaml` 的 `dsh-chat-translate` 段)、密钥走 DSH `credentials` 服务、设置面板经 `settingsScope` 与 `credentials` Remote API 读写——无任何自研配置文件。
17
+ - **DSH 原生配置接入**:配置就是本插件在 profile 里的插件配置——设置面板经 DSH 的共享配置表单(`ctx.configForms`)读写本插件配置条目 `dsh-chat-translate` 的 `config`,用户层落在当前 profile 的 patch;密钥走 DSH `credentials` 服务(`~/.dsh/.credentials.yaml` 的 `TRANSLATE_API_KEY`)。无任何自研配置文件。
18
18
  - **设置面板集成**:设置面板挂在「设置 - 阅读体验」页(本插件贡献其中一张卡片),页内提供总开关、AI/Bing 通道开关、「显示思考链翻译按钮」(默认打开)与思考链超时、Base URL 与模型配置、通道测试与并发数调节。按钮的显示还要求 AI 通道打开且已配置(Key / Base URL / 模型齐全),缺任何一条都不注入按钮并还原已翻译内容。
19
19
 
20
20
  ## 安装
package/cordis.patch.yml CHANGED
@@ -1,6 +1,7 @@
1
1
  # @lynn123411/dsh-chat-translate bundle patch
2
2
  # 声明在 package.json 的 dsh.bundle.patch,`dsh plugin add` 后自动并入 profile 层栈。
3
- # 浏览器半区(lib/client.js)由 client-modules 依据 dsh.client 声明自动发现。
3
+ # 这条 insert 行是 root loader entry(不是 preset 子树),client-modules 正是对这类行
4
+ # 依据 dsh.client 声明服务浏览器半区(lib/client.js),所以插件本体单行即可。
4
5
  - insert:
5
6
  - id: dsh-chat-translate
6
7
  name: '@lynn123411/dsh-chat-translate'
@@ -11,22 +11,24 @@
11
11
 
12
12
  > ⚠️ **注意**:DSH 的凭据加载器要求 refs 键值非空。手动编辑时要么填写真实 Key,要么**直接删除该行**——不要写成 `TRANSLATE_API_KEY: ""` 这类空值,否则 DSH 启动会失败。未配置时插件自动由 Bing 通道兜底,不会报错。
13
13
 
14
- 2. **Base URL 与模型等设置**:在「设置 - 阅读体验」中填写 Base URL(如 `https://api.openai.com/v1` 或 `https://api.deepseek.com/v1`)与模型名(如 `gpt-4o-mini`、`deepseek-chat`),点击「测试 AI 通道」验证。所有设置(开关、并发数、超时等)由 DSH 持久化到 `~/.dsh/settings.yaml` 的 `dsh-chat-translate` 段,可直接编辑该文件(DSH 会热同步):
14
+ 2. **Base URL 与模型等设置**:在「设置 - 阅读体验」中填写 Base URL(如 `https://api.openai.com/v1` 或 `https://api.deepseek.com/v1`)与模型名(如 `gpt-4o-mini`、`deepseek-chat`),点击「测试 AI 通道」验证。所有设置(开关、并发数、超时等)都是本插件在 profile 里的插件配置:写入当前 profile 的 patch(`~/.dsh/profiles/<profile>/cordis.patch.yml`)中 `dsh-chat-translate` 条目的 `config`,可直接编辑该文件——字段都是可实时更新的,改完立即生效,不需重载:
15
15
 
16
16
  ```yaml
17
- dsh-chat-translate:
18
- enabled: true
19
- concurrency: 3
20
- aiEnabled: true
21
- bingEnabled: true
22
- thinkEnabled: true
23
- thinkTimeoutMs: 600000
24
- baseUrl: https://api.deepseek.com/v1
25
- model: deepseek-chat
26
- targetLang: zh-Hans
17
+ - id: dsh-chat-translate
18
+ name: '@lynn123411/dsh-chat-translate'
19
+ config:
20
+ enabled: true
21
+ concurrency: 3
22
+ aiEnabled: true
23
+ bingEnabled: true
24
+ thinkEnabled: true
25
+ thinkTimeoutMs: 600000
26
+ baseUrl: https://api.deepseek.com/v1
27
+ model: deepseek-chat
28
+ targetLang: zh-Hans
27
29
  ```
28
30
 
29
- > 从 1.2 起,插件不再读写独立的 `~/.dsh/dsh-chat-translate-config.json`:升级后首次加载会自动把旧文件的值迁移进上面的 settings 段并删除旧文件(若你已在 UI 中改过设置,则以你的设置为准)。
31
+ > 从 1.2 起,插件不再读写独立的 `~/.dsh/dsh-chat-translate-config.json`:升级后首次加载会自动把旧文件的值迁移进这个配置条目并删除旧文件(若你已在 UI 中改过设置,则以你的设置为准)。
30
32
 
31
33
  3. **思考链翻译按钮**:AI 卡片里的「显示思考链翻译按钮」默认打开;按钮的显示还要求总开关打开、AI 通道打开且已配置(Key / Base URL / 模型齐全),缺任何一条都不注入按钮并还原已翻译内容。按钮只在展开的思考卡上出现,位于「Think」右侧;折叠的卡片没有按钮,展开后才插入。点击才翻译这条思考正文,不会自动翻译。
32
34
 
package/lib/client.js CHANGED
@@ -1452,9 +1452,9 @@ var DEFAULT_STATE = {
1452
1452
  var SettingsStore = class {
1453
1453
  state = { ...DEFAULT_STATE };
1454
1454
  listeners = /* @__PURE__ */ new Set();
1455
- scope = null;
1455
+ form = null;
1456
1456
  credentials = null;
1457
- unsubscribeScope = null;
1457
+ unsubscribeForm = null;
1458
1458
  keyConfigured = false;
1459
1459
  writeTimer = null;
1460
1460
  pendingFields = /* @__PURE__ */ new Set();
@@ -1462,15 +1462,15 @@ var SettingsStore = class {
1462
1462
  * Bind the DSH services. Called once from the settings UI setup; re-binding
1463
1463
  * (e.g. after a reconnect) detaches the previous subscription first.
1464
1464
  */
1465
- attach(scope, credentials) {
1466
- if (this.unsubscribeScope) {
1467
- this.unsubscribeScope();
1468
- this.unsubscribeScope = null;
1465
+ attach(form, credentials) {
1466
+ if (this.unsubscribeForm) {
1467
+ this.unsubscribeForm();
1468
+ this.unsubscribeForm = null;
1469
1469
  }
1470
- this.scope = scope;
1470
+ this.form = form;
1471
1471
  this.credentials = credentials;
1472
- if (scope) {
1473
- this.unsubscribeScope = scope.subscribe(() => this.derive());
1472
+ if (form) {
1473
+ this.unsubscribeForm = form.subscribe(() => this.derive());
1474
1474
  this.derive();
1475
1475
  }
1476
1476
  void this.refreshKeyStatus();
@@ -1484,10 +1484,10 @@ var SettingsStore = class {
1484
1484
  this.listeners.delete(listener);
1485
1485
  };
1486
1486
  }
1487
- /** Map the resolved namespace value into client state and notify. */
1487
+ /** Map the resolved section into client state and notify. */
1488
1488
  derive() {
1489
- if (!this.scope) return;
1490
- const snap = this.scope.getSnapshot();
1489
+ if (!this.form) return;
1490
+ const snap = this.form.getSnapshot();
1491
1491
  const value = snap.value;
1492
1492
  if (!value || typeof value !== "object") return;
1493
1493
  const next = { ...this.state };
@@ -1552,11 +1552,11 @@ var SettingsStore = class {
1552
1552
  }
1553
1553
  /**
1554
1554
  * Optimistically apply locally, then persist each touched field through the
1555
- * settings scope. Writes are trailing-debounced (300ms) so typing in the
1555
+ * configuration form. Writes are trailing-debounced (300ms) so typing in the
1556
1556
  * baseUrl/model inputs collapses into a single queued mutation instead of
1557
1557
  * one write per keystroke — which would otherwise flash stale mirror values
1558
- * back into the inputs between commits. A failed write makes the scope
1559
- * reload its mirror, which re-derives this store from the host document
1558
+ * back into the inputs between commits. A failed write makes the form reload
1559
+ * its mirror, which re-derives this store from the Host document
1560
1560
  * (conflict-safe recovery).
1561
1561
  */
1562
1562
  async update(partial) {
@@ -1578,7 +1578,7 @@ var SettingsStore = class {
1578
1578
  thinkTimeoutMs: sanitizedThinkTimeout
1579
1579
  };
1580
1580
  this.applyState(next);
1581
- if (this.scope) {
1581
+ if (this.form) {
1582
1582
  const fields = [
1583
1583
  "enabled",
1584
1584
  "concurrency",
@@ -1605,7 +1605,7 @@ var SettingsStore = class {
1605
1605
  }, 300);
1606
1606
  }
1607
1607
  async flushWrite() {
1608
- if (!this.scope) return;
1608
+ if (!this.form) return;
1609
1609
  const fields = [...this.pendingFields];
1610
1610
  this.pendingFields.clear();
1611
1611
  if (fields.length === 0) return;
@@ -1614,7 +1614,7 @@ var SettingsStore = class {
1614
1614
  path: [field],
1615
1615
  value: this.state[field]
1616
1616
  }));
1617
- await this.scope.mutate(ops).catch(() => {
1617
+ await this.form.mutate(ops).catch(() => {
1618
1618
  });
1619
1619
  }
1620
1620
  async testChannel(channel) {
@@ -1643,11 +1643,11 @@ var SettingsStore = class {
1643
1643
  this.writeTimer = null;
1644
1644
  }
1645
1645
  this.pendingFields.clear();
1646
- if (this.unsubscribeScope) {
1647
- this.unsubscribeScope();
1648
- this.unsubscribeScope = null;
1646
+ if (this.unsubscribeForm) {
1647
+ this.unsubscribeForm();
1648
+ this.unsubscribeForm = null;
1649
1649
  }
1650
- this.scope = null;
1650
+ this.form = null;
1651
1651
  this.credentials = null;
1652
1652
  this.listeners.clear();
1653
1653
  }
@@ -2256,18 +2256,15 @@ function TidySettingsPanel(props) {
2256
2256
  }
2257
2257
  function setupSettingsUi(ctx) {
2258
2258
  if (typeof window === "undefined") return;
2259
+ const configForms = ctx.configForms;
2260
+ const remoteCredentials = ctx.remote?.credentials ?? null;
2259
2261
  try {
2260
- const settingsScope = ctx?.settingsScope || (ctx?.get ? ctx.get("settingsScope") : null);
2261
- const remoteCredentials = ctx?.remote?.credentials || (ctx?.get ? ctx.get("remote.credentials") : null);
2262
- if (settingsScope && typeof settingsScope.bind === "function") {
2263
- const scope = settingsScope.bind({ namespace: SETTINGS_NAMESPACE });
2264
- settingsStore.attach(scope, remoteCredentials ?? null);
2265
- }
2262
+ settingsStore.attach(configForms.get(SETTINGS_NAMESPACE), remoteCredentials);
2266
2263
  } catch (err) {
2267
- console.warn("[dsh-chat-translate] Failed to bind settings scope:", err);
2264
+ console.warn("[dsh-chat-translate] Failed to bind the configuration form:", err);
2268
2265
  }
2269
- const locale = typeof ctx?.get === "function" ? ctx.get("locale") : null;
2270
- if (locale && typeof locale.register === "function" && typeof ctx?.effect === "function") {
2266
+ const locale = ctx.locale;
2267
+ if (locale && typeof locale.register === "function") {
2271
2268
  ctx.effect(
2272
2269
  () => locale.register(NS, { zh, en }),
2273
2270
  "dsh-chat-translate: locale dictionaries"
@@ -2275,27 +2272,36 @@ function setupSettingsUi(ctx) {
2275
2272
  }
2276
2273
  const t = locale && typeof locale.bind === "function" ? locale.bind(NS) : (key) => zh[key] ?? key;
2277
2274
  try {
2278
- const slots = ctx?.slots || (ctx?.get ? ctx.get("slots") : null);
2275
+ const slots = ctx.slots;
2279
2276
  if (!slots || typeof slots.inject !== "function") return;
2280
- slots.inject(
2281
- "settings.section",
2282
- () => claimReadingSettingsPage(ctx, () => t("pageNav"), NS)
2283
- );
2284
- slots.inject(
2285
- READING_ITEM_SLOT,
2286
- () => slots.register(
2287
- {
2288
- name: READING_ITEM_SLOT,
2289
- // id = 本插件的 Host 设置命名空间(settings.section 时代的 id 沿用)
2290
- id: SETTINGS_NAMESPACE,
2291
- // 卡片在共享页里的顺序:丝滑流式 10、吸顶提示 20、聊天翻译 30
2292
- order: 30,
2293
- // 共享页按此标签渲染 tab
2294
- label: () => t("title"),
2295
- locale: NS
2296
- },
2297
- TidySettingsPanel
2298
- )
2277
+ ctx.effect(
2278
+ () => configForms.whileServed([SETTINGS_NAMESPACE], () => {
2279
+ const offPage = slots.inject(
2280
+ "settings.section",
2281
+ () => claimReadingSettingsPage(ctx, () => t("pageNav"), NS)
2282
+ );
2283
+ const offCard = slots.inject(
2284
+ READING_ITEM_SLOT,
2285
+ () => slots.register(
2286
+ {
2287
+ name: READING_ITEM_SLOT,
2288
+ // id = 本插件的配置条目 id(= profile entry id = 设置命名空间)
2289
+ id: SETTINGS_NAMESPACE,
2290
+ // 卡片在共享页里的顺序:丝滑流式 10、吸顶提示 20、聊天翻译 30
2291
+ order: 30,
2292
+ // 共享页按此标签渲染 tab
2293
+ label: () => t("title"),
2294
+ locale: NS
2295
+ },
2296
+ TidySettingsPanel
2297
+ )
2298
+ );
2299
+ return () => {
2300
+ offCard();
2301
+ offPage();
2302
+ };
2303
+ }),
2304
+ "dsh-chat-translate: shared reading page"
2299
2305
  );
2300
2306
  } catch (err) {
2301
2307
  console.warn("[dsh-chat-translate] Failed to inject settings section:", err);
@@ -2304,7 +2310,7 @@ function setupSettingsUi(ctx) {
2304
2310
 
2305
2311
  // src/client/index.ts
2306
2312
  var name = "dsh-chat-translate";
2307
- var inject = ["slots", "settingsScope", "remote", "remote.credentials", "locale"];
2313
+ var inject = ["slots", "configForms", "remote", "remote.credentials", "locale"];
2308
2314
  function apply(ctx) {
2309
2315
  ctx.effect(() => chatTranslateObserver.start(document), "dsh-chat-translate: title translate observer");
2310
2316
  ctx.effect(() => setupSettingsUi(ctx), "dsh-chat-translate: settings section");