@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 +1 -1
- package/cordis.patch.yml +2 -1
- package/docs//351/205/215/347/275/256.md +14 -12
- package/lib/client.js +58 -52
- package/lib/client.js.map +2 -2
- package/lib/index.js +152 -55
- package/lib/index.js.map +4 -4
- package/lib/types/client/index.d.ts +9 -8
- package/lib/types/client/reading-settings-page.d.ts +7 -0
- package/lib/types/client/settings/store.d.ts +35 -24
- package/lib/types/client/settings/ui.d.ts +5 -4
- package/lib/types/index.d.ts +53 -12
- package/lib/types/server/config.d.ts +59 -28
- package/lib/types/server/router.d.ts +4 -4
- package/package.json +12 -12
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
|
|
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
|
-
#
|
|
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
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
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
|
-
|
|
1455
|
+
form = null;
|
|
1456
1456
|
credentials = null;
|
|
1457
|
-
|
|
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(
|
|
1466
|
-
if (this.
|
|
1467
|
-
this.
|
|
1468
|
-
this.
|
|
1465
|
+
attach(form, credentials) {
|
|
1466
|
+
if (this.unsubscribeForm) {
|
|
1467
|
+
this.unsubscribeForm();
|
|
1468
|
+
this.unsubscribeForm = null;
|
|
1469
1469
|
}
|
|
1470
|
-
this.
|
|
1470
|
+
this.form = form;
|
|
1471
1471
|
this.credentials = credentials;
|
|
1472
|
-
if (
|
|
1473
|
-
this.
|
|
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
|
|
1487
|
+
/** Map the resolved section into client state and notify. */
|
|
1488
1488
|
derive() {
|
|
1489
|
-
if (!this.
|
|
1490
|
-
const snap = this.
|
|
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
|
-
*
|
|
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
|
|
1559
|
-
*
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1647
|
-
this.
|
|
1648
|
-
this.
|
|
1646
|
+
if (this.unsubscribeForm) {
|
|
1647
|
+
this.unsubscribeForm();
|
|
1648
|
+
this.unsubscribeForm = null;
|
|
1649
1649
|
}
|
|
1650
|
-
this.
|
|
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
|
-
|
|
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
|
|
2264
|
+
console.warn("[dsh-chat-translate] Failed to bind the configuration form:", err);
|
|
2268
2265
|
}
|
|
2269
|
-
const locale =
|
|
2270
|
-
if (locale && typeof locale.register === "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
|
|
2275
|
+
const slots = ctx.slots;
|
|
2279
2276
|
if (!slots || typeof slots.inject !== "function") return;
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
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", "
|
|
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");
|