@lynn123411/dsh-chat-translate 1.2.6 → 1.3.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/docs//351/205/215/347/275/256.md +2 -2
- package/lib/client.js +179 -17
- package/lib/client.js.map +4 -4
- package/lib/types/client/index.d.ts +9 -5
- package/lib/types/client/locales.d.ts +14 -0
- package/lib/types/client/reading-settings-page.d.ts +85 -0
- package/lib/types/client/settings/ui.d.ts +15 -1
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ DeepSeek Harness Web 界面的工具调用与思考摘要智能翻译插件。
|
|
|
12
12
|
- **当前会话作用域**:仅翻译当前查看的会话,切换会话自动跟随新内容;视口懒加载(150px 缓冲)与文档顺序排队,译文按阅读顺序出现。
|
|
13
13
|
- **智能调度与熔断保护**:1–100 动态并发限流队列、AI 30s / Bing 2s 超时、连续失败熔断自愈、在途请求合并去重、7 天 LRU 磁盘持久化缓存(`~/.dsh/dsh-chat-translate/cache.json`)。
|
|
14
14
|
- **DSH 原生配置接入**:配置走 DSH `settings` 服务(用户层写入 `~/.dsh/settings.yaml` 的 `dsh-chat-translate` 段)、密钥走 DSH `credentials` 服务、设置面板经 `settingsScope` 与 `credentials` Remote API 读写——无任何自研配置文件。
|
|
15
|
-
-
|
|
15
|
+
- **设置面板集成**:设置面板已迁入与 `dsh-smooth-stream`、`dsh-oil-sticky-prompt` 共用的「设置 - 阅读体验」共享页(本插件贡献其中一张卡片),页内提供总开关、AI/Bing 通道开关、Base URL 与模型配置、通道测试与并发数调节。
|
|
16
16
|
|
|
17
17
|
## 安装
|
|
18
18
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> 本文档为 [@lynn123411/dsh-chat-translate](../README.md) 的配置说明(自 README 的「## 配置」段迁出,内容保持一致)。
|
|
4
4
|
|
|
5
|
-
1. **API Key**:打开「设置 -
|
|
5
|
+
1. **API Key**:打开「设置 - 阅读体验」,在 AI 翻译卡片中直接填写 API Key 并保存(经 DSH 凭据服务写入 `~/.dsh/.credentials.yaml` 的 `TRANSLATE_API_KEY`,权限 0600,保存后立即生效;留空保存 = 清除该键)。也可以手动编辑该文件:
|
|
6
6
|
|
|
7
7
|
```yaml
|
|
8
8
|
refs:
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
> ⚠️ **注意**:DSH 的凭据加载器要求 refs 键值非空。手动编辑时要么填写真实 Key,要么**直接删除该行**——不要写成 `TRANSLATE_API_KEY: ""` 这类空值,否则 DSH 启动会失败。未配置时插件自动由 Bing 通道兜底,不会报错。
|
|
13
13
|
|
|
14
|
-
2. **Base URL 与模型等设置**:在「设置 -
|
|
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 会热同步):
|
|
15
15
|
|
|
16
16
|
```yaml
|
|
17
17
|
dsh-chat-translate:
|
package/lib/client.js
CHANGED
|
@@ -738,7 +738,7 @@ var ChatTranslateObserver = class {
|
|
|
738
738
|
var chatTranslateObserver = new ChatTranslateObserver();
|
|
739
739
|
|
|
740
740
|
// src/client/settings/ui.tsx
|
|
741
|
-
var
|
|
741
|
+
var import_react2 = require("react");
|
|
742
742
|
|
|
743
743
|
// src/client/settings/store.ts
|
|
744
744
|
var SETTINGS_NAMESPACE = "dsh-chat-translate";
|
|
@@ -1208,6 +1208,152 @@ var SETTINGS_CSS = String.raw`
|
|
|
1208
1208
|
}
|
|
1209
1209
|
`;
|
|
1210
1210
|
|
|
1211
|
+
// src/client/reading-settings-page.tsx
|
|
1212
|
+
var import_react = require("react");
|
|
1213
|
+
var READING_PAGE_ID = "reading";
|
|
1214
|
+
var READING_PAGE_ORDER = 110;
|
|
1215
|
+
var READING_ITEM_SLOT = "reading.settings.item";
|
|
1216
|
+
function readLabel(label) {
|
|
1217
|
+
if (typeof label === "function") return label();
|
|
1218
|
+
return typeof label === "string" ? label : "";
|
|
1219
|
+
}
|
|
1220
|
+
var TABLIST_STYLE = {
|
|
1221
|
+
display: "flex",
|
|
1222
|
+
alignItems: "flex-end",
|
|
1223
|
+
gap: "22px",
|
|
1224
|
+
marginTop: "2px",
|
|
1225
|
+
marginBottom: "16px"
|
|
1226
|
+
};
|
|
1227
|
+
var TAB_STYLE = {
|
|
1228
|
+
appearance: "none",
|
|
1229
|
+
background: "transparent",
|
|
1230
|
+
// No border on ANY tab: the marker below is the active tab's own element, so
|
|
1231
|
+
// an inactive tab has nothing that could render a line.
|
|
1232
|
+
border: "none",
|
|
1233
|
+
position: "relative",
|
|
1234
|
+
padding: "7px 1px 11px",
|
|
1235
|
+
cursor: "pointer",
|
|
1236
|
+
font: "inherit",
|
|
1237
|
+
fontSize: "13px",
|
|
1238
|
+
lineHeight: "20px",
|
|
1239
|
+
color: "var(--dsw-alias-label-tertiary, inherit)"
|
|
1240
|
+
};
|
|
1241
|
+
var TAB_ACTIVE_STYLE = {
|
|
1242
|
+
...TAB_STYLE,
|
|
1243
|
+
color: "var(--dsw-alias-label-primary, inherit)"
|
|
1244
|
+
};
|
|
1245
|
+
var TAB_MARKER_STYLE = {
|
|
1246
|
+
position: "absolute",
|
|
1247
|
+
left: 0,
|
|
1248
|
+
right: 0,
|
|
1249
|
+
bottom: 0,
|
|
1250
|
+
height: "2px",
|
|
1251
|
+
borderRadius: "2px 2px 0 0",
|
|
1252
|
+
background: "var(--dsw-alias-label-primary, currentColor)"
|
|
1253
|
+
};
|
|
1254
|
+
var PANEL_STYLE = { listStyle: "none", margin: 0, padding: 0 };
|
|
1255
|
+
var PANEL_HIDDEN_STYLE = { ...PANEL_STYLE, display: "none" };
|
|
1256
|
+
function createReadingTabs(ctx) {
|
|
1257
|
+
const locale = ctx.get("locale");
|
|
1258
|
+
let version = -1;
|
|
1259
|
+
let revision = -1;
|
|
1260
|
+
let tabs = [];
|
|
1261
|
+
return {
|
|
1262
|
+
getSnapshot: () => {
|
|
1263
|
+
const nextVersion = ctx.slots.getVersion(READING_ITEM_SLOT);
|
|
1264
|
+
const nextRevision = locale === void 0 ? 0 : locale.getSnapshot().revision;
|
|
1265
|
+
if (nextVersion === version && nextRevision === revision) return tabs;
|
|
1266
|
+
version = nextVersion;
|
|
1267
|
+
revision = nextRevision;
|
|
1268
|
+
tabs = ctx.slots.entries(READING_ITEM_SLOT).map((entry) => ({
|
|
1269
|
+
id: entry.options.id ?? "",
|
|
1270
|
+
order: entry.options.order ?? 0,
|
|
1271
|
+
label: readLabel(entry.options.label)
|
|
1272
|
+
})).sort((left, right) => left.order - right.order);
|
|
1273
|
+
return tabs;
|
|
1274
|
+
},
|
|
1275
|
+
subscribe: (listener) => {
|
|
1276
|
+
const offSlots = ctx.slots.subscribe(READING_ITEM_SLOT, listener);
|
|
1277
|
+
const offLocale = locale?.subscribe(listener);
|
|
1278
|
+
return () => {
|
|
1279
|
+
offSlots();
|
|
1280
|
+
offLocale?.();
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
function ReadingSettingsSection({ renderSlot, readingTabs }) {
|
|
1286
|
+
const tabs = (0, import_react.useSyncExternalStore)(
|
|
1287
|
+
readingTabs.subscribe,
|
|
1288
|
+
readingTabs.getSnapshot,
|
|
1289
|
+
readingTabs.getSnapshot
|
|
1290
|
+
);
|
|
1291
|
+
const [requested, setRequested] = (0, import_react.useState)(null);
|
|
1292
|
+
const selected = requested !== null && tabs.some((tab) => tab.id === requested) ? requested : tabs[0]?.id ?? null;
|
|
1293
|
+
if (selected === null) return null;
|
|
1294
|
+
return (0, import_react.createElement)(
|
|
1295
|
+
"div",
|
|
1296
|
+
null,
|
|
1297
|
+
(0, import_react.createElement)(
|
|
1298
|
+
"div",
|
|
1299
|
+
{ role: "tablist", style: TABLIST_STYLE },
|
|
1300
|
+
tabs.map((tab) => (0, import_react.createElement)(
|
|
1301
|
+
"button",
|
|
1302
|
+
{
|
|
1303
|
+
key: tab.id,
|
|
1304
|
+
type: "button",
|
|
1305
|
+
role: "tab",
|
|
1306
|
+
"aria-selected": tab.id === selected,
|
|
1307
|
+
style: tab.id === selected ? TAB_ACTIVE_STYLE : TAB_STYLE,
|
|
1308
|
+
onClick: () => {
|
|
1309
|
+
setRequested(tab.id);
|
|
1310
|
+
}
|
|
1311
|
+
},
|
|
1312
|
+
tab.label,
|
|
1313
|
+
tab.id === selected ? (0, import_react.createElement)("span", { style: TAB_MARKER_STYLE, "aria-hidden": true }) : null
|
|
1314
|
+
))
|
|
1315
|
+
),
|
|
1316
|
+
tabs.map((tab) => (0, import_react.createElement)(
|
|
1317
|
+
"ul",
|
|
1318
|
+
{
|
|
1319
|
+
key: tab.id,
|
|
1320
|
+
role: "tabpanel",
|
|
1321
|
+
hidden: tab.id !== selected,
|
|
1322
|
+
style: tab.id === selected ? PANEL_STYLE : PANEL_HIDDEN_STYLE
|
|
1323
|
+
},
|
|
1324
|
+
renderSlot(READING_ITEM_SLOT, {}, { only: tab.id })
|
|
1325
|
+
))
|
|
1326
|
+
);
|
|
1327
|
+
}
|
|
1328
|
+
function readingPageClaimed(ctx) {
|
|
1329
|
+
return ctx.slots.entries("settings.section").some((entry) => entry.options.id === READING_PAGE_ID);
|
|
1330
|
+
}
|
|
1331
|
+
function claimReadingSettingsPage(ctx, label, locale) {
|
|
1332
|
+
if (readingPageClaimed(ctx)) return () => {
|
|
1333
|
+
};
|
|
1334
|
+
const readingTabs = createReadingTabs(ctx);
|
|
1335
|
+
return ctx.slots.register({
|
|
1336
|
+
name: "settings.section",
|
|
1337
|
+
id: READING_PAGE_ID,
|
|
1338
|
+
order: READING_PAGE_ORDER,
|
|
1339
|
+
label,
|
|
1340
|
+
locale,
|
|
1341
|
+
inject: () => ({ readingTabs }),
|
|
1342
|
+
children: { "reading.settings.item": { kind: "list", scope: "root" } }
|
|
1343
|
+
}, ReadingSettingsSection);
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
// src/client/locales.ts
|
|
1347
|
+
var NS = "settings.chatTranslate";
|
|
1348
|
+
var en = {
|
|
1349
|
+
pageNav: "Reading",
|
|
1350
|
+
title: "Chat translate"
|
|
1351
|
+
};
|
|
1352
|
+
var zh = {
|
|
1353
|
+
pageNav: "\u9605\u8BFB\u4F53\u9A8C",
|
|
1354
|
+
title: "\u804A\u5929\u7FFB\u8BD1"
|
|
1355
|
+
};
|
|
1356
|
+
|
|
1211
1357
|
// src/client/settings/ui.tsx
|
|
1212
1358
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1213
1359
|
var stylesInjected = false;
|
|
@@ -1232,14 +1378,14 @@ function Switch(props) {
|
|
|
1232
1378
|
}
|
|
1233
1379
|
);
|
|
1234
1380
|
}
|
|
1235
|
-
function TidySettingsPanel() {
|
|
1381
|
+
function TidySettingsPanel(_props) {
|
|
1236
1382
|
ensureSettingsStyles();
|
|
1237
|
-
const [state, setState] = (0,
|
|
1238
|
-
const [testing, setTesting] = (0,
|
|
1239
|
-
const [apiKeyInput, setApiKeyInput] = (0,
|
|
1240
|
-
const [savingKey, setSavingKey] = (0,
|
|
1241
|
-
const [keyMsg, setKeyMsg] = (0,
|
|
1242
|
-
(0,
|
|
1383
|
+
const [state, setState] = (0, import_react2.useState)(() => settingsStore.getState());
|
|
1384
|
+
const [testing, setTesting] = (0, import_react2.useState)(null);
|
|
1385
|
+
const [apiKeyInput, setApiKeyInput] = (0, import_react2.useState)("");
|
|
1386
|
+
const [savingKey, setSavingKey] = (0, import_react2.useState)(false);
|
|
1387
|
+
const [keyMsg, setKeyMsg] = (0, import_react2.useState)(null);
|
|
1388
|
+
(0, import_react2.useEffect)(() => {
|
|
1243
1389
|
return settingsStore.subscribe(() => {
|
|
1244
1390
|
setState(settingsStore.getState());
|
|
1245
1391
|
});
|
|
@@ -1458,21 +1604,37 @@ function setupSettingsUi(ctx) {
|
|
|
1458
1604
|
} catch (err) {
|
|
1459
1605
|
console.warn("[dsh-chat-translate] Failed to bind settings scope:", err);
|
|
1460
1606
|
}
|
|
1607
|
+
const locale = typeof ctx?.get === "function" ? ctx.get("locale") : null;
|
|
1608
|
+
if (locale && typeof locale.register === "function" && typeof ctx?.effect === "function") {
|
|
1609
|
+
ctx.effect(
|
|
1610
|
+
() => locale.register(NS, { zh, en }),
|
|
1611
|
+
"dsh-chat-translate: locale dictionaries"
|
|
1612
|
+
);
|
|
1613
|
+
}
|
|
1614
|
+
const t = locale && typeof locale.bind === "function" ? locale.bind(NS) : (key) => zh[key] ?? key;
|
|
1461
1615
|
try {
|
|
1462
1616
|
const slots = ctx?.slots || (ctx?.get ? ctx.get("slots") : null);
|
|
1463
1617
|
if (!slots || typeof slots.inject !== "function") return;
|
|
1464
|
-
slots.inject(
|
|
1465
|
-
|
|
1618
|
+
slots.inject(
|
|
1619
|
+
"settings.section",
|
|
1620
|
+
() => claimReadingSettingsPage(ctx, () => t("pageNav"), NS)
|
|
1621
|
+
);
|
|
1622
|
+
slots.inject(
|
|
1623
|
+
READING_ITEM_SLOT,
|
|
1624
|
+
() => slots.register(
|
|
1466
1625
|
{
|
|
1467
|
-
name:
|
|
1468
|
-
id
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1626
|
+
name: READING_ITEM_SLOT,
|
|
1627
|
+
// id = 本插件的 Host 设置命名空间(settings.section 时代的 id 沿用)
|
|
1628
|
+
id: SETTINGS_NAMESPACE,
|
|
1629
|
+
// 卡片在共享页里的顺序:丝滑流式 10、吸顶提示 20、聊天翻译 30
|
|
1630
|
+
order: 30,
|
|
1631
|
+
// 共享页按此标签渲染 tab
|
|
1632
|
+
label: () => t("title"),
|
|
1633
|
+
locale: NS
|
|
1472
1634
|
},
|
|
1473
1635
|
TidySettingsPanel
|
|
1474
|
-
)
|
|
1475
|
-
|
|
1636
|
+
)
|
|
1637
|
+
);
|
|
1476
1638
|
} catch (err) {
|
|
1477
1639
|
console.warn("[dsh-chat-translate] Failed to inject settings section:", err);
|
|
1478
1640
|
}
|
package/lib/client.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/client/index.ts", "../src/client/translate/client-cache.ts", "../src/describe-error.ts", "../src/client/translate/api.ts", "../src/client/translate/mount.ts", "../src/client/translate/viewport-observer.ts", "../src/client/translate/lazy.ts", "../src/client/translate/observer.ts", "../src/client/settings/ui.tsx", "../src/client/settings/store.ts", "../src/client/settings/styles.ts"],
|
|
4
|
-
"sourcesContent": ["import { chatTranslateObserver } from './translate/observer.ts';\nimport { setupSettingsUi } from './settings/ui.tsx';\n\n/** Client plugin name, shared with the browser bundle id. */\nexport const name = 'dsh-chat-translate';\n\n/**\n * Declared services: slots for the settings section, settingsScope for the\n * per-namespace settings mirror, and the remote + remote.credentials pair for\n * the credentials Remote namespace \u2014 the runtime withholds any service not\n * declared here, so `ctx.remote.credentials` would be undefined otherwise.\n */\nexport const inject = ['slots', 'settingsScope', 'remote', 'remote.credentials'];\n\ninterface ClientContext {\n effect(factory: () => void | (() => void), label: string): void;\n get?(serviceName: string): any;\n slots?: any;\n settingsScope?: any;\n remote?: { credentials?: any };\n}\n\n/**\n * Mount the tool-call / think-summary translation observer and the settings UI.\n * @param ctx - DSH browser client context.\n */\nexport function apply(ctx: ClientContext): void {\n // 1. Mount tool title / think summary translation observer (current session only)\n ctx.effect(() => chatTranslateObserver.start(document), 'dsh-chat-translate: title translate observer');\n\n // 2. Mount settings UI section\n ctx.effect(() => setupSettingsUi(ctx), 'dsh-chat-translate: settings section');\n}\n\nexport { chatTranslateObserver } from './translate/observer.ts';\nexport { setupSettingsUi } from './settings/ui.tsx';\nexport { NonDestructiveTranslationMount } from './translate/mount.ts';\nexport { StreamDebounceViewportObserver } from './translate/viewport-observer.ts';\nexport { clientCache } from './translate/client-cache.ts';\nexport { lazyQueue } from './translate/lazy.ts';\nexport { settingsStore } from './settings/store.ts';\n", "const CACHE_KEY = 'dsh-chat-translate:cache';\nconst MAX_LOCAL_ENTRIES = 500;\n/** Entries older than this are treated as expired. */\nconst TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days\n\ninterface CacheEntry {\n t: number; // epoch ms; 0 = legacy entry without timestamp\n v: string;\n}\n\nexport class ClientCache {\n // Map preserves insertion order in JS, enabling true LRU semantics.\n private memCache = new Map<string, CacheEntry>();\n private dirty = false;\n private saveTimer: number | null = null;\n\n constructor() {\n this.load();\n }\n\n private load(): void {\n if (typeof localStorage === 'undefined') return;\n try {\n const raw = localStorage.getItem(CACHE_KEY);\n if (raw) {\n const obj = JSON.parse(raw);\n if (obj && typeof obj === 'object') {\n for (const [k, entry] of Object.entries(obj)) {\n if (typeof entry === 'string') {\n // Legacy entry from an older release.\n this.memCache.set(k, { t: 0, v: entry });\n } else if (entry && typeof entry === 'object' && typeof (entry as CacheEntry).v === 'string') {\n this.memCache.set(k, entry as CacheEntry);\n }\n }\n }\n }\n } catch {\n // Ignore parse failure\n }\n }\n\n get(text: string): string | undefined {\n const key = text.trim().toLowerCase();\n const entry = this.memCache.get(key);\n if (entry === undefined) return undefined;\n // Drop dirty fallback entries where value equals key\n if (entry.v && entry.v.trim().toLowerCase() === key) {\n this.memCache.delete(key);\n this.dirty = true;\n this.scheduleSave();\n return undefined;\n }\n if (entry.t > 0 && Date.now() - entry.t > TTL_MS) {\n this.memCache.delete(key);\n this.dirty = true;\n this.scheduleSave();\n return undefined;\n }\n // True LRU: Re-insert to move to the end (most recently used)\n this.memCache.delete(key);\n this.memCache.set(key, entry);\n return entry.v;\n }\n\n set(text: string, translated: string): void {\n const key = text.trim().toLowerCase();\n if (this.memCache.has(key)) {\n this.memCache.delete(key);\n } else if (this.memCache.size >= MAX_LOCAL_ENTRIES) {\n // Evict least recently used (first item in Map)\n const oldestKey = this.memCache.keys().next().value;\n if (oldestKey !== undefined) {\n this.memCache.delete(oldestKey);\n }\n }\n this.memCache.set(key, { t: Date.now(), v: translated });\n this.dirty = true;\n this.scheduleSave();\n }\n\n private scheduleSave(): void {\n if (this.saveTimer !== null || typeof window === 'undefined') return;\n this.saveTimer = window.setTimeout(() => {\n this.saveTimer = null;\n this.flushSync();\n }, 2000);\n }\n\n flushSync(): void {\n if (!this.dirty || typeof localStorage === 'undefined') return;\n this.dirty = false;\n try {\n const obj: Record<string, CacheEntry> = {};\n for (const [k, v] of this.memCache.entries()) {\n obj[k] = v;\n }\n localStorage.setItem(CACHE_KEY, JSON.stringify(obj));\n } catch {\n // Ignore quota error\n }\n }\n\n clear(): void {\n this.memCache.clear();\n this.dirty = true;\n this.flushSync();\n }\n\n size(): number {\n return this.memCache.size;\n }\n}\n\nexport const clientCache = new ClientCache();", "/**\n * \u628A fetch / \u7F51\u7EDC\u5931\u8D25\u7684\u53EF\u8BFB\u539F\u56E0\u5C55\u5F00\u6210\u5355\u884C\u6587\u672C\uFF08\u5BBF\u4E3B\u4E0E\u5BA2\u6237\u7AEF\u5171\u7528\uFF09\u3002\n *\n * \u80CC\u666F\uFF1ANode 18+ \u4E0E\u6D4F\u89C8\u5668\u7684 fetch \u5728\u7F51\u7EDC\u5C42\u5931\u8D25\u65F6\u53EA\u7ED9\u4E00\u53E5 `TypeError: fetch failed`\n * \uFF08\u6D4F\u89C8\u5668\u662F `Failed to fetch`\uFF09\uFF0C\u771F\u5B9E\u539F\u56E0\u85CF\u5728 `err.cause` \u91CC\u2014\u2014\u4F8B\u5982\n * `connect ENETUNREACH 172.24.52.126:8081`\u3001`other side closed [UND_ERR_SOCKET]`\u3001\n * `getaddrinfo ENOTFOUND`\u3002\u53EA\u4E0A\u62A5 `err.message` \u4F1A\u8BA9\u300C\u7AEF\u70B9\u4E0D\u53EF\u8FBE / \u88AB\u4EE3\u7406\u65AD\u5F00\u300D\u548C\n * \u300C\u63D2\u4EF6\u81EA\u8EAB\u51FA\u9519\u300D\u957F\u5F97\u4E00\u6A21\u4E00\u6837\uFF1A\u7528\u6237\u53EA\u770B\u5230\u65E0\u4FE1\u606F\u91CF\u7684 fetch failed\uFF0C\u65E0\u4ECE\u5224\u65AD\u8BE5\u6539\u4EC0\u4E48\u3002\n *\n * \u8FD9\u91CC\u628A message \u4E0E cause \u94FE\uFF08\u542B AggregateError \u7684\u591A\u5730\u5740\u5931\u8D25\uFF09\u62FC\u6210 `A \u2190 B \u2190 C`\u3002\n */\nexport function describeError(err: unknown): string {\n if (err === null || err === undefined) return String(err);\n const parts: string[] = [];\n const push = (s: string): void => {\n const t = String(s || '').trim();\n if (t && !parts.includes(t)) parts.push(t);\n };\n const head = (err as { message?: string }).message || String(err);\n push(head);\n const headCode = (err as { code?: string }).code;\n if (headCode && !head.includes(headCode)) push('[' + headCode + ']');\n let cause = (err as { cause?: unknown }).cause as\n | { message?: string; code?: string; cause?: unknown; errors?: unknown }\n | undefined;\n for (let depth = 0; cause && depth < 4; depth++) {\n const label = cause.message || String(cause);\n push(cause.code && !label.includes(cause.code) ? label + ' [' + cause.code + ']' : label);\n // AggregateError\uFF1A\u591A\u5730\u5740\u5168\u90E8\u5931\u8D25\uFF08happy-eyeballs \u53CC\u6808\u5E38\u89C1\uFF09\uFF0C\u5C55\u5F00\u524D\u51E0\u6761\n if (Array.isArray(cause.errors)) {\n for (const sub of (cause.errors as { message?: string; code?: string }[]).slice(0, 3)) {\n if (sub) push(sub.code || sub.message || String(sub));\n }\n }\n cause = cause.cause as typeof cause;\n }\n return parts.join(' \u2190 ');\n}", "import { describeError } from '../../describe-error.ts';\n\nexport interface TranslateItemResult {\n original: string;\n translated: string;\n channel: string;\n cached: boolean;\n}\n\nexport interface TranslateBatchOptions {\n signal?: AbortSignal;\n timeoutMs?: number;\n}\n\nexport async function requestTranslateBatch(\n texts: string[],\n options: TranslateBatchOptions = {}\n): Promise<TranslateItemResult[]> {\n if (!Array.isArray(texts) || texts.length === 0) return [];\n \n // Filter out empty or whitespace-only texts\n const validTexts = texts.map((t) => (typeof t === 'string' ? t : ''));\n if (validTexts.length === 0) return [];\n\n const controller = new AbortController();\n const timeoutId = options.timeoutMs\n ? setTimeout(() => controller.abort(), options.timeoutMs)\n : null;\n\n const effectiveSignal = options.signal\n ? (options.signal.aborted ? options.signal : controller.signal)\n : controller.signal;\n\n if (options.signal) {\n options.signal.addEventListener('abort', () => controller.abort(), { once: true });\n }\n\n try {\n const res = await fetch('/api/dsh-chat-translate/translate', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ texts: validTexts }),\n signal: effectiveSignal,\n });\n\n if (res.ok) {\n const data = (await res.json()) as { ok: boolean; results?: TranslateItemResult[]; error?: string };\n if (data.ok && Array.isArray(data.results) && data.results.length > 0) {\n return data.results;\n } else if (data.error) {\n console.warn(`[dsh-chat-translate] \u7FFB\u8BD1\u63A5\u53E3\u9519\u8BEF: ${data.error}`);\n }\n } else {\n console.warn(`[dsh-chat-translate] \u7FFB\u8BD1\u8BF7\u6C42\u5931\u8D25 (HTTP ${res.status})`);\n }\n } catch (err: any) {\n if (err?.name === 'AbortError') {\n // Intentionally aborted\n }\n } finally {\n if (timeoutId !== null) {\n clearTimeout(timeoutId);\n }\n }\n\n // Host route unreachable or error \u2014 leave the original text untouched\n return validTexts.map((t) => ({\n original: t,\n translated: t,\n channel: 'fallback-client',\n cached: false,\n }));\n}\n\nexport async function testServerChannel(channel: string): Promise<{ ok: boolean; latencyMs: number; error?: string }> {\n try {\n const res = await fetch('/api/dsh-chat-translate/test-channel', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ channel }),\n });\n return await res.json();\n } catch (err: any) {\n return { ok: false, latencyMs: 0, error: describeError(err) };\n }\n}\n", "/**\n * Non-destructive translation DOM mount mechanism.\n *\n * Preserves the original React Fiber tree, event listeners, and nested DOM nodes\n * by wrapping them in a hidden container (<span class=\"dsh-tidy-original-hidden\">)\n * rather than destroying them with `element.textContent = translated`.\n *\n * Displays the translated text in a companion container (<span class=\"dsh-tidy-translated-block\">).\n * Clicking the translation toggles between original and translated text in-place.\n */\n\nexport const CLASS_ORIGINAL_HIDDEN = 'dsh-tidy-original-hidden';\nexport const CLASS_ORIGINAL_SHOWN = 'dsh-tidy-original-shown';\nexport const CLASS_TRANSLATED_BLOCK = 'dsh-tidy-translated-block';\n\nexport interface MountOptions {\n originalText?: string;\n interactive?: boolean;\n}\n\nexport class NonDestructiveTranslationMount {\n /**\n * Mounts a translated string onto the target element non-destructively.\n */\n static mount(\n element: HTMLElement,\n translated: string,\n options: MountOptions = {}\n ): void {\n if (!element || !element.ownerDocument) return;\n const doc = element.ownerDocument;\n\n // Check if already mounted\n let transWrapper = element.querySelector<HTMLElement>(`:scope > .${CLASS_TRANSLATED_BLOCK}`);\n let origWrapper = element.querySelector<HTMLElement>(\n `:scope > .${CLASS_ORIGINAL_HIDDEN}, :scope > .${CLASS_ORIGINAL_SHOWN}`\n );\n\n if (transWrapper && origWrapper) {\n // Update existing translation\n transWrapper.textContent = translated;\n element.dataset.tidyTranslated = 'true';\n if (options.originalText) element.dataset.original = options.originalText;\n return;\n }\n\n const originalText = options.originalText ?? this.extractVisibleText(element);\n\n // Create wrapper for original children\n origWrapper = doc.createElement('span');\n origWrapper.className = CLASS_ORIGINAL_HIDDEN;\n origWrapper.style.display = 'none';\n\n // Move all existing child nodes into origWrapper\n while (element.firstChild) {\n origWrapper.appendChild(element.firstChild);\n }\n\n // Create translation block\n transWrapper = doc.createElement('span');\n transWrapper.className = CLASS_TRANSLATED_BLOCK;\n transWrapper.textContent = translated;\n\n // Interactive bilingual toggle\n const interactive = options.interactive !== false;\n if (interactive) {\n\n const showOriginal = (e: MouseEvent): void => {\n e.stopPropagation();\n if (!origWrapper || !transWrapper) return;\n origWrapper.style.display = 'inline';\n origWrapper.className = CLASS_ORIGINAL_SHOWN;\n transWrapper.style.display = 'none';\n };\n\n const showTranslated = (e: MouseEvent): void => {\n e.stopPropagation();\n if (!origWrapper || !transWrapper) return;\n origWrapper.style.display = 'none';\n origWrapper.className = CLASS_ORIGINAL_HIDDEN;\n transWrapper.style.display = 'inline';\n };\n\n transWrapper.addEventListener('click', showOriginal);\n origWrapper.addEventListener('click', showTranslated);\n }\n\n // Append both to target element\n element.appendChild(transWrapper);\n element.appendChild(origWrapper);\n\n element.dataset.tidyTranslated = 'true';\n element.dataset.original = originalText;\n }\n\n /**\n * Unmounts translation and restores original DOM nodes completely.\n */\n static unmount(element: HTMLElement): void {\n if (!element) return;\n\n const origWrapper = element.querySelector<HTMLElement>(\n `:scope > .${CLASS_ORIGINAL_HIDDEN}, :scope > .${CLASS_ORIGINAL_SHOWN}`\n );\n const transWrapper = element.querySelector<HTMLElement>(`:scope > .${CLASS_TRANSLATED_BLOCK}`);\n\n if (origWrapper) {\n // Move all original children back to element\n while (origWrapper.firstChild) {\n element.insertBefore(origWrapper.firstChild, origWrapper);\n }\n origWrapper.remove();\n }\n\n if (transWrapper) {\n transWrapper.remove();\n }\n\n // Fallback if no wrapper was created but text was modified directly\n if (!origWrapper && element.dataset.original) {\n element.textContent = element.dataset.original;\n }\n\n delete element.dataset.tidyTranslated;\n delete element.dataset.original;\n }\n\n /**\n * Checks if an element has non-destructive translation mounted.\n */\n static isMounted(element: HTMLElement): boolean {\n return (\n element.dataset.tidyTranslated === 'true' &&\n !!element.querySelector(`:scope > .${CLASS_TRANSLATED_BLOCK}`)\n );\n }\n\n /**\n * Gets the original text recorded on the element or contained in origWrapper.\n */\n static getOriginal(element: HTMLElement): string | undefined {\n if (element.dataset.original) return element.dataset.original;\n const origWrapper = element.querySelector<HTMLElement>(\n `:scope > .${CLASS_ORIGINAL_HIDDEN}, :scope > .${CLASS_ORIGINAL_SHOWN}`\n );\n return origWrapper ? origWrapper.textContent?.trim() : undefined;\n }\n\n /**\n * Extracts text content excluding our own translation wrappers.\n */\n static extractVisibleText(element: HTMLElement): string {\n const origWrapper = element.querySelector<HTMLElement>(\n `:scope > .${CLASS_ORIGINAL_HIDDEN}, :scope > .${CLASS_ORIGINAL_SHOWN}`\n );\n if (origWrapper) {\n return origWrapper.textContent?.trim() || '';\n }\n const transWrapper = element.querySelector<HTMLElement>(`:scope > .${CLASS_TRANSLATED_BLOCK}`);\n if (transWrapper) {\n return transWrapper.textContent?.trim() || '';\n }\n return element.textContent?.trim() || '';\n }\n}", "/**\n * StreamDebounceViewportObserver\n *\n * Combines:\n * 1. True IntersectionObserver for viewport visibility detection (rootMargin: '150px 0px').\n * 2. Typing/streaming debounce (default 400ms) to avoid translating partial streaming sentences.\n * 3. Batch queuing to group multiple visible elements into efficient batch requests.\n */\n\nexport interface ViewportObserverOptions {\n rootMargin?: string;\n debounceMs?: number;\n onVisibleBatch: (items: Array<{ element: HTMLElement; text: string }>) => void;\n}\n\nexport class StreamDebounceViewportObserver {\n private intersectionObserver: IntersectionObserver | null = null;\n /**\n * Pending per-element streaming debounce timers. A Map (not a WeakMap)\n * because disconnect() must enumerate and clear every pending timer: a\n * WeakMap cannot be iterated, so a debounce armed just before the switch was\n * turned off would still fire registerForViewport() afterwards.\n */\n private streamingTimers = new Map<HTMLElement, number>();\n private pendingQueue: Array<{ element: HTMLElement; text: string }> = [];\n private batchFlushTimer: number | null = null;\n private options: Required<Omit<ViewportObserverOptions, 'onVisibleBatch'>> & {\n onVisibleBatch: ViewportObserverOptions['onVisibleBatch'];\n };\n\n constructor(options: ViewportObserverOptions) {\n this.options = {\n rootMargin: options.rootMargin ?? '150px 0px',\n debounceMs: options.debounceMs ?? 400,\n onVisibleBatch: options.onVisibleBatch,\n };\n this.initIntersectionObserver();\n }\n\n private initIntersectionObserver(): void {\n if (typeof window === 'undefined' || typeof IntersectionObserver === 'undefined') {\n return;\n }\n\n this.intersectionObserver = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting && entry.target instanceof HTMLElement) {\n const el = entry.target;\n // Stop observing once it enters viewport and is queued\n this.intersectionObserver?.unobserve(el);\n const text = el.dataset.tidyPendingText || el.textContent?.trim() || '';\n if (text) {\n delete el.dataset.tidyPendingText;\n this.enqueueBatch(el, text);\n }\n }\n }\n },\n {\n root: null, // viewport\n rootMargin: this.options.rootMargin,\n threshold: 0,\n }\n );\n }\n\n /**\n * Observe an element with streaming debounce.\n * If streaming updates characterData repeatedly within debounceMs, the timer resets.\n */\n observeWithDebounce(element: HTMLElement, text: string, immediate = false): void {\n if (!element || !text) return;\n\n // Clear any active streaming timer for this element\n const existingTimer = this.streamingTimers.get(element);\n if (existingTimer !== undefined) {\n clearTimeout(existingTimer);\n this.streamingTimers.delete(element);\n }\n\n if (immediate || this.options.debounceMs <= 0) {\n this.registerForViewport(element, text);\n return;\n }\n\n const timer = window.setTimeout(() => {\n // Stale-callback guard: disconnect() clears the map, so a callback that\n // is no longer the registered timer must not re-enter the observer.\n if (this.streamingTimers.get(element) !== timer) return;\n this.streamingTimers.delete(element);\n if (element.isConnected) {\n // Read latest text content after streaming settles\n const latestText = element.textContent?.trim() || text;\n this.registerForViewport(element, latestText);\n }\n }, this.options.debounceMs);\n\n this.streamingTimers.set(element, timer);\n }\n\n private registerForViewport(element: HTMLElement, text: string): void {\n if (!element.isConnected) return;\n\n if (!this.intersectionObserver) {\n // Fallback if IntersectionObserver is unsupported: enqueue immediately\n this.enqueueBatch(element, text);\n return;\n }\n\n element.dataset.tidyPendingText = text;\n this.intersectionObserver.observe(element);\n }\n\n private enqueueBatch(element: HTMLElement, text: string): void {\n this.pendingQueue.push({ element, text });\n if (this.batchFlushTimer === null && typeof window !== 'undefined') {\n this.batchFlushTimer = window.setTimeout(() => {\n this.batchFlushTimer = null;\n this.flushQueue();\n }, 50);\n }\n }\n\n private flushQueue(): void {\n if (this.pendingQueue.length === 0) return;\n const batch = [...this.pendingQueue];\n this.pendingQueue = [];\n this.options.onVisibleBatch(batch);\n }\n\n unobserve(element: HTMLElement): void {\n const timer = this.streamingTimers.get(element);\n if (timer !== undefined) {\n clearTimeout(timer);\n this.streamingTimers.delete(element);\n }\n delete element.dataset.tidyPendingText;\n if (this.intersectionObserver) {\n this.intersectionObserver.unobserve(element);\n }\n }\n\n disconnect(): void {\n if (this.batchFlushTimer !== null) {\n clearTimeout(this.batchFlushTimer);\n this.batchFlushTimer = null;\n }\n // Drop every pending streaming debounce: after a disable none of them may\n // fire registerForViewport() (the Map is iterable, so this is exhaustive).\n for (const timer of this.streamingTimers.values()) {\n clearTimeout(timer);\n }\n this.streamingTimers.clear();\n this.pendingQueue = [];\n if (this.intersectionObserver) {\n this.intersectionObserver.disconnect();\n this.initIntersectionObserver();\n }\n }\n}\n", "import { clientCache } from './client-cache.ts';\nimport { requestTranslateBatch } from './api.ts';\nimport { NonDestructiveTranslationMount } from './mount.ts';\nimport { StreamDebounceViewportObserver } from './viewport-observer.ts';\n\ntype TranslateTask = {\n element: HTMLElement;\n text: string;\n};\n\nclass LazyTranslationQueue {\n private enabled = true;\n private viewportObserver: StreamDebounceViewportObserver;\n\n constructor() {\n this.viewportObserver = new StreamDebounceViewportObserver({\n rootMargin: '150px 0px',\n debounceMs: 400,\n onVisibleBatch: (items) => this.handleVisibleBatch(items),\n });\n }\n\n setEnabled(enabled: boolean): void {\n this.enabled = enabled;\n if (!enabled) {\n this.viewportObserver.disconnect();\n }\n }\n\n observe(element: HTMLElement, text: string, immediate = false): void {\n if (!this.enabled || !element.isConnected) return;\n\n // 1. If cached, apply immediately non-destructively\n const cached = clientCache.get(text);\n if (cached) {\n this.applyTranslation(element, cached, text);\n return;\n }\n\n // 2. Delegate to streaming-debounced viewport observer\n this.viewportObserver.observeWithDebounce(element, text, immediate);\n }\n\n private async handleVisibleBatch(items: TranslateTask[]): Promise<void> {\n if (!this.enabled || items.length === 0) return;\n\n // Document order: translations appear in reading order (top-to-bottom),\n // never out-of-order even when IntersectionObserver fires entries randomly.\n const sorted = [...items].sort((a, b) => {\n if (a.element === b.element) return 0;\n const pos = a.element.compareDocumentPosition(b.element);\n return pos & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;\n });\n\n // Group elements by text to deduplicate API requests\n const textMap = new Map<string, HTMLElement[]>();\n for (const item of sorted) {\n if (!item.element.isConnected) continue;\n\n const cached = clientCache.get(item.text);\n if (cached) {\n this.applyTranslation(item.element, cached, item.text);\n continue;\n }\n\n const list = textMap.get(item.text) || [];\n list.push(item.element);\n textMap.set(item.text, list);\n }\n\n const uniqueTexts = Array.from(textMap.keys());\n if (uniqueTexts.length === 0) return;\n\n const results = await requestTranslateBatch(uniqueTexts);\n\n for (const res of results) {\n if (\n res.translated &&\n res.translated.trim() &&\n res.channel !== 'fallback' &&\n res.channel !== 'fallback-client' &&\n res.translated.trim() !== res.original.trim()\n ) {\n clientCache.set(res.original, res.translated);\n const entries = textMap.get(res.original) || [];\n for (const entry of entries) {\n if (entry.isConnected && this.enabled) {\n this.applyTranslation(entry, res.translated, res.original);\n }\n }\n } else if (res.channel === 'fallback' || res.channel === 'fallback-client') {\n console.debug(`[dsh-chat-translate] \u7FFB\u8BD1\u672A\u6210\u529F (\u964D\u7EA7\u4FDD\u7559\u539F\u6587): \"${res.original.slice(0, 40)}\"`);\n }\n }\n }\n\n private applyTranslation(element: HTMLElement, translated: string, original: string): void {\n if (!element.isConnected || !this.enabled) return;\n NonDestructiveTranslationMount.mount(element, translated, {\n originalText: original,\n });\n }\n\n disconnect(): void {\n this.viewportObserver.disconnect();\n }\n}\n\nexport const lazyQueue = new LazyTranslationQueue();", "import { clientCache } from './client-cache.ts';\nimport { lazyQueue } from './lazy.ts';\nimport { NonDestructiveTranslationMount } from './mount.ts';\n\n// Case-insensitive match: DSH builds vary the summary class casing across\n// versions \u2014 lowercase \"summary\" for tool-call rows (MISisG_summary /\n// _48RFeq_summary) vs camelCase \"thinkSummary\" for the Think card row.\n// Attribute values are matched case-sensitively by default in HTML, which is\n// why the old lowercase-only selector silently missed think summaries.\nconst TOOL_TITLE_SELECTOR = '[class*=\"summary\" i]';\n\n/**\n * Current-session scroll container (the conversation layout re-renders this\n * whole subtree when the active session changes), falling back to the chat\n * flow. Both are emitted by the DSH web UI.\n */\nconst SESSION_ROOT_SELECTOR = '[data-conversation-scroll], [data-chat-flow]';\n\n/** How often (ms) we re-check that the observed root is still the live one. */\nconst ROOT_CHECK_INTERVAL_MS = 3000;\n\nfunction isToolSummarySpan(span: HTMLElement): boolean {\n if (!span || span.nodeType !== 1) return false;\n if (span.hasAttribute('aria-hidden')) return false;\n\n // Never match parent rows or containers that contain title, leading icon, chevron or nested summary\n if (span.querySelector?.('[class*=\"title\"], [class*=\"leading\"], [class*=\"chevron\"], [class*=\"sep\"], [class*=\"summary\" i]')) {\n return false;\n }\n\n const cls = span.className || '';\n if (/title|leading|icon|badge|chevron|separator|sep\\b|row\\b|root\\b|card\\b/i.test(cls)) return false;\n\n // Never translate fold toggle (\"\u5C55\u5F00\"/\"\u6536\u8D77\") or Think card's own title badge (\"Think\"/\"\u601D\u8003\")\n const rawToggle = (span.textContent || '').trim();\n if (\n rawToggle.length <= 12 &&\n /^(\u5C55\u5F00|\u6536\u8D77|\u5C55\u5F00\u5168\u90E8|\u6536\u8D77\u5168\u90E8|Expand|Collapse|Show more|Show less|Think|\u601D\u8003)$/i.test(rawToggle)\n ) {\n return false;\n }\n if (rawToggle === 'Think' || rawToggle === '\u601D\u8003') return false;\n if (\n span.closest('button, [role=\"button\"]') &&\n rawToggle.length <= 12 &&\n /\u5C55\u5F00|\u6536\u8D77|Expand|Collapse|Think|\u601D\u8003/i.test(rawToggle)\n ) {\n return false;\n }\n\n // Must belong to tool call or think block card\n if (\n span.closest(\n '[data-chat-call-id], [data-slot=\"tool.call.toolview\"], [data-sample], [data-variant], [data-tool], [data-disclosure-row]'\n )\n ) {\n return true;\n }\n return false;\n}\n\n/**\n * DSH's Think card (ReasoningRow) carries `data-state=\"running\"` on its root\n * while the reasoning block is still streaming and flips it to `\"ok\"` once it\n * has finished. While running, the collapsed summary follows the live tail of\n * the reasoning text (`.summary[data-follow-end]`), so translating it would\n * capture an intermediate line that is replaced by the stable first-line\n * summary when the block ends. Defer instead: the observed `data-state`\n * mutation re-scans this span, at which point the final summary is processed.\n */\nfunction thinkBlockStillRunning(span: HTMLElement): boolean {\n const thinkRoot = span.closest<HTMLElement>('[data-variant=\"think\"]');\n return !!thinkRoot && thinkRoot.dataset.state === 'running';\n}\n\nexport class ChatTranslateObserver {\n private observer: MutationObserver | null = null;\n private rootElement: HTMLElement | null = null;\n private rootCheckTimer: number | null = null;\n private isEnabled = true;\n\n constructor() {\n this.handleMutations = this.handleMutations.bind(this);\n }\n\n setEnabled(enabled: boolean): void {\n this.isEnabled = enabled;\n if (enabled) {\n lazyQueue.setEnabled(true);\n this.start();\n } else {\n this.restoreOriginals();\n this.disconnect();\n lazyQueue.setEnabled(false);\n }\n }\n\n private restoreOriginals(): void {\n const scope = this.rootElement ?? document;\n const spans = scope.querySelectorAll<HTMLElement>('[data-tidy-translated=\"true\"]');\n for (const span of spans) {\n NonDestructiveTranslationMount.unmount(span);\n }\n }\n\n /**\n * The active session's scroll container. Only the currently-viewed session\n * is translated; switching sessions replaces this subtree and the observer\n * naturally follows the new content.\n */\n private isVisible(el: HTMLElement): boolean {\n if (el.hasAttribute('hidden')) return false;\n if (el.style.display === 'none') return false;\n try {\n return el.getClientRects().length > 0;\n } catch {\n return true;\n }\n }\n\n private findRoot(documentRef: Document): HTMLElement {\n if (!documentRef.body) {\n return documentRef.documentElement;\n }\n const candidates = documentRef.querySelectorAll<HTMLElement>(SESSION_ROOT_SELECTOR);\n for (const el of candidates) {\n if (this.isVisible(el)) {\n return el;\n }\n }\n return documentRef.body;\n }\n\n start(documentRef: Document = document): () => void {\n if (!this.isEnabled || typeof window === 'undefined') {\n return () => {};\n }\n\n const findAndObserveRoot = () => {\n const root = this.findRoot(documentRef);\n\n this.rootElement = root;\n\n // 1. Initial scan of existing tool elements\n this.scanContainer(root);\n\n // 2. Setup MutationObserver\n if (!this.observer) {\n this.observer = new MutationObserver(this.handleMutations);\n this.observer.observe(root, {\n childList: true,\n subtree: true,\n attributes: true,\n characterData: true,\n attributeFilter: ['data-state', 'data-tool', 'data-variant', 'data-sample', 'aria-expanded'],\n });\n }\n };\n\n findAndObserveRoot();\n\n // Defense: if the observed root is detached or hidden (e.g. the user\n // switched to another view), re-probe for the live session container.\n this.scheduleRootCheck();\n\n return () => {\n this.disconnect();\n };\n }\n\n private scheduleRootCheck(): void {\n if (this.rootCheckTimer !== null || typeof window === 'undefined') return;\n this.rootCheckTimer = window.setInterval(() => {\n if (this.rootElement && this.rootElement.isConnected && this.isVisible(this.rootElement)) {\n return;\n }\n this.restart();\n }, ROOT_CHECK_INTERVAL_MS);\n }\n\n private restart(): void {\n if (typeof window === 'undefined') return;\n const wasEnabled = this.isEnabled;\n this.disconnect();\n if (wasEnabled) {\n this.start();\n }\n }\n\n private handleMutations(mutations: MutationRecord[]): void {\n if (!this.isEnabled) return;\n\n for (const mutation of mutations) {\n if (mutation.type === 'childList') {\n for (let i = 0; i < mutation.addedNodes.length; i++) {\n const node = mutation.addedNodes[i];\n if (node instanceof HTMLElement) {\n if (\n node.classList?.contains('dsh-tidy-translated-block') ||\n node.classList?.contains('dsh-tidy-original-hidden') ||\n node.classList?.contains('dsh-tidy-original-shown')\n ) {\n continue;\n }\n this.scanNode(node);\n }\n }\n } else if (mutation.type === 'attributes') {\n const target = mutation.target;\n if (target instanceof HTMLElement) {\n if (\n target.classList?.contains('dsh-tidy-translated-block') ||\n target.classList?.contains('dsh-tidy-original-hidden') ||\n target.classList?.contains('dsh-tidy-original-shown')\n ) {\n continue;\n }\n this.scanNode(target);\n }\n } else if (mutation.type === 'characterData') {\n const parent = mutation.target.parentElement;\n if (parent instanceof HTMLElement) {\n if (\n parent.classList?.contains('dsh-tidy-translated-block') ||\n parent.classList?.contains('dsh-tidy-original-hidden') ||\n parent.classList?.contains('dsh-tidy-original-shown')\n ) {\n continue;\n }\n this.scanNode(parent);\n }\n }\n }\n }\n\n private scanContainer(container: HTMLElement): void {\n const spans = container.querySelectorAll<HTMLElement>(TOOL_TITLE_SELECTOR);\n spans.forEach((span) => {\n if (isToolSummarySpan(span)) {\n this.processSpan(span);\n }\n });\n }\n\n private scanNode(node: HTMLElement): void {\n if (node.matches?.(TOOL_TITLE_SELECTOR) && isToolSummarySpan(node)) {\n this.processSpan(node);\n }\n\n const spans = node.querySelectorAll<HTMLElement>(TOOL_TITLE_SELECTOR);\n spans.forEach((span) => {\n if (isToolSummarySpan(span)) {\n this.processSpan(span);\n }\n });\n }\n\n private processSpan(span: HTMLElement): void {\n // Think summaries are translated only after the reasoning block has fully\n // finished streaming \u2014 never while `data-state=\"running\"` (the summary is\n // then a live tail line that would be replaced moments later). The\n // transition to \"ok\" triggers a re-scan via the observed data-state\n // attribute, which is when this span becomes processable.\n if (thinkBlockStillRunning(span)) return;\n\n if (NonDestructiveTranslationMount.isMounted(span)) {\n const original = NonDestructiveTranslationMount.getOriginal(span);\n if (original) {\n const cached = clientCache.get(original);\n if (cached) return;\n }\n return;\n }\n\n const text = NonDestructiveTranslationMount.extractVisibleText(span);\n if (!text) return;\n\n // Check fast client cache\n const cached = clientCache.get(text);\n if (cached) {\n NonDestructiveTranslationMount.mount(span, cached, {\n originalText: text,\n });\n return;\n }\n\n // Send to viewport lazy queue\n lazyQueue.observe(span, text);\n }\n\n disconnect(): void {\n if (this.rootCheckTimer !== null) {\n clearInterval(this.rootCheckTimer);\n this.rootCheckTimer = null;\n }\n if (this.observer) {\n this.observer.disconnect();\n this.observer = null;\n }\n lazyQueue.disconnect();\n this.rootElement = null;\n }\n}\n\nexport const chatTranslateObserver = new ChatTranslateObserver();\n", "import { useState, useEffect } from 'react';\nimport * as React from 'react';\nimport { settingsStore, SETTINGS_NAMESPACE, type ClientSettingsState } from './store.ts';\nimport { SETTINGS_CSS } from './styles.ts';\nlet stylesInjected = false;\nfunction ensureSettingsStyles(): void {\n if (stylesInjected || typeof document === 'undefined') return;\n const el = document.createElement('style');\n el.dataset.tidySettings = 'true';\n el.textContent = SETTINGS_CSS;\n document.head.appendChild(el);\n stylesInjected = true;\n}\n\nfunction Switch(props: { checked: boolean; onChange: () => void; label: string }): React.ReactElement {\n return (\n <button\n type=\"button\"\n className=\"dsh-tidy-switch\"\n role=\"switch\"\n aria-checked={props.checked}\n onClick={props.onChange}\n aria-label={props.label}\n />\n );\n}\n\nexport function TidySettingsPanel(): React.ReactElement {\n ensureSettingsStyles();\n\n const [state, setState] = useState<ClientSettingsState>(() => settingsStore.getState());\n const [testing, setTesting] = useState<{ channel: string; running: boolean; ok?: boolean; message?: string } | null>(null);\n const [apiKeyInput, setApiKeyInput] = useState('');\n const [savingKey, setSavingKey] = useState(false);\n const [keyMsg, setKeyMsg] = useState<{ ok: boolean; text: string } | null>(null);\n\n useEffect(() => {\n return settingsStore.subscribe(() => {\n setState(settingsStore.getState());\n });\n }, []);\n\n const runTest = async (channel: string): Promise<void> => {\n setTesting({ channel, running: true });\n const res = await settingsStore.testChannel(channel);\n const message = res.ok\n ? `\u8FDE\u63A5\u6B63\u5E38\uFF0C\u5EF6\u8FDF ${res.latencyMs}ms`\n : `\u5931\u8D25\uFF1A${res.error || '\u672A\u77E5\u9519\u8BEF'}`;\n setTesting({ channel, running: false, ok: res.ok, message });\n };\n\n const handleSaveKey = async (): Promise<void> => {\n setSavingKey(true);\n setKeyMsg(null);\n const res = await settingsStore.saveApiKey(apiKeyInput);\n setSavingKey(false);\n if (res.ok) {\n const cleared = !apiKeyInput.trim();\n setKeyMsg({\n ok: true,\n text: cleared\n ? '\u5DF2\u6E05\u9664 API Key\uFF08AI \u901A\u9053\u5C06\u4E0D\u53EF\u7528\uFF0CBing \u515C\u5E95\uFF09'\n : '\u5DF2\u4FDD\u5B58\u5230 ~/.dsh/.credentials.yaml\uFF0C\u7ACB\u5373\u751F\u6548',\n });\n setApiKeyInput('');\n } else {\n setKeyMsg({ ok: false, text: `\u4FDD\u5B58\u5931\u8D25\uFF1A${res.error || '\u672A\u77E5\u9519\u8BEF'}` });\n }\n };\n\n return (\n <div className=\"dsh-tidy-settings\">\n {/* 1. \u603B\u5F00\u5173 */}\n <div className=\"dsh-tidy-card\">\n <div className=\"dsh-tidy-title\">\n <span>\u5DE5\u5177\u8C03\u7528\u4E0E\u601D\u8003\u6458\u8981\u7FFB\u8BD1</span>\n <Switch\n checked={state.enabled}\n onChange={() => settingsStore.update({ enabled: !state.enabled })}\n label=\"\u542F\u7528\u5DE5\u5177\u8C03\u7528\u4E0E\u6458\u8981\u7FFB\u8BD1\"\n />\n </div>\n <div className=\"dsh-tidy-desc\">\n \u81EA\u52A8\u5C06\u5F53\u524D\u4F1A\u8BDD\u4E2D\u5DE5\u5177\u8C03\u7528\u6807\u9898\u4E0E\u601D\u8003\u6298\u53E0\u6458\u8981\uFF08\u5982 <code>Locate DSH home directory structure</code>\uFF09\u7FFB\u8BD1\u4E3A\u4E2D\u6587\uFF0C\n \u70B9\u51FB\u8BD1\u6587\u53EF\u539F\u5730\u5207\u6362\u539F\u6587/\u8BD1\u6587\u3002\u601D\u8003\u6298\u53E0\u6458\u8981\u4EC5\u5728\u601D\u8003\u5B8C\u5168\u7ED3\u675F\u540E\u624D\u7FFB\u8BD1\uFF08\u4E0D\u7FFB\u8BD1\u6D41\u5F0F\u4E2D\u95F4\u6001\uFF09\u3002\n \u4EC5\u4F5C\u7528\u4E8E\u5F53\u524D\u67E5\u770B\u7684\u4F1A\u8BDD\uFF0C\u5BF9\u8BDD\u6B63\u6587\u6C38\u4E0D\u7FFB\u8BD1\u3002\n </div>\n </div>\n\n {state.enabled && (\n <>\n {/* 2. AI \u901A\u9053 */}\n <div className=\"dsh-tidy-card\">\n <div className=\"dsh-tidy-title\">\n <span>AI \u7FFB\u8BD1\uFF08OpenAI \u517C\u5BB9\u534F\u8BAE\uFF09</span>\n <Switch\n checked={state.aiEnabled}\n onChange={() => settingsStore.update({ aiEnabled: !state.aiEnabled })}\n label=\"\u542F\u7528 AI \u7FFB\u8BD1\u901A\u9053\"\n />\n </div>\n <div className=\"dsh-tidy-desc\">\n <span className={`dsh-tidy-badge ${state.aiConfigured ? 'dsh-tidy-badge-ok' : 'dsh-tidy-badge-warn'}`}>\n {state.aiConfigured ? '\u5DF2\u914D\u7F6E' : '\u672A\u914D\u7F6E'}\n </span>{' '}\n \u672A\u914D\u7F6E\u65F6 AI \u901A\u9053\u81EA\u52A8\u8DF3\u8FC7\uFF0C\u7531 Bing \u515C\u5E95\u3002\n </div>\n\n {state.aiEnabled && (\n <>\n <div className=\"dsh-tidy-row\">\n <div className=\"dsh-tidy-row-info\">\n <div className=\"dsh-tidy-row-title\">API Key</div>\n <div className=\"dsh-tidy-row-desc\">\n \u4FDD\u5B58\u81F3 <code>~/.dsh/.credentials.yaml</code> \u7684 <code>TRANSLATE_API_KEY</code>\uFF0C\u4FDD\u5B58\u540E\u7ACB\u5373\u751F\u6548\uFF1B\u7559\u7A7A\u4FDD\u5B58 = \u6E05\u9664\n </div>\n </div>\n <div className=\"dsh-tidy-input-row\">\n <input\n type=\"password\"\n className=\"dsh-tidy-input\"\n placeholder={state.aiConfigured ? '\u5DF2\u914D\u7F6E\uFF08\u5982\u9700\u66F4\u6362\u8BF7\u76F4\u63A5\u8F93\u5165\uFF09' : 'sk-...'}\n value={apiKeyInput}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) => setApiKeyInput(e.target.value)}\n style={{ width: '260px' }}\n aria-label=\"API Key\"\n />\n <button type=\"button\" className=\"dsh-tidy-btn\" disabled={savingKey} onClick={handleSaveKey}>\n {savingKey ? '\u4FDD\u5B58\u4E2D\u2026' : '\u4FDD\u5B58'}\n </button>\n </div>\n </div>\n {keyMsg && (\n <div className={`dsh-tidy-test-result ${keyMsg.ok ? 'ok' : 'fail'}`}>{keyMsg.text}</div>\n )}\n\n <div className=\"dsh-tidy-row\">\n <div className=\"dsh-tidy-row-info\">\n <div className=\"dsh-tidy-row-title\">Base URL</div>\n <div className=\"dsh-tidy-row-desc\">\n \u4EFB\u610F OpenAI \u517C\u5BB9\u670D\u52A1\u7AEF\u70B9\uFF0C\u5982 <code>https://api.openai.com/v1</code>\u3001{' '}\n <code>https://api.deepseek.com/v1</code>\n </div>\n </div>\n <input\n type=\"text\"\n className=\"dsh-tidy-input\"\n placeholder=\"https://api.openai.com/v1\"\n value={state.baseUrl}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) =>\n settingsStore.update({ baseUrl: e.target.value })\n }\n style={{ width: '260px' }}\n aria-label=\"AI Base URL\"\n />\n </div>\n <div className=\"dsh-tidy-row\">\n <div className=\"dsh-tidy-row-info\">\n <div className=\"dsh-tidy-row-title\">\u6A21\u578B</div>\n <div className=\"dsh-tidy-row-desc\">\n \u5982 <code>gpt-4o-mini</code>\u3001<code>deepseek-chat</code>\uFF1B\u7559\u7A7A\u89C6\u4E3A\u672A\u914D\u7F6E\n </div>\n </div>\n <input\n type=\"text\"\n className=\"dsh-tidy-input\"\n placeholder=\"gpt-4o-mini\"\n value={state.model}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) =>\n settingsStore.update({ model: e.target.value })\n }\n style={{ width: '260px' }}\n aria-label=\"AI \u6A21\u578B\"\n />\n </div>\n <div className=\"dsh-tidy-row\">\n <button\n type=\"button\"\n className=\"dsh-tidy-btn\"\n disabled={testing?.running}\n onClick={() => runTest('openai')}\n >\n {testing?.running ? '\u6D4B\u8BD5\u4E2D\u2026' : '\u6D4B\u8BD5 AI \u901A\u9053'}\n </button>\n {testing?.channel === 'openai' && !testing.running && (\n <span className={`dsh-tidy-test-result ${testing.ok ? 'ok' : 'fail'}`}>{testing.message}</span>\n )}\n </div>\n </>\n )}\n </div>\n\n {/* 3. Bing \u901A\u9053 */}\n <div className=\"dsh-tidy-card\">\n <div className=\"dsh-tidy-title\">\n <span>Bing \u7F51\u9875\u7FFB\u8BD1\uFF08\u514D Key \u515C\u5E95\uFF09</span>\n <Switch\n checked={state.bingEnabled}\n onChange={() => settingsStore.update({ bingEnabled: !state.bingEnabled })}\n label=\"\u542F\u7528 Bing \u7FFB\u8BD1\u901A\u9053\"\n />\n </div>\n <div className=\"dsh-tidy-desc\">\n \u5185\u7F6E\u514D Key \u7FFB\u8BD1\u901A\u9053\uFF0C\u65E0\u9700\u4EFB\u4F55\u914D\u7F6E\u3002AI \u672A\u914D\u7F6E\u6216\u8BF7\u6C42\u5931\u8D25\u65F6\u81EA\u52A8\u515C\u5E95\uFF1BAI \u4E0E Bing \u540C\u65F6\u5173\u95ED\u5219\u4E0D\u7FFB\u8BD1\u3002\n </div>\n </div>\n\n {/* 4. \u884C\u4E3A\u8BF4\u660E */}\n <div className=\"dsh-tidy-card\">\n <div className=\"dsh-tidy-row-title\">\u901A\u9053\u884C\u4E3A</div>\n <ul className=\"dsh-tidy-desc dsh-tidy-behavior-list\">\n <li>AI \u5F00\u542F\u4E14\u5DF2\u914D\u7F6E \u2192 AI \u4F18\u5148\u7FFB\u8BD1\uFF0C\u5931\u8D25\u81EA\u52A8\u964D\u7EA7 Bing</li>\n <li>AI \u5F00\u542F\u4F46\u672A\u914D\u7F6E + Bing \u5F00\u542F \u2192 \u7531 Bing \u7FFB\u8BD1</li>\n <li>AI \u5F00\u542F\u4F46\u672A\u914D\u7F6E + Bing \u5173\u95ED \u2192 \u4E0D\u7FFB\u8BD1</li>\n <li>AI \u5173\u95ED + Bing \u5F00\u542F \u2192 \u76F4\u63A5\u4F7F\u7528 Bing</li>\n <li>AI \u5173\u95ED + Bing \u5173\u95ED \u2192 \u4E0D\u7FFB\u8BD1</li>\n </ul>\n </div>\n\n {/* 5. \u5E76\u53D1\u63A7\u5236 */}\n <div className=\"dsh-tidy-card\">\n <div className=\"dsh-tidy-row\">\n <div className=\"dsh-tidy-row-info\">\n <div className=\"dsh-tidy-row-title\">\u6700\u5927\u7FFB\u8BD1\u5E76\u53D1\u6570</div>\n <div className=\"dsh-tidy-row-desc\">\n \u63A7\u5236\u89C6\u53E3\u6EDA\u52A8\u4E0E\u591A\u5DE5\u5177\u5361\u7247\u65F6\u7684\u6700\u5927\u5E76\u884C\u8BF7\u6C42\u6570\uFF08\u8303\u56F4 1-100\uFF0C\u63A8\u8350 3\uFF09\u3002\n </div>\n </div>\n <input\n type=\"number\"\n className=\"dsh-tidy-input\"\n min={1}\n max={100}\n step={1}\n value={state.concurrency}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) => {\n const val = parseInt(e.target.value, 10);\n if (!Number.isFinite(val)) return;\n settingsStore.update({ concurrency: Math.min(Math.max(val, 1), 100) });\n }}\n style={{ width: '88px' }}\n aria-label=\"\u6700\u5927\u7FFB\u8BD1\u5E76\u53D1\u6570\"\n />\n </div>\n </div>\n </>\n )}\n </div>\n );\n}\n\nexport function setupSettingsUi(ctx: any): void {\n if (typeof window === 'undefined') return;\n\n // Ride DSH's own settings surface: the `settingsScope` service mirrors the\n // host document (per-namespace describe) and the `credentials` Remote API\n // writes the API key. No custom config HTTP endpoint since 1.2.\n try {\n // Declared via inject: 'settingsScope' and 'remote.credentials' (plus the\n // 'remote' root). Fall back to optional lookup so a degraded environment\n // degrades to an in-memory store instead of failing loudly.\n const settingsScope = ctx?.settingsScope || (ctx?.get ? ctx.get('settingsScope') : null);\n const remoteCredentials =\n ctx?.remote?.credentials || (ctx?.get ? ctx.get('remote.credentials') : null);\n if (settingsScope && typeof settingsScope.bind === 'function') {\n const scope = settingsScope.bind({ namespace: SETTINGS_NAMESPACE });\n settingsStore.attach(scope, remoteCredentials ?? null);\n }\n } catch (err) {\n console.warn('[dsh-chat-translate] Failed to bind settings scope:', err);\n }\n\n try {\n const slots = ctx?.slots || (ctx?.get ? ctx.get('slots') : null);\n if (!slots || typeof slots.inject !== 'function') return;\n\n slots.inject('settings.section', () => {\n return slots.register(\n {\n name: 'settings.section',\n id: 'dsh-chat-translate',\n // \u7EA6\u5B9A\uFF1A\u81EA\u6709\u63D2\u4EF6\u8BBE\u7F6E\u9879 order \u4ECE 110 \u8D77\u6B65\u8FDB 10\uFF08\u539F\u751F\u6700\u5927 100=\u684C\u9762\u8BBE\u7F6E\uFF09\uFF0C\u4FDD\u8BC1\u6392\u5728\u6240\u6709\u539F\u751F\u9879\u4E4B\u4E0B\n order: 110,\n label: () => '\u804A\u5929\u7FFB\u8BD1',\n },\n TidySettingsPanel\n );\n });\n } catch (err) {\n console.warn('[dsh-chat-translate] Failed to inject settings section:', err);\n }\n}\n", "import { describeError } from '../../describe-error.ts';\nimport { chatTranslateObserver } from '../translate/observer.ts';\nimport { testServerChannel } from '../translate/api.ts';\n\nexport interface ClientSettingsState {\n enabled: boolean;\n concurrency: number;\n aiEnabled: boolean;\n bingEnabled: boolean;\n baseUrl: string;\n model: string;\n aiConfigured: boolean;\n}\n\n/** Settings namespace + credentials ref, mirroring the host constants. */\nexport const SETTINGS_NAMESPACE = 'dsh-chat-translate';\nexport const TRANSLATE_API_KEY_REF = 'TRANSLATE_API_KEY';\n\n/**\n * Minimal structural shapes of the DSH browser services this store rides on:\n * `settingsScope` (from @deepseek-ai/dsh-client-ui-settings) and the\n * `credentials` Remote namespace. Keeping them structural keeps this bundle\n * free of host-service imports and lets tests inject fakes.\n */\nexport interface SettingsScopeLike {\n getSnapshot(): {\n status: string;\n value?: Record<string, unknown>;\n writable: boolean;\n };\n subscribe(listener: () => void): () => void;\n set(field: string, value: unknown): Promise<unknown>;\n unset(field: string): Promise<unknown>;\n}\n\n/** Shape of every DSH client Remote call: {ok, value} / {ok, error} wrapper. */\nexport type RemoteResult<T> =\n | { ok: true; value: T }\n | { ok: false; error: { code?: string; message: string; details?: unknown } };\n\nexport interface CredentialsRemoteLike {\n describe(\n refs: string[]\n ): Promise<RemoteResult<Record<string, { configured: boolean; source?: string; writable: boolean }>>>;\n set(ref: string, value: string): Promise<RemoteResult<void>>;\n unset(ref: string): Promise<RemoteResult<void>>;\n}\n\nconst DEFAULT_STATE: ClientSettingsState = {\n enabled: true,\n concurrency: 3,\n aiEnabled: true,\n bingEnabled: true,\n baseUrl: '',\n model: '',\n aiConfigured: false,\n};\n\n/**\n * Client settings store backed by the DSH settings namespace.\n *\n * Since 1.2 there is no localStorage overlay and no custom config HTTP\n * endpoint: the store derives from the bound `settingsScope` (which mirrors\n * the host document and folds every write answer back), and writes go\n * through `scope.set` \u2014 serialized, revision-checked, persisted by DSH.\n * Without a bound scope (e.g. non-loopback pages, unit tests) it degrades to\n * an in-memory store with the same semantics DSH itself uses for memory\n * persistence.\n */\nclass SettingsStore {\n private state: ClientSettingsState = { ...DEFAULT_STATE };\n private listeners = new Set<() => void>();\n private scope: SettingsScopeLike | null = null;\n private credentials: CredentialsRemoteLike | null = null;\n private unsubscribeScope: (() => void) | null = null;\n private keyConfigured = false;\n private writeTimer: number | null = null;\n private pendingFields = new Set<string>();\n\n /**\n * Bind the DSH services. Called once from the settings UI setup; re-binding\n * (e.g. after a reconnect) detaches the previous subscription first.\n */\n attach(scope: SettingsScopeLike | null, credentials: CredentialsRemoteLike | null): void {\n if (this.unsubscribeScope) {\n this.unsubscribeScope();\n this.unsubscribeScope = null;\n }\n this.scope = scope;\n this.credentials = credentials;\n if (scope) {\n this.unsubscribeScope = scope.subscribe(() => this.derive());\n this.derive();\n }\n void this.refreshKeyStatus();\n }\n\n getState(): ClientSettingsState {\n return { ...this.state };\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /** Map the resolved namespace value into client state and notify. */\n private derive(): void {\n if (!this.scope) return;\n const snap = this.scope.getSnapshot();\n const value = snap.value;\n if (!value || typeof value !== 'object') return;\n const next: ClientSettingsState = { ...this.state };\n if (typeof value.enabled === 'boolean') next.enabled = value.enabled;\n const c = value.concurrency;\n if (typeof c === 'number' && Number.isFinite(c)) {\n next.concurrency = Math.min(Math.max(Math.round(c), 1), 100);\n }\n if (typeof value.aiEnabled === 'boolean') next.aiEnabled = value.aiEnabled;\n if (typeof value.bingEnabled === 'boolean') next.bingEnabled = value.bingEnabled;\n if (typeof value.baseUrl === 'string') next.baseUrl = value.baseUrl;\n if (typeof value.model === 'string') next.model = value.model;\n this.applyState(next);\n }\n\n private applyState(next: ClientSettingsState): void {\n next.aiConfigured = Boolean(next.baseUrl && next.model && this.keyConfigured);\n const enabledChanged = next.enabled !== this.state.enabled;\n this.state = next;\n if (enabledChanged) {\n try {\n chatTranslateObserver.setEnabled(this.state.enabled);\n } catch {}\n }\n this.notify();\n }\n\n private notify(): void {\n for (const listener of this.listeners) {\n try {\n listener();\n } catch {}\n }\n }\n\n /** Pull the key's status-only view and re-derive aiConfigured. */\n private async refreshKeyStatus(): Promise<void> {\n if (!this.credentials) return;\n try {\n const res = await this.credentials.describe([TRANSLATE_API_KEY_REF]);\n if (!res.ok) return;\n const info = res.value[TRANSLATE_API_KEY_REF];\n const configured = Boolean(info?.configured);\n if (configured !== this.keyConfigured) {\n this.keyConfigured = configured;\n this.applyState({ ...this.state });\n }\n } catch {\n // Credentials surface unavailable \u2014 keep the last known status.\n }\n }\n\n /**\n * Optimistically apply locally, then persist each touched field through the\n * settings scope. Writes are trailing-debounced (300ms) so typing in the\n * baseUrl/model inputs collapses into a single queued mutation instead of\n * one write per keystroke \u2014 which would otherwise flash stale mirror values\n * back into the inputs between commits. A failed write makes the scope\n * reload its mirror, which re-derives this store from the host document\n * (conflict-safe recovery).\n */\n async update(partial: Partial<ClientSettingsState>): Promise<void> {\n let sanitizedConcurrency = this.state.concurrency;\n if (typeof partial.concurrency === 'number' && Number.isFinite(partial.concurrency)) {\n sanitizedConcurrency = Math.min(Math.max(Math.round(partial.concurrency), 1), 100);\n }\n const next: ClientSettingsState = {\n ...this.state,\n ...partial,\n concurrency: sanitizedConcurrency,\n };\n this.applyState(next);\n\n if (this.scope) {\n const fields = ['enabled', 'concurrency', 'aiEnabled', 'bingEnabled', 'baseUrl', 'model'] as const;\n for (const field of fields) {\n if (partial[field] !== undefined) {\n this.pendingFields.add(field);\n }\n }\n this.scheduleWrite();\n }\n }\n\n private scheduleWrite(): void {\n if (this.writeTimer !== null || typeof window === 'undefined') return;\n this.writeTimer = window.setTimeout(() => {\n this.writeTimer = null;\n void this.flushWrite();\n }, 300);\n }\n\n private async flushWrite(): Promise<void> {\n if (!this.scope) return;\n const fields = [...this.pendingFields];\n this.pendingFields.clear();\n const writes: Promise<unknown>[] = [];\n for (const field of fields) {\n writes.push(this.scope.set(field, this.state[field as keyof ClientSettingsState]));\n }\n await Promise.all(writes).catch(() => {});\n }\n\n async testChannel(channel: string): Promise<{ ok: boolean; latencyMs: number; error?: string }> {\n return testServerChannel(channel);\n }\n\n /** Write (or clear) the API key through the credentials Remote API. */\n async saveApiKey(apiKey: string): Promise<{ ok: boolean; error?: string }> {\n if (!this.credentials) {\n return { ok: false, error: '\u51ED\u636E\u670D\u52A1\u4E0D\u53EF\u7528\uFF08\u975E\u56DE\u73AF\u9875\u9762\uFF09' };\n }\n try {\n const key = apiKey.trim();\n // Remote calls resolve with {ok:false} instead of rejecting on refusal\n // (e.g. env-shadowed refs), so the .ok check decides the outcome.\n const res = key\n ? await this.credentials.set(TRANSLATE_API_KEY_REF, key)\n : await this.credentials.unset(TRANSLATE_API_KEY_REF);\n if (!res.ok) {\n return { ok: false, error: res.error?.message || '\u4FDD\u5B58\u5931\u8D25' };\n }\n await this.refreshKeyStatus();\n return { ok: true };\n } catch (err: any) {\n return { ok: false, error: describeError(err) };\n }\n }\n\n dispose(): void {\n if (this.writeTimer !== null && typeof window !== 'undefined') {\n clearTimeout(this.writeTimer);\n this.writeTimer = null;\n }\n this.pendingFields.clear();\n if (this.unsubscribeScope) {\n this.unsubscribeScope();\n this.unsubscribeScope = null;\n }\n this.scope = null;\n this.credentials = null;\n this.listeners.clear();\n }\n}\n\nexport const settingsStore = new SettingsStore();\n", "export const SETTINGS_CSS = String.raw`\n.dsh-tidy-settings {\n display: flex;\n flex-direction: column;\n gap: 16px;\n max-width: 580px;\n padding-bottom: 32px;\n color: var(--dsw-alias-label-primary, inherit);\n font-family: inherit;\n}\n\n.dsh-tidy-card {\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.2));\n border-radius: 12px;\n padding: 16px 18px;\n background: var(--dsw-alias-bg-card, rgba(128, 128, 128, 0.05));\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.dsh-tidy-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary, inherit);\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n\n.dsh-tidy-desc {\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary, rgba(128, 128, 128, 0.8));\n}\n\n.dsh-tidy-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 14px;\n padding: 8px 0;\n}\n\n.dsh-tidy-row + .dsh-tidy-row {\n border-top: 1px solid var(--dsw-alias-border-l3, rgba(128, 128, 128, 0.1));\n}\n\n.dsh-tidy-row-info {\n flex: 1;\n min-width: 0;\n}\n\n.dsh-tidy-row-title {\n font-size: 13px;\n font-weight: 500;\n}\n\n.dsh-tidy-row-desc {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary, rgba(128, 128, 128, 0.7));\n margin-top: 2px;\n line-height: 1.4;\n}\n\n/* Switch */\n.dsh-tidy-switch {\n position: relative;\n width: 38px;\n height: 22px;\n flex: none;\n cursor: pointer;\n border-radius: 999px;\n border: none;\n background: rgba(128, 128, 128, 0.3);\n transition: background 0.15s ease;\n padding: 0;\n outline: none;\n}\n\n.dsh-tidy-switch[aria-checked=\"true\"] {\n background: #3b82f6;\n}\n\n.dsh-tidy-switch::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: #ffffff;\n transition: transform 0.15s ease;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n}\n\n.dsh-tidy-switch[aria-checked=\"true\"]::after {\n transform: translateX(16px);\n}\n\n/* Inputs */\n.dsh-tidy-input-group {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.dsh-tidy-label {\n font-size: 12px;\n font-weight: 500;\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n\n.dsh-tidy-input-row {\n display: flex;\n gap: 8px;\n}\n\n.dsh-tidy-input {\n flex: 1;\n min-width: 0;\n padding: 8px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.25));\n background: var(--dsw-alias-bg-input, rgba(0, 0, 0, 0.05));\n color: inherit;\n font-size: 12px;\n outline: none;\n transition: border-color 0.15s;\n}\n\n.dsh-tidy-input:focus {\n border-color: #3b82f6;\n}\n\n.dsh-tidy-btn {\n padding: 6px 14px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.3));\n background: transparent;\n color: inherit;\n font-size: 12px;\n cursor: pointer;\n transition: all 0.15s ease;\n white-space: nowrap;\n}\n\n.dsh-tidy-btn:hover {\n background: rgba(128, 128, 128, 0.1);\n border-color: rgba(128, 128, 128, 0.5);\n}\n\n.dsh-tidy-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.dsh-tidy-badge {\n font-size: 10px;\n padding: 2px 6px;\n border-radius: 4px;\n font-weight: 500;\n}\n\n.dsh-tidy-badge-ok {\n background: rgba(34, 197, 94, 0.15);\n color: #22c55e;\n}\n\n.dsh-tidy-badge-none {\n background: rgba(156, 163, 175, 0.15);\n color: #9ca3af;\n}\n\n.dsh-tidy-badge-warn {\n background: rgba(245, 158, 11, 0.15);\n color: #f59e0b;\n}\n\n.dsh-tidy-test-result {\n font-size: 12px;\n margin-left: 10px;\n}\n\n.dsh-tidy-test-result.ok {\n color: #22c55e;\n}\n\n.dsh-tidy-test-result.fail {\n color: #ef4444;\n}\n\n.dsh-tidy-behavior-list {\n padding-left: 18px;\n margin: 4px 0 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n/* Priority list */\n.dsh-tidy-priority-list {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.dsh-tidy-priority-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 12px;\n border-radius: 8px;\n background: rgba(128, 128, 128, 0.06);\n border: 1px solid var(--dsw-alias-border-l3, rgba(128, 128, 128, 0.12));\n}\n\n.dsh-tidy-priority-name {\n font-size: 12px;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.dsh-tidy-order-badge {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: #3b82f6;\n color: #ffffff;\n font-size: 11px;\n font-weight: 600;\n}\n\n.dsh-tidy-btn-group {\n display: flex;\n gap: 4px;\n}\n\n.dsh-tidy-icon-btn {\n width: 26px;\n height: 26px;\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 6px;\n border: 1px solid rgba(128, 128, 128, 0.2);\n background: transparent;\n color: inherit;\n cursor: pointer;\n font-size: 12px;\n}\n\n.dsh-tidy-icon-btn:hover:not(:disabled) {\n background: rgba(128, 128, 128, 0.15);\n}\n\n.dsh-tidy-icon-btn:disabled {\n opacity: 0.3;\n cursor: default;\n}\n\n/* Slider / Select */\n.dsh-tidy-select {\n padding: 6px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.3));\n background: var(--dsw-alias-bg-input, rgba(0, 0, 0, 0.05));\n color: inherit;\n font-size: 12px;\n outline: none;\n}\n`;\n"],
|
|
5
|
-
"mappings": "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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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",
|
|
6
|
-
"names": ["cached"]
|
|
3
|
+
"sources": ["../src/client/index.ts", "../src/client/translate/client-cache.ts", "../src/describe-error.ts", "../src/client/translate/api.ts", "../src/client/translate/mount.ts", "../src/client/translate/viewport-observer.ts", "../src/client/translate/lazy.ts", "../src/client/translate/observer.ts", "../src/client/settings/ui.tsx", "../src/client/settings/store.ts", "../src/client/settings/styles.ts", "../src/client/reading-settings-page.tsx", "../src/client/locales.ts"],
|
|
4
|
+
"sourcesContent": ["import { chatTranslateObserver } from './translate/observer.ts';\nimport { setupSettingsUi } from './settings/ui.tsx';\n// Type-only, erased from the bundle: the renderer augments Context with the\n// `slots` service the shared \u300C\u9605\u8BFB\u4F53\u9A8C\u300D page shell consumes, and the settings\n// contract declares the `settings.section` slot its page component renders into.\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client';\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client';\n\n/** Client plugin name, shared with the browser bundle id. */\nexport const name = 'dsh-chat-translate';\n\n/**\n * Declared services: slots for the shared \u300C\u9605\u8BFB\u4F53\u9A8C\u300D settings page (page claim\n * plus the card registration), settingsScope for the per-namespace settings\n * mirror, and the remote + remote.credentials pair for the credentials Remote\n * namespace \u2014 the runtime withholds any service not declared here, so\n * `ctx.remote.credentials` would be undefined otherwise. The locale service is\n * optional and read through `ctx.get`, like the renderer's slots registry.\n */\nexport const inject = ['slots', 'settingsScope', 'remote', 'remote.credentials'];\n\ninterface ClientContext {\n effect(factory: () => void | (() => void), label: string): void;\n get?(serviceName: string): any;\n slots?: any;\n settingsScope?: any;\n locale?: any;\n remote?: { credentials?: any };\n}\n\n/**\n * Mount the tool-call / think-summary translation observer and the settings UI\n * card inside the shared reading-settings page.\n * @param ctx - DSH browser client context.\n */\nexport function apply(ctx: ClientContext): void {\n // 1. Mount tool title / think summary translation observer (current session only)\n ctx.effect(() => chatTranslateObserver.start(document), 'dsh-chat-translate: title translate observer');\n\n // 2. Join the shared \u300C\u9605\u8BFB\u4F53\u9A8C\u300D settings page (claim it, or register a card into it)\n ctx.effect(() => setupSettingsUi(ctx), 'dsh-chat-translate: settings section');\n}\n\nexport { chatTranslateObserver } from './translate/observer.ts';\nexport { setupSettingsUi } from './settings/ui.tsx';\nexport { NonDestructiveTranslationMount } from './translate/mount.ts';\nexport { StreamDebounceViewportObserver } from './translate/viewport-observer.ts';\nexport { clientCache } from './translate/client-cache.ts';\nexport { lazyQueue } from './translate/lazy.ts';\nexport { settingsStore } from './settings/store.ts';\n", "const CACHE_KEY = 'dsh-chat-translate:cache';\nconst MAX_LOCAL_ENTRIES = 500;\n/** Entries older than this are treated as expired. */\nconst TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days\n\ninterface CacheEntry {\n t: number; // epoch ms; 0 = legacy entry without timestamp\n v: string;\n}\n\nexport class ClientCache {\n // Map preserves insertion order in JS, enabling true LRU semantics.\n private memCache = new Map<string, CacheEntry>();\n private dirty = false;\n private saveTimer: number | null = null;\n\n constructor() {\n this.load();\n }\n\n private load(): void {\n if (typeof localStorage === 'undefined') return;\n try {\n const raw = localStorage.getItem(CACHE_KEY);\n if (raw) {\n const obj = JSON.parse(raw);\n if (obj && typeof obj === 'object') {\n for (const [k, entry] of Object.entries(obj)) {\n if (typeof entry === 'string') {\n // Legacy entry from an older release.\n this.memCache.set(k, { t: 0, v: entry });\n } else if (entry && typeof entry === 'object' && typeof (entry as CacheEntry).v === 'string') {\n this.memCache.set(k, entry as CacheEntry);\n }\n }\n }\n }\n } catch {\n // Ignore parse failure\n }\n }\n\n get(text: string): string | undefined {\n const key = text.trim().toLowerCase();\n const entry = this.memCache.get(key);\n if (entry === undefined) return undefined;\n // Drop dirty fallback entries where value equals key\n if (entry.v && entry.v.trim().toLowerCase() === key) {\n this.memCache.delete(key);\n this.dirty = true;\n this.scheduleSave();\n return undefined;\n }\n if (entry.t > 0 && Date.now() - entry.t > TTL_MS) {\n this.memCache.delete(key);\n this.dirty = true;\n this.scheduleSave();\n return undefined;\n }\n // True LRU: Re-insert to move to the end (most recently used)\n this.memCache.delete(key);\n this.memCache.set(key, entry);\n return entry.v;\n }\n\n set(text: string, translated: string): void {\n const key = text.trim().toLowerCase();\n if (this.memCache.has(key)) {\n this.memCache.delete(key);\n } else if (this.memCache.size >= MAX_LOCAL_ENTRIES) {\n // Evict least recently used (first item in Map)\n const oldestKey = this.memCache.keys().next().value;\n if (oldestKey !== undefined) {\n this.memCache.delete(oldestKey);\n }\n }\n this.memCache.set(key, { t: Date.now(), v: translated });\n this.dirty = true;\n this.scheduleSave();\n }\n\n private scheduleSave(): void {\n if (this.saveTimer !== null || typeof window === 'undefined') return;\n this.saveTimer = window.setTimeout(() => {\n this.saveTimer = null;\n this.flushSync();\n }, 2000);\n }\n\n flushSync(): void {\n if (!this.dirty || typeof localStorage === 'undefined') return;\n this.dirty = false;\n try {\n const obj: Record<string, CacheEntry> = {};\n for (const [k, v] of this.memCache.entries()) {\n obj[k] = v;\n }\n localStorage.setItem(CACHE_KEY, JSON.stringify(obj));\n } catch {\n // Ignore quota error\n }\n }\n\n clear(): void {\n this.memCache.clear();\n this.dirty = true;\n this.flushSync();\n }\n\n size(): number {\n return this.memCache.size;\n }\n}\n\nexport const clientCache = new ClientCache();", "/**\n * \u628A fetch / \u7F51\u7EDC\u5931\u8D25\u7684\u53EF\u8BFB\u539F\u56E0\u5C55\u5F00\u6210\u5355\u884C\u6587\u672C\uFF08\u5BBF\u4E3B\u4E0E\u5BA2\u6237\u7AEF\u5171\u7528\uFF09\u3002\n *\n * \u80CC\u666F\uFF1ANode 18+ \u4E0E\u6D4F\u89C8\u5668\u7684 fetch \u5728\u7F51\u7EDC\u5C42\u5931\u8D25\u65F6\u53EA\u7ED9\u4E00\u53E5 `TypeError: fetch failed`\n * \uFF08\u6D4F\u89C8\u5668\u662F `Failed to fetch`\uFF09\uFF0C\u771F\u5B9E\u539F\u56E0\u85CF\u5728 `err.cause` \u91CC\u2014\u2014\u4F8B\u5982\n * `connect ENETUNREACH 172.24.52.126:8081`\u3001`other side closed [UND_ERR_SOCKET]`\u3001\n * `getaddrinfo ENOTFOUND`\u3002\u53EA\u4E0A\u62A5 `err.message` \u4F1A\u8BA9\u300C\u7AEF\u70B9\u4E0D\u53EF\u8FBE / \u88AB\u4EE3\u7406\u65AD\u5F00\u300D\u548C\n * \u300C\u63D2\u4EF6\u81EA\u8EAB\u51FA\u9519\u300D\u957F\u5F97\u4E00\u6A21\u4E00\u6837\uFF1A\u7528\u6237\u53EA\u770B\u5230\u65E0\u4FE1\u606F\u91CF\u7684 fetch failed\uFF0C\u65E0\u4ECE\u5224\u65AD\u8BE5\u6539\u4EC0\u4E48\u3002\n *\n * \u8FD9\u91CC\u628A message \u4E0E cause \u94FE\uFF08\u542B AggregateError \u7684\u591A\u5730\u5740\u5931\u8D25\uFF09\u62FC\u6210 `A \u2190 B \u2190 C`\u3002\n */\nexport function describeError(err: unknown): string {\n if (err === null || err === undefined) return String(err);\n const parts: string[] = [];\n const push = (s: string): void => {\n const t = String(s || '').trim();\n if (t && !parts.includes(t)) parts.push(t);\n };\n const head = (err as { message?: string }).message || String(err);\n push(head);\n const headCode = (err as { code?: string }).code;\n if (headCode && !head.includes(headCode)) push('[' + headCode + ']');\n let cause = (err as { cause?: unknown }).cause as\n | { message?: string; code?: string; cause?: unknown; errors?: unknown }\n | undefined;\n for (let depth = 0; cause && depth < 4; depth++) {\n const label = cause.message || String(cause);\n push(cause.code && !label.includes(cause.code) ? label + ' [' + cause.code + ']' : label);\n // AggregateError\uFF1A\u591A\u5730\u5740\u5168\u90E8\u5931\u8D25\uFF08happy-eyeballs \u53CC\u6808\u5E38\u89C1\uFF09\uFF0C\u5C55\u5F00\u524D\u51E0\u6761\n if (Array.isArray(cause.errors)) {\n for (const sub of (cause.errors as { message?: string; code?: string }[]).slice(0, 3)) {\n if (sub) push(sub.code || sub.message || String(sub));\n }\n }\n cause = cause.cause as typeof cause;\n }\n return parts.join(' \u2190 ');\n}", "import { describeError } from '../../describe-error.ts';\n\nexport interface TranslateItemResult {\n original: string;\n translated: string;\n channel: string;\n cached: boolean;\n}\n\nexport interface TranslateBatchOptions {\n signal?: AbortSignal;\n timeoutMs?: number;\n}\n\nexport async function requestTranslateBatch(\n texts: string[],\n options: TranslateBatchOptions = {}\n): Promise<TranslateItemResult[]> {\n if (!Array.isArray(texts) || texts.length === 0) return [];\n \n // Filter out empty or whitespace-only texts\n const validTexts = texts.map((t) => (typeof t === 'string' ? t : ''));\n if (validTexts.length === 0) return [];\n\n const controller = new AbortController();\n const timeoutId = options.timeoutMs\n ? setTimeout(() => controller.abort(), options.timeoutMs)\n : null;\n\n const effectiveSignal = options.signal\n ? (options.signal.aborted ? options.signal : controller.signal)\n : controller.signal;\n\n if (options.signal) {\n options.signal.addEventListener('abort', () => controller.abort(), { once: true });\n }\n\n try {\n const res = await fetch('/api/dsh-chat-translate/translate', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ texts: validTexts }),\n signal: effectiveSignal,\n });\n\n if (res.ok) {\n const data = (await res.json()) as { ok: boolean; results?: TranslateItemResult[]; error?: string };\n if (data.ok && Array.isArray(data.results) && data.results.length > 0) {\n return data.results;\n } else if (data.error) {\n console.warn(`[dsh-chat-translate] \u7FFB\u8BD1\u63A5\u53E3\u9519\u8BEF: ${data.error}`);\n }\n } else {\n console.warn(`[dsh-chat-translate] \u7FFB\u8BD1\u8BF7\u6C42\u5931\u8D25 (HTTP ${res.status})`);\n }\n } catch (err: any) {\n if (err?.name === 'AbortError') {\n // Intentionally aborted\n }\n } finally {\n if (timeoutId !== null) {\n clearTimeout(timeoutId);\n }\n }\n\n // Host route unreachable or error \u2014 leave the original text untouched\n return validTexts.map((t) => ({\n original: t,\n translated: t,\n channel: 'fallback-client',\n cached: false,\n }));\n}\n\nexport async function testServerChannel(channel: string): Promise<{ ok: boolean; latencyMs: number; error?: string }> {\n try {\n const res = await fetch('/api/dsh-chat-translate/test-channel', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ channel }),\n });\n return await res.json();\n } catch (err: any) {\n return { ok: false, latencyMs: 0, error: describeError(err) };\n }\n}\n", "/**\n * Non-destructive translation DOM mount mechanism.\n *\n * Preserves the original React Fiber tree, event listeners, and nested DOM nodes\n * by wrapping them in a hidden container (<span class=\"dsh-tidy-original-hidden\">)\n * rather than destroying them with `element.textContent = translated`.\n *\n * Displays the translated text in a companion container (<span class=\"dsh-tidy-translated-block\">).\n * Clicking the translation toggles between original and translated text in-place.\n */\n\nexport const CLASS_ORIGINAL_HIDDEN = 'dsh-tidy-original-hidden';\nexport const CLASS_ORIGINAL_SHOWN = 'dsh-tidy-original-shown';\nexport const CLASS_TRANSLATED_BLOCK = 'dsh-tidy-translated-block';\n\nexport interface MountOptions {\n originalText?: string;\n interactive?: boolean;\n}\n\nexport class NonDestructiveTranslationMount {\n /**\n * Mounts a translated string onto the target element non-destructively.\n */\n static mount(\n element: HTMLElement,\n translated: string,\n options: MountOptions = {}\n ): void {\n if (!element || !element.ownerDocument) return;\n const doc = element.ownerDocument;\n\n // Check if already mounted\n let transWrapper = element.querySelector<HTMLElement>(`:scope > .${CLASS_TRANSLATED_BLOCK}`);\n let origWrapper = element.querySelector<HTMLElement>(\n `:scope > .${CLASS_ORIGINAL_HIDDEN}, :scope > .${CLASS_ORIGINAL_SHOWN}`\n );\n\n if (transWrapper && origWrapper) {\n // Update existing translation\n transWrapper.textContent = translated;\n element.dataset.tidyTranslated = 'true';\n if (options.originalText) element.dataset.original = options.originalText;\n return;\n }\n\n const originalText = options.originalText ?? this.extractVisibleText(element);\n\n // Create wrapper for original children\n origWrapper = doc.createElement('span');\n origWrapper.className = CLASS_ORIGINAL_HIDDEN;\n origWrapper.style.display = 'none';\n\n // Move all existing child nodes into origWrapper\n while (element.firstChild) {\n origWrapper.appendChild(element.firstChild);\n }\n\n // Create translation block\n transWrapper = doc.createElement('span');\n transWrapper.className = CLASS_TRANSLATED_BLOCK;\n transWrapper.textContent = translated;\n\n // Interactive bilingual toggle\n const interactive = options.interactive !== false;\n if (interactive) {\n\n const showOriginal = (e: MouseEvent): void => {\n e.stopPropagation();\n if (!origWrapper || !transWrapper) return;\n origWrapper.style.display = 'inline';\n origWrapper.className = CLASS_ORIGINAL_SHOWN;\n transWrapper.style.display = 'none';\n };\n\n const showTranslated = (e: MouseEvent): void => {\n e.stopPropagation();\n if (!origWrapper || !transWrapper) return;\n origWrapper.style.display = 'none';\n origWrapper.className = CLASS_ORIGINAL_HIDDEN;\n transWrapper.style.display = 'inline';\n };\n\n transWrapper.addEventListener('click', showOriginal);\n origWrapper.addEventListener('click', showTranslated);\n }\n\n // Append both to target element\n element.appendChild(transWrapper);\n element.appendChild(origWrapper);\n\n element.dataset.tidyTranslated = 'true';\n element.dataset.original = originalText;\n }\n\n /**\n * Unmounts translation and restores original DOM nodes completely.\n */\n static unmount(element: HTMLElement): void {\n if (!element) return;\n\n const origWrapper = element.querySelector<HTMLElement>(\n `:scope > .${CLASS_ORIGINAL_HIDDEN}, :scope > .${CLASS_ORIGINAL_SHOWN}`\n );\n const transWrapper = element.querySelector<HTMLElement>(`:scope > .${CLASS_TRANSLATED_BLOCK}`);\n\n if (origWrapper) {\n // Move all original children back to element\n while (origWrapper.firstChild) {\n element.insertBefore(origWrapper.firstChild, origWrapper);\n }\n origWrapper.remove();\n }\n\n if (transWrapper) {\n transWrapper.remove();\n }\n\n // Fallback if no wrapper was created but text was modified directly\n if (!origWrapper && element.dataset.original) {\n element.textContent = element.dataset.original;\n }\n\n delete element.dataset.tidyTranslated;\n delete element.dataset.original;\n }\n\n /**\n * Checks if an element has non-destructive translation mounted.\n */\n static isMounted(element: HTMLElement): boolean {\n return (\n element.dataset.tidyTranslated === 'true' &&\n !!element.querySelector(`:scope > .${CLASS_TRANSLATED_BLOCK}`)\n );\n }\n\n /**\n * Gets the original text recorded on the element or contained in origWrapper.\n */\n static getOriginal(element: HTMLElement): string | undefined {\n if (element.dataset.original) return element.dataset.original;\n const origWrapper = element.querySelector<HTMLElement>(\n `:scope > .${CLASS_ORIGINAL_HIDDEN}, :scope > .${CLASS_ORIGINAL_SHOWN}`\n );\n return origWrapper ? origWrapper.textContent?.trim() : undefined;\n }\n\n /**\n * Extracts text content excluding our own translation wrappers.\n */\n static extractVisibleText(element: HTMLElement): string {\n const origWrapper = element.querySelector<HTMLElement>(\n `:scope > .${CLASS_ORIGINAL_HIDDEN}, :scope > .${CLASS_ORIGINAL_SHOWN}`\n );\n if (origWrapper) {\n return origWrapper.textContent?.trim() || '';\n }\n const transWrapper = element.querySelector<HTMLElement>(`:scope > .${CLASS_TRANSLATED_BLOCK}`);\n if (transWrapper) {\n return transWrapper.textContent?.trim() || '';\n }\n return element.textContent?.trim() || '';\n }\n}", "/**\n * StreamDebounceViewportObserver\n *\n * Combines:\n * 1. True IntersectionObserver for viewport visibility detection (rootMargin: '150px 0px').\n * 2. Typing/streaming debounce (default 400ms) to avoid translating partial streaming sentences.\n * 3. Batch queuing to group multiple visible elements into efficient batch requests.\n */\n\nexport interface ViewportObserverOptions {\n rootMargin?: string;\n debounceMs?: number;\n onVisibleBatch: (items: Array<{ element: HTMLElement; text: string }>) => void;\n}\n\nexport class StreamDebounceViewportObserver {\n private intersectionObserver: IntersectionObserver | null = null;\n /**\n * Pending per-element streaming debounce timers. A Map (not a WeakMap)\n * because disconnect() must enumerate and clear every pending timer: a\n * WeakMap cannot be iterated, so a debounce armed just before the switch was\n * turned off would still fire registerForViewport() afterwards.\n */\n private streamingTimers = new Map<HTMLElement, number>();\n private pendingQueue: Array<{ element: HTMLElement; text: string }> = [];\n private batchFlushTimer: number | null = null;\n private options: Required<Omit<ViewportObserverOptions, 'onVisibleBatch'>> & {\n onVisibleBatch: ViewportObserverOptions['onVisibleBatch'];\n };\n\n constructor(options: ViewportObserverOptions) {\n this.options = {\n rootMargin: options.rootMargin ?? '150px 0px',\n debounceMs: options.debounceMs ?? 400,\n onVisibleBatch: options.onVisibleBatch,\n };\n this.initIntersectionObserver();\n }\n\n private initIntersectionObserver(): void {\n if (typeof window === 'undefined' || typeof IntersectionObserver === 'undefined') {\n return;\n }\n\n this.intersectionObserver = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting && entry.target instanceof HTMLElement) {\n const el = entry.target;\n // Stop observing once it enters viewport and is queued\n this.intersectionObserver?.unobserve(el);\n const text = el.dataset.tidyPendingText || el.textContent?.trim() || '';\n if (text) {\n delete el.dataset.tidyPendingText;\n this.enqueueBatch(el, text);\n }\n }\n }\n },\n {\n root: null, // viewport\n rootMargin: this.options.rootMargin,\n threshold: 0,\n }\n );\n }\n\n /**\n * Observe an element with streaming debounce.\n * If streaming updates characterData repeatedly within debounceMs, the timer resets.\n */\n observeWithDebounce(element: HTMLElement, text: string, immediate = false): void {\n if (!element || !text) return;\n\n // Clear any active streaming timer for this element\n const existingTimer = this.streamingTimers.get(element);\n if (existingTimer !== undefined) {\n clearTimeout(existingTimer);\n this.streamingTimers.delete(element);\n }\n\n if (immediate || this.options.debounceMs <= 0) {\n this.registerForViewport(element, text);\n return;\n }\n\n const timer = window.setTimeout(() => {\n // Stale-callback guard: disconnect() clears the map, so a callback that\n // is no longer the registered timer must not re-enter the observer.\n if (this.streamingTimers.get(element) !== timer) return;\n this.streamingTimers.delete(element);\n if (element.isConnected) {\n // Read latest text content after streaming settles\n const latestText = element.textContent?.trim() || text;\n this.registerForViewport(element, latestText);\n }\n }, this.options.debounceMs);\n\n this.streamingTimers.set(element, timer);\n }\n\n private registerForViewport(element: HTMLElement, text: string): void {\n if (!element.isConnected) return;\n\n if (!this.intersectionObserver) {\n // Fallback if IntersectionObserver is unsupported: enqueue immediately\n this.enqueueBatch(element, text);\n return;\n }\n\n element.dataset.tidyPendingText = text;\n this.intersectionObserver.observe(element);\n }\n\n private enqueueBatch(element: HTMLElement, text: string): void {\n this.pendingQueue.push({ element, text });\n if (this.batchFlushTimer === null && typeof window !== 'undefined') {\n this.batchFlushTimer = window.setTimeout(() => {\n this.batchFlushTimer = null;\n this.flushQueue();\n }, 50);\n }\n }\n\n private flushQueue(): void {\n if (this.pendingQueue.length === 0) return;\n const batch = [...this.pendingQueue];\n this.pendingQueue = [];\n this.options.onVisibleBatch(batch);\n }\n\n unobserve(element: HTMLElement): void {\n const timer = this.streamingTimers.get(element);\n if (timer !== undefined) {\n clearTimeout(timer);\n this.streamingTimers.delete(element);\n }\n delete element.dataset.tidyPendingText;\n if (this.intersectionObserver) {\n this.intersectionObserver.unobserve(element);\n }\n }\n\n disconnect(): void {\n if (this.batchFlushTimer !== null) {\n clearTimeout(this.batchFlushTimer);\n this.batchFlushTimer = null;\n }\n // Drop every pending streaming debounce: after a disable none of them may\n // fire registerForViewport() (the Map is iterable, so this is exhaustive).\n for (const timer of this.streamingTimers.values()) {\n clearTimeout(timer);\n }\n this.streamingTimers.clear();\n this.pendingQueue = [];\n if (this.intersectionObserver) {\n this.intersectionObserver.disconnect();\n this.initIntersectionObserver();\n }\n }\n}\n", "import { clientCache } from './client-cache.ts';\nimport { requestTranslateBatch } from './api.ts';\nimport { NonDestructiveTranslationMount } from './mount.ts';\nimport { StreamDebounceViewportObserver } from './viewport-observer.ts';\n\ntype TranslateTask = {\n element: HTMLElement;\n text: string;\n};\n\nclass LazyTranslationQueue {\n private enabled = true;\n private viewportObserver: StreamDebounceViewportObserver;\n\n constructor() {\n this.viewportObserver = new StreamDebounceViewportObserver({\n rootMargin: '150px 0px',\n debounceMs: 400,\n onVisibleBatch: (items) => this.handleVisibleBatch(items),\n });\n }\n\n setEnabled(enabled: boolean): void {\n this.enabled = enabled;\n if (!enabled) {\n this.viewportObserver.disconnect();\n }\n }\n\n observe(element: HTMLElement, text: string, immediate = false): void {\n if (!this.enabled || !element.isConnected) return;\n\n // 1. If cached, apply immediately non-destructively\n const cached = clientCache.get(text);\n if (cached) {\n this.applyTranslation(element, cached, text);\n return;\n }\n\n // 2. Delegate to streaming-debounced viewport observer\n this.viewportObserver.observeWithDebounce(element, text, immediate);\n }\n\n private async handleVisibleBatch(items: TranslateTask[]): Promise<void> {\n if (!this.enabled || items.length === 0) return;\n\n // Document order: translations appear in reading order (top-to-bottom),\n // never out-of-order even when IntersectionObserver fires entries randomly.\n const sorted = [...items].sort((a, b) => {\n if (a.element === b.element) return 0;\n const pos = a.element.compareDocumentPosition(b.element);\n return pos & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;\n });\n\n // Group elements by text to deduplicate API requests\n const textMap = new Map<string, HTMLElement[]>();\n for (const item of sorted) {\n if (!item.element.isConnected) continue;\n\n const cached = clientCache.get(item.text);\n if (cached) {\n this.applyTranslation(item.element, cached, item.text);\n continue;\n }\n\n const list = textMap.get(item.text) || [];\n list.push(item.element);\n textMap.set(item.text, list);\n }\n\n const uniqueTexts = Array.from(textMap.keys());\n if (uniqueTexts.length === 0) return;\n\n const results = await requestTranslateBatch(uniqueTexts);\n\n for (const res of results) {\n if (\n res.translated &&\n res.translated.trim() &&\n res.channel !== 'fallback' &&\n res.channel !== 'fallback-client' &&\n res.translated.trim() !== res.original.trim()\n ) {\n clientCache.set(res.original, res.translated);\n const entries = textMap.get(res.original) || [];\n for (const entry of entries) {\n if (entry.isConnected && this.enabled) {\n this.applyTranslation(entry, res.translated, res.original);\n }\n }\n } else if (res.channel === 'fallback' || res.channel === 'fallback-client') {\n console.debug(`[dsh-chat-translate] \u7FFB\u8BD1\u672A\u6210\u529F (\u964D\u7EA7\u4FDD\u7559\u539F\u6587): \"${res.original.slice(0, 40)}\"`);\n }\n }\n }\n\n private applyTranslation(element: HTMLElement, translated: string, original: string): void {\n if (!element.isConnected || !this.enabled) return;\n NonDestructiveTranslationMount.mount(element, translated, {\n originalText: original,\n });\n }\n\n disconnect(): void {\n this.viewportObserver.disconnect();\n }\n}\n\nexport const lazyQueue = new LazyTranslationQueue();", "import { clientCache } from './client-cache.ts';\nimport { lazyQueue } from './lazy.ts';\nimport { NonDestructiveTranslationMount } from './mount.ts';\n\n// Case-insensitive match: DSH builds vary the summary class casing across\n// versions \u2014 lowercase \"summary\" for tool-call rows (MISisG_summary /\n// _48RFeq_summary) vs camelCase \"thinkSummary\" for the Think card row.\n// Attribute values are matched case-sensitively by default in HTML, which is\n// why the old lowercase-only selector silently missed think summaries.\nconst TOOL_TITLE_SELECTOR = '[class*=\"summary\" i]';\n\n/**\n * Current-session scroll container (the conversation layout re-renders this\n * whole subtree when the active session changes), falling back to the chat\n * flow. Both are emitted by the DSH web UI.\n */\nconst SESSION_ROOT_SELECTOR = '[data-conversation-scroll], [data-chat-flow]';\n\n/** How often (ms) we re-check that the observed root is still the live one. */\nconst ROOT_CHECK_INTERVAL_MS = 3000;\n\nfunction isToolSummarySpan(span: HTMLElement): boolean {\n if (!span || span.nodeType !== 1) return false;\n if (span.hasAttribute('aria-hidden')) return false;\n\n // Never match parent rows or containers that contain title, leading icon, chevron or nested summary\n if (span.querySelector?.('[class*=\"title\"], [class*=\"leading\"], [class*=\"chevron\"], [class*=\"sep\"], [class*=\"summary\" i]')) {\n return false;\n }\n\n const cls = span.className || '';\n if (/title|leading|icon|badge|chevron|separator|sep\\b|row\\b|root\\b|card\\b/i.test(cls)) return false;\n\n // Never translate fold toggle (\"\u5C55\u5F00\"/\"\u6536\u8D77\") or Think card's own title badge (\"Think\"/\"\u601D\u8003\")\n const rawToggle = (span.textContent || '').trim();\n if (\n rawToggle.length <= 12 &&\n /^(\u5C55\u5F00|\u6536\u8D77|\u5C55\u5F00\u5168\u90E8|\u6536\u8D77\u5168\u90E8|Expand|Collapse|Show more|Show less|Think|\u601D\u8003)$/i.test(rawToggle)\n ) {\n return false;\n }\n if (rawToggle === 'Think' || rawToggle === '\u601D\u8003') return false;\n if (\n span.closest('button, [role=\"button\"]') &&\n rawToggle.length <= 12 &&\n /\u5C55\u5F00|\u6536\u8D77|Expand|Collapse|Think|\u601D\u8003/i.test(rawToggle)\n ) {\n return false;\n }\n\n // Must belong to tool call or think block card\n if (\n span.closest(\n '[data-chat-call-id], [data-slot=\"tool.call.toolview\"], [data-sample], [data-variant], [data-tool], [data-disclosure-row]'\n )\n ) {\n return true;\n }\n return false;\n}\n\n/**\n * DSH's Think card (ReasoningRow) carries `data-state=\"running\"` on its root\n * while the reasoning block is still streaming and flips it to `\"ok\"` once it\n * has finished. While running, the collapsed summary follows the live tail of\n * the reasoning text (`.summary[data-follow-end]`), so translating it would\n * capture an intermediate line that is replaced by the stable first-line\n * summary when the block ends. Defer instead: the observed `data-state`\n * mutation re-scans this span, at which point the final summary is processed.\n */\nfunction thinkBlockStillRunning(span: HTMLElement): boolean {\n const thinkRoot = span.closest<HTMLElement>('[data-variant=\"think\"]');\n return !!thinkRoot && thinkRoot.dataset.state === 'running';\n}\n\nexport class ChatTranslateObserver {\n private observer: MutationObserver | null = null;\n private rootElement: HTMLElement | null = null;\n private rootCheckTimer: number | null = null;\n private isEnabled = true;\n\n constructor() {\n this.handleMutations = this.handleMutations.bind(this);\n }\n\n setEnabled(enabled: boolean): void {\n this.isEnabled = enabled;\n if (enabled) {\n lazyQueue.setEnabled(true);\n this.start();\n } else {\n this.restoreOriginals();\n this.disconnect();\n lazyQueue.setEnabled(false);\n }\n }\n\n private restoreOriginals(): void {\n const scope = this.rootElement ?? document;\n const spans = scope.querySelectorAll<HTMLElement>('[data-tidy-translated=\"true\"]');\n for (const span of spans) {\n NonDestructiveTranslationMount.unmount(span);\n }\n }\n\n /**\n * The active session's scroll container. Only the currently-viewed session\n * is translated; switching sessions replaces this subtree and the observer\n * naturally follows the new content.\n */\n private isVisible(el: HTMLElement): boolean {\n if (el.hasAttribute('hidden')) return false;\n if (el.style.display === 'none') return false;\n try {\n return el.getClientRects().length > 0;\n } catch {\n return true;\n }\n }\n\n private findRoot(documentRef: Document): HTMLElement {\n if (!documentRef.body) {\n return documentRef.documentElement;\n }\n const candidates = documentRef.querySelectorAll<HTMLElement>(SESSION_ROOT_SELECTOR);\n for (const el of candidates) {\n if (this.isVisible(el)) {\n return el;\n }\n }\n return documentRef.body;\n }\n\n start(documentRef: Document = document): () => void {\n if (!this.isEnabled || typeof window === 'undefined') {\n return () => {};\n }\n\n const findAndObserveRoot = () => {\n const root = this.findRoot(documentRef);\n\n this.rootElement = root;\n\n // 1. Initial scan of existing tool elements\n this.scanContainer(root);\n\n // 2. Setup MutationObserver\n if (!this.observer) {\n this.observer = new MutationObserver(this.handleMutations);\n this.observer.observe(root, {\n childList: true,\n subtree: true,\n attributes: true,\n characterData: true,\n attributeFilter: ['data-state', 'data-tool', 'data-variant', 'data-sample', 'aria-expanded'],\n });\n }\n };\n\n findAndObserveRoot();\n\n // Defense: if the observed root is detached or hidden (e.g. the user\n // switched to another view), re-probe for the live session container.\n this.scheduleRootCheck();\n\n return () => {\n this.disconnect();\n };\n }\n\n private scheduleRootCheck(): void {\n if (this.rootCheckTimer !== null || typeof window === 'undefined') return;\n this.rootCheckTimer = window.setInterval(() => {\n if (this.rootElement && this.rootElement.isConnected && this.isVisible(this.rootElement)) {\n return;\n }\n this.restart();\n }, ROOT_CHECK_INTERVAL_MS);\n }\n\n private restart(): void {\n if (typeof window === 'undefined') return;\n const wasEnabled = this.isEnabled;\n this.disconnect();\n if (wasEnabled) {\n this.start();\n }\n }\n\n private handleMutations(mutations: MutationRecord[]): void {\n if (!this.isEnabled) return;\n\n for (const mutation of mutations) {\n if (mutation.type === 'childList') {\n for (let i = 0; i < mutation.addedNodes.length; i++) {\n const node = mutation.addedNodes[i];\n if (node instanceof HTMLElement) {\n if (\n node.classList?.contains('dsh-tidy-translated-block') ||\n node.classList?.contains('dsh-tidy-original-hidden') ||\n node.classList?.contains('dsh-tidy-original-shown')\n ) {\n continue;\n }\n this.scanNode(node);\n }\n }\n } else if (mutation.type === 'attributes') {\n const target = mutation.target;\n if (target instanceof HTMLElement) {\n if (\n target.classList?.contains('dsh-tidy-translated-block') ||\n target.classList?.contains('dsh-tidy-original-hidden') ||\n target.classList?.contains('dsh-tidy-original-shown')\n ) {\n continue;\n }\n this.scanNode(target);\n }\n } else if (mutation.type === 'characterData') {\n const parent = mutation.target.parentElement;\n if (parent instanceof HTMLElement) {\n if (\n parent.classList?.contains('dsh-tidy-translated-block') ||\n parent.classList?.contains('dsh-tidy-original-hidden') ||\n parent.classList?.contains('dsh-tidy-original-shown')\n ) {\n continue;\n }\n this.scanNode(parent);\n }\n }\n }\n }\n\n private scanContainer(container: HTMLElement): void {\n const spans = container.querySelectorAll<HTMLElement>(TOOL_TITLE_SELECTOR);\n spans.forEach((span) => {\n if (isToolSummarySpan(span)) {\n this.processSpan(span);\n }\n });\n }\n\n private scanNode(node: HTMLElement): void {\n if (node.matches?.(TOOL_TITLE_SELECTOR) && isToolSummarySpan(node)) {\n this.processSpan(node);\n }\n\n const spans = node.querySelectorAll<HTMLElement>(TOOL_TITLE_SELECTOR);\n spans.forEach((span) => {\n if (isToolSummarySpan(span)) {\n this.processSpan(span);\n }\n });\n }\n\n private processSpan(span: HTMLElement): void {\n // Think summaries are translated only after the reasoning block has fully\n // finished streaming \u2014 never while `data-state=\"running\"` (the summary is\n // then a live tail line that would be replaced moments later). The\n // transition to \"ok\" triggers a re-scan via the observed data-state\n // attribute, which is when this span becomes processable.\n if (thinkBlockStillRunning(span)) return;\n\n if (NonDestructiveTranslationMount.isMounted(span)) {\n const original = NonDestructiveTranslationMount.getOriginal(span);\n if (original) {\n const cached = clientCache.get(original);\n if (cached) return;\n }\n return;\n }\n\n const text = NonDestructiveTranslationMount.extractVisibleText(span);\n if (!text) return;\n\n // Check fast client cache\n const cached = clientCache.get(text);\n if (cached) {\n NonDestructiveTranslationMount.mount(span, cached, {\n originalText: text,\n });\n return;\n }\n\n // Send to viewport lazy queue\n lazyQueue.observe(span, text);\n }\n\n disconnect(): void {\n if (this.rootCheckTimer !== null) {\n clearInterval(this.rootCheckTimer);\n this.rootCheckTimer = null;\n }\n if (this.observer) {\n this.observer.disconnect();\n this.observer = null;\n }\n lazyQueue.disconnect();\n this.rootElement = null;\n }\n}\n\nexport const chatTranslateObserver = new ChatTranslateObserver();\n", "import { useState, useEffect } from 'react';\nimport * as React from 'react';\nimport type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';\nimport { settingsStore, SETTINGS_NAMESPACE, type ClientSettingsState } from './store.ts';\nimport { SETTINGS_CSS } from './styles.ts';\nimport { claimReadingSettingsPage, READING_ITEM_SLOT } from '../reading-settings-page.tsx';\nimport { NS, en, zh, type ChatTranslateLocaleKey } from '../locales.ts';\nlet stylesInjected = false;\nfunction ensureSettingsStyles(): void {\n if (stylesInjected || typeof document === 'undefined') return;\n const el = document.createElement('style');\n el.dataset.tidySettings = 'true';\n el.textContent = SETTINGS_CSS;\n document.head.appendChild(el);\n stylesInjected = true;\n}\n\nfunction Switch(props: { checked: boolean; onChange: () => void; label: string }): React.ReactElement {\n return (\n <button\n type=\"button\"\n className=\"dsh-tidy-switch\"\n role=\"switch\"\n aria-checked={props.checked}\n onClick={props.onChange}\n aria-label={props.label}\n />\n );\n}\n\n/**\n * Chat-translate's card inside the shared \u300C\u9605\u8BFB\u4F53\u9A8C\u300D settings page. The card\n * keeps its own chrome (title rows, switches, credential form); the shared page\n * only stacks the participants' cards.\n * @param _props - renderer-bound seat of the `reading.settings.item` slot.\n * @returns The card element.\n */\nexport function TidySettingsPanel(_props: PropsRuntime<'reading.settings.item'>): React.ReactElement {\n ensureSettingsStyles();\n\n const [state, setState] = useState<ClientSettingsState>(() => settingsStore.getState());\n const [testing, setTesting] = useState<{ channel: string; running: boolean; ok?: boolean; message?: string } | null>(null);\n const [apiKeyInput, setApiKeyInput] = useState('');\n const [savingKey, setSavingKey] = useState(false);\n const [keyMsg, setKeyMsg] = useState<{ ok: boolean; text: string } | null>(null);\n\n useEffect(() => {\n return settingsStore.subscribe(() => {\n setState(settingsStore.getState());\n });\n }, []);\n\n const runTest = async (channel: string): Promise<void> => {\n setTesting({ channel, running: true });\n const res = await settingsStore.testChannel(channel);\n const message = res.ok\n ? `\u8FDE\u63A5\u6B63\u5E38\uFF0C\u5EF6\u8FDF ${res.latencyMs}ms`\n : `\u5931\u8D25\uFF1A${res.error || '\u672A\u77E5\u9519\u8BEF'}`;\n setTesting({ channel, running: false, ok: res.ok, message });\n };\n\n const handleSaveKey = async (): Promise<void> => {\n setSavingKey(true);\n setKeyMsg(null);\n const res = await settingsStore.saveApiKey(apiKeyInput);\n setSavingKey(false);\n if (res.ok) {\n const cleared = !apiKeyInput.trim();\n setKeyMsg({\n ok: true,\n text: cleared\n ? '\u5DF2\u6E05\u9664 API Key\uFF08AI \u901A\u9053\u5C06\u4E0D\u53EF\u7528\uFF0CBing \u515C\u5E95\uFF09'\n : '\u5DF2\u4FDD\u5B58\u5230 ~/.dsh/.credentials.yaml\uFF0C\u7ACB\u5373\u751F\u6548',\n });\n setApiKeyInput('');\n } else {\n setKeyMsg({ ok: false, text: `\u4FDD\u5B58\u5931\u8D25\uFF1A${res.error || '\u672A\u77E5\u9519\u8BEF'}` });\n }\n };\n\n return (\n <div className=\"dsh-tidy-settings\">\n {/* 1. \u603B\u5F00\u5173 */}\n <div className=\"dsh-tidy-card\">\n <div className=\"dsh-tidy-title\">\n <span>\u5DE5\u5177\u8C03\u7528\u4E0E\u601D\u8003\u6458\u8981\u7FFB\u8BD1</span>\n <Switch\n checked={state.enabled}\n onChange={() => settingsStore.update({ enabled: !state.enabled })}\n label=\"\u542F\u7528\u5DE5\u5177\u8C03\u7528\u4E0E\u6458\u8981\u7FFB\u8BD1\"\n />\n </div>\n <div className=\"dsh-tidy-desc\">\n \u81EA\u52A8\u5C06\u5F53\u524D\u4F1A\u8BDD\u4E2D\u5DE5\u5177\u8C03\u7528\u6807\u9898\u4E0E\u601D\u8003\u6298\u53E0\u6458\u8981\uFF08\u5982 <code>Locate DSH home directory structure</code>\uFF09\u7FFB\u8BD1\u4E3A\u4E2D\u6587\uFF0C\n \u70B9\u51FB\u8BD1\u6587\u53EF\u539F\u5730\u5207\u6362\u539F\u6587/\u8BD1\u6587\u3002\u601D\u8003\u6298\u53E0\u6458\u8981\u4EC5\u5728\u601D\u8003\u5B8C\u5168\u7ED3\u675F\u540E\u624D\u7FFB\u8BD1\uFF08\u4E0D\u7FFB\u8BD1\u6D41\u5F0F\u4E2D\u95F4\u6001\uFF09\u3002\n \u4EC5\u4F5C\u7528\u4E8E\u5F53\u524D\u67E5\u770B\u7684\u4F1A\u8BDD\uFF0C\u5BF9\u8BDD\u6B63\u6587\u6C38\u4E0D\u7FFB\u8BD1\u3002\n </div>\n </div>\n\n {state.enabled && (\n <>\n {/* 2. AI \u901A\u9053 */}\n <div className=\"dsh-tidy-card\">\n <div className=\"dsh-tidy-title\">\n <span>AI \u7FFB\u8BD1\uFF08OpenAI \u517C\u5BB9\u534F\u8BAE\uFF09</span>\n <Switch\n checked={state.aiEnabled}\n onChange={() => settingsStore.update({ aiEnabled: !state.aiEnabled })}\n label=\"\u542F\u7528 AI \u7FFB\u8BD1\u901A\u9053\"\n />\n </div>\n <div className=\"dsh-tidy-desc\">\n <span className={`dsh-tidy-badge ${state.aiConfigured ? 'dsh-tidy-badge-ok' : 'dsh-tidy-badge-warn'}`}>\n {state.aiConfigured ? '\u5DF2\u914D\u7F6E' : '\u672A\u914D\u7F6E'}\n </span>{' '}\n \u672A\u914D\u7F6E\u65F6 AI \u901A\u9053\u81EA\u52A8\u8DF3\u8FC7\uFF0C\u7531 Bing \u515C\u5E95\u3002\n </div>\n\n {state.aiEnabled && (\n <>\n <div className=\"dsh-tidy-row\">\n <div className=\"dsh-tidy-row-info\">\n <div className=\"dsh-tidy-row-title\">API Key</div>\n <div className=\"dsh-tidy-row-desc\">\n \u4FDD\u5B58\u81F3 <code>~/.dsh/.credentials.yaml</code> \u7684 <code>TRANSLATE_API_KEY</code>\uFF0C\u4FDD\u5B58\u540E\u7ACB\u5373\u751F\u6548\uFF1B\u7559\u7A7A\u4FDD\u5B58 = \u6E05\u9664\n </div>\n </div>\n <div className=\"dsh-tidy-input-row\">\n <input\n type=\"password\"\n className=\"dsh-tidy-input\"\n placeholder={state.aiConfigured ? '\u5DF2\u914D\u7F6E\uFF08\u5982\u9700\u66F4\u6362\u8BF7\u76F4\u63A5\u8F93\u5165\uFF09' : 'sk-...'}\n value={apiKeyInput}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) => setApiKeyInput(e.target.value)}\n style={{ width: '260px' }}\n aria-label=\"API Key\"\n />\n <button type=\"button\" className=\"dsh-tidy-btn\" disabled={savingKey} onClick={handleSaveKey}>\n {savingKey ? '\u4FDD\u5B58\u4E2D\u2026' : '\u4FDD\u5B58'}\n </button>\n </div>\n </div>\n {keyMsg && (\n <div className={`dsh-tidy-test-result ${keyMsg.ok ? 'ok' : 'fail'}`}>{keyMsg.text}</div>\n )}\n\n <div className=\"dsh-tidy-row\">\n <div className=\"dsh-tidy-row-info\">\n <div className=\"dsh-tidy-row-title\">Base URL</div>\n <div className=\"dsh-tidy-row-desc\">\n \u4EFB\u610F OpenAI \u517C\u5BB9\u670D\u52A1\u7AEF\u70B9\uFF0C\u5982 <code>https://api.openai.com/v1</code>\u3001{' '}\n <code>https://api.deepseek.com/v1</code>\n </div>\n </div>\n <input\n type=\"text\"\n className=\"dsh-tidy-input\"\n placeholder=\"https://api.openai.com/v1\"\n value={state.baseUrl}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) =>\n settingsStore.update({ baseUrl: e.target.value })\n }\n style={{ width: '260px' }}\n aria-label=\"AI Base URL\"\n />\n </div>\n <div className=\"dsh-tidy-row\">\n <div className=\"dsh-tidy-row-info\">\n <div className=\"dsh-tidy-row-title\">\u6A21\u578B</div>\n <div className=\"dsh-tidy-row-desc\">\n \u5982 <code>gpt-4o-mini</code>\u3001<code>deepseek-chat</code>\uFF1B\u7559\u7A7A\u89C6\u4E3A\u672A\u914D\u7F6E\n </div>\n </div>\n <input\n type=\"text\"\n className=\"dsh-tidy-input\"\n placeholder=\"gpt-4o-mini\"\n value={state.model}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) =>\n settingsStore.update({ model: e.target.value })\n }\n style={{ width: '260px' }}\n aria-label=\"AI \u6A21\u578B\"\n />\n </div>\n <div className=\"dsh-tidy-row\">\n <button\n type=\"button\"\n className=\"dsh-tidy-btn\"\n disabled={testing?.running}\n onClick={() => runTest('openai')}\n >\n {testing?.running ? '\u6D4B\u8BD5\u4E2D\u2026' : '\u6D4B\u8BD5 AI \u901A\u9053'}\n </button>\n {testing?.channel === 'openai' && !testing.running && (\n <span className={`dsh-tidy-test-result ${testing.ok ? 'ok' : 'fail'}`}>{testing.message}</span>\n )}\n </div>\n </>\n )}\n </div>\n\n {/* 3. Bing \u901A\u9053 */}\n <div className=\"dsh-tidy-card\">\n <div className=\"dsh-tidy-title\">\n <span>Bing \u7F51\u9875\u7FFB\u8BD1\uFF08\u514D Key \u515C\u5E95\uFF09</span>\n <Switch\n checked={state.bingEnabled}\n onChange={() => settingsStore.update({ bingEnabled: !state.bingEnabled })}\n label=\"\u542F\u7528 Bing \u7FFB\u8BD1\u901A\u9053\"\n />\n </div>\n <div className=\"dsh-tidy-desc\">\n \u5185\u7F6E\u514D Key \u7FFB\u8BD1\u901A\u9053\uFF0C\u65E0\u9700\u4EFB\u4F55\u914D\u7F6E\u3002AI \u672A\u914D\u7F6E\u6216\u8BF7\u6C42\u5931\u8D25\u65F6\u81EA\u52A8\u515C\u5E95\uFF1BAI \u4E0E Bing \u540C\u65F6\u5173\u95ED\u5219\u4E0D\u7FFB\u8BD1\u3002\n </div>\n </div>\n\n {/* 4. \u884C\u4E3A\u8BF4\u660E */}\n <div className=\"dsh-tidy-card\">\n <div className=\"dsh-tidy-row-title\">\u901A\u9053\u884C\u4E3A</div>\n <ul className=\"dsh-tidy-desc dsh-tidy-behavior-list\">\n <li>AI \u5F00\u542F\u4E14\u5DF2\u914D\u7F6E \u2192 AI \u4F18\u5148\u7FFB\u8BD1\uFF0C\u5931\u8D25\u81EA\u52A8\u964D\u7EA7 Bing</li>\n <li>AI \u5F00\u542F\u4F46\u672A\u914D\u7F6E + Bing \u5F00\u542F \u2192 \u7531 Bing \u7FFB\u8BD1</li>\n <li>AI \u5F00\u542F\u4F46\u672A\u914D\u7F6E + Bing \u5173\u95ED \u2192 \u4E0D\u7FFB\u8BD1</li>\n <li>AI \u5173\u95ED + Bing \u5F00\u542F \u2192 \u76F4\u63A5\u4F7F\u7528 Bing</li>\n <li>AI \u5173\u95ED + Bing \u5173\u95ED \u2192 \u4E0D\u7FFB\u8BD1</li>\n </ul>\n </div>\n\n {/* 5. \u5E76\u53D1\u63A7\u5236 */}\n <div className=\"dsh-tidy-card\">\n <div className=\"dsh-tidy-row\">\n <div className=\"dsh-tidy-row-info\">\n <div className=\"dsh-tidy-row-title\">\u6700\u5927\u7FFB\u8BD1\u5E76\u53D1\u6570</div>\n <div className=\"dsh-tidy-row-desc\">\n \u63A7\u5236\u89C6\u53E3\u6EDA\u52A8\u4E0E\u591A\u5DE5\u5177\u5361\u7247\u65F6\u7684\u6700\u5927\u5E76\u884C\u8BF7\u6C42\u6570\uFF08\u8303\u56F4 1-100\uFF0C\u63A8\u8350 3\uFF09\u3002\n </div>\n </div>\n <input\n type=\"number\"\n className=\"dsh-tidy-input\"\n min={1}\n max={100}\n step={1}\n value={state.concurrency}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) => {\n const val = parseInt(e.target.value, 10);\n if (!Number.isFinite(val)) return;\n settingsStore.update({ concurrency: Math.min(Math.max(val, 1), 100) });\n }}\n style={{ width: '88px' }}\n aria-label=\"\u6700\u5927\u7FFB\u8BD1\u5E76\u53D1\u6570\"\n />\n </div>\n </div>\n </>\n )}\n </div>\n );\n}\n\n/**\n * Bind the settings store to DSH's native settings/credentials services and\n * join the shared \u300C\u9605\u8BFB\u4F53\u9A8C\u300D settings page: whichever participant activates\n * first claims the page, everyone registers a card into its child slot.\n * @param ctx - DSH browser client context; services are resolved defensively.\n */\nexport function setupSettingsUi(ctx: any): void {\n if (typeof window === 'undefined') return;\n\n // Ride DSH's own settings surface: the `settingsScope` service mirrors the\n // host document (per-namespace describe) and the `credentials` Remote API\n // writes the API key. No custom config HTTP endpoint since 1.2.\n try {\n // Declared via inject: 'settingsScope' and 'remote.credentials' (plus the\n // 'remote' root). Fall back to optional lookup so a degraded environment\n // degrades to an in-memory store instead of failing loudly.\n const settingsScope = ctx?.settingsScope || (ctx?.get ? ctx.get('settingsScope') : null);\n const remoteCredentials =\n ctx?.remote?.credentials || (ctx?.get ? ctx.get('remote.credentials') : null);\n if (settingsScope && typeof settingsScope.bind === 'function') {\n const scope = settingsScope.bind({ namespace: SETTINGS_NAMESPACE });\n settingsStore.attach(scope, remoteCredentials ?? null);\n }\n } catch (err) {\n console.warn('[dsh-chat-translate] Failed to bind settings scope:', err);\n }\n\n // The locale service is optional, like the services above: the page label and\n // the card's `t` seat fall back to the Chinese copy without it. It is read\n // through `ctx.get` and never as `ctx.locale` \u2014 the kernel guard throws\n // `cannot get property \"locale\" without inject` for any service the plugin did\n // not declare, and locale is deliberately not one of them (an undeclared read\n // here aborted the whole client apply and took the plugin off the page).\n const locale = typeof ctx?.get === 'function' ? ctx.get('locale') : null;\n if (locale && typeof locale.register === 'function' && typeof ctx?.effect === 'function') {\n ctx.effect(\n () => locale.register(NS, { zh, en }),\n 'dsh-chat-translate: locale dictionaries'\n );\n }\n const t = locale && typeof locale.bind === 'function'\n ? locale.bind(NS)\n // Key-aware fallback: an earlier revision returned `zh.pageNav` for EVERY\n // key, which labelled the shared page's tab with the page name instead of\n // this plugin's own title whenever the locale service was not yet there.\n : (key: ChatTranslateLocaleKey): string => zh[key] ?? key;\n\n try {\n const slots = ctx?.slots || (ctx?.get ? ctx.get('slots') : null);\n if (!slots || typeof slots.inject !== 'function') return;\n\n // \u5171\u4EAB\u300C\u9605\u8BFB\u4F53\u9A8C\u300D\u8BBE\u7F6E\u9875\uFF1A\u672C\u63D2\u4EF6\u4E0E dsh-smooth-stream\u3001dsh-oil-sticky-prompt\n // \u5171\u7528\u4E00\u9875\uFF0C\u5185\u6838\u4E0D\u5141\u8BB8 settings.section \u7684\u540C\u4E00 id \u88AB\u6CE8\u518C\u4E24\u6B21\u3001\u4E5F\u4E0D\u5141\u8BB8\u5B50 slot\n // \u88AB\u58F0\u660E\u4E24\u6B21\uFF0C\u56E0\u6B64\u4E09\u5BB6\u643A\u5E26\u540C\u4E00\u4EFD\u9875\u58F3\u3001\u5148\u5230\u5148\u5F97\u5F53\u9009\uFF1A\u5F53\u9009\u8005\u6CE8\u518C\u9875\u9762\u5E76\u58F0\u660E\n // reading.settings.item \u5B50 slot\uFF0C\u672A\u5F53\u9009\u8005\u53EA\u628A\u5361\u7247\u6CE8\u518C\u8FDB\u8BE5\u5B50 slot \u7B49\u9875\u9762\u51FA\u73B0\u3002\n // \u5171\u4EAB\u9875\u58F3\u9700\u8981\u771F ctx\uFF1A\u5B83\u7528 ctx.slots \u8BFB\u6CE8\u518C\u8868\uFF0C\u5E76\u7528 ctx.get('locale') \u5728\u8BED\u8A00\n // \u5207\u6362\u65F6\u91CD\u8BFB tab \u6807\u7B7E\uFF08\u53EF\u9009\u670D\u52A1\uFF0C\u5FC5\u987B\u8D70 ctx.get\uFF0C\u4E0D\u80FD\u7528 ctx.locale\uFF09\u3002\n slots.inject('settings.section', () =>\n claimReadingSettingsPage(ctx, () => t('pageNav'), NS)\n );\n\n slots.inject(READING_ITEM_SLOT, () =>\n slots.register(\n {\n name: READING_ITEM_SLOT,\n // id = \u672C\u63D2\u4EF6\u7684 Host \u8BBE\u7F6E\u547D\u540D\u7A7A\u95F4\uFF08settings.section \u65F6\u4EE3\u7684 id \u6CBF\u7528\uFF09\n id: SETTINGS_NAMESPACE,\n // \u5361\u7247\u5728\u5171\u4EAB\u9875\u91CC\u7684\u987A\u5E8F\uFF1A\u4E1D\u6ED1\u6D41\u5F0F 10\u3001\u5438\u9876\u63D0\u793A 20\u3001\u804A\u5929\u7FFB\u8BD1 30\n order: 30,\n // \u5171\u4EAB\u9875\u6309\u6B64\u6807\u7B7E\u6E32\u67D3 tab\n label: () => t('title'),\n locale: NS,\n },\n TidySettingsPanel\n )\n );\n } catch (err) {\n console.warn('[dsh-chat-translate] Failed to inject settings section:', err);\n }\n}\n", "import { describeError } from '../../describe-error.ts';\nimport { chatTranslateObserver } from '../translate/observer.ts';\nimport { testServerChannel } from '../translate/api.ts';\n\nexport interface ClientSettingsState {\n enabled: boolean;\n concurrency: number;\n aiEnabled: boolean;\n bingEnabled: boolean;\n baseUrl: string;\n model: string;\n aiConfigured: boolean;\n}\n\n/** Settings namespace + credentials ref, mirroring the host constants. */\nexport const SETTINGS_NAMESPACE = 'dsh-chat-translate';\nexport const TRANSLATE_API_KEY_REF = 'TRANSLATE_API_KEY';\n\n/**\n * Minimal structural shapes of the DSH browser services this store rides on:\n * `settingsScope` (from @deepseek-ai/dsh-client-ui-settings) and the\n * `credentials` Remote namespace. Keeping them structural keeps this bundle\n * free of host-service imports and lets tests inject fakes.\n */\nexport interface SettingsScopeLike {\n getSnapshot(): {\n status: string;\n value?: Record<string, unknown>;\n writable: boolean;\n };\n subscribe(listener: () => void): () => void;\n set(field: string, value: unknown): Promise<unknown>;\n unset(field: string): Promise<unknown>;\n}\n\n/** Shape of every DSH client Remote call: {ok, value} / {ok, error} wrapper. */\nexport type RemoteResult<T> =\n | { ok: true; value: T }\n | { ok: false; error: { code?: string; message: string; details?: unknown } };\n\nexport interface CredentialsRemoteLike {\n describe(\n refs: string[]\n ): Promise<RemoteResult<Record<string, { configured: boolean; source?: string; writable: boolean }>>>;\n set(ref: string, value: string): Promise<RemoteResult<void>>;\n unset(ref: string): Promise<RemoteResult<void>>;\n}\n\nconst DEFAULT_STATE: ClientSettingsState = {\n enabled: true,\n concurrency: 3,\n aiEnabled: true,\n bingEnabled: true,\n baseUrl: '',\n model: '',\n aiConfigured: false,\n};\n\n/**\n * Client settings store backed by the DSH settings namespace.\n *\n * Since 1.2 there is no localStorage overlay and no custom config HTTP\n * endpoint: the store derives from the bound `settingsScope` (which mirrors\n * the host document and folds every write answer back), and writes go\n * through `scope.set` \u2014 serialized, revision-checked, persisted by DSH.\n * Without a bound scope (e.g. non-loopback pages, unit tests) it degrades to\n * an in-memory store with the same semantics DSH itself uses for memory\n * persistence.\n */\nclass SettingsStore {\n private state: ClientSettingsState = { ...DEFAULT_STATE };\n private listeners = new Set<() => void>();\n private scope: SettingsScopeLike | null = null;\n private credentials: CredentialsRemoteLike | null = null;\n private unsubscribeScope: (() => void) | null = null;\n private keyConfigured = false;\n private writeTimer: number | null = null;\n private pendingFields = new Set<string>();\n\n /**\n * Bind the DSH services. Called once from the settings UI setup; re-binding\n * (e.g. after a reconnect) detaches the previous subscription first.\n */\n attach(scope: SettingsScopeLike | null, credentials: CredentialsRemoteLike | null): void {\n if (this.unsubscribeScope) {\n this.unsubscribeScope();\n this.unsubscribeScope = null;\n }\n this.scope = scope;\n this.credentials = credentials;\n if (scope) {\n this.unsubscribeScope = scope.subscribe(() => this.derive());\n this.derive();\n }\n void this.refreshKeyStatus();\n }\n\n getState(): ClientSettingsState {\n return { ...this.state };\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /** Map the resolved namespace value into client state and notify. */\n private derive(): void {\n if (!this.scope) return;\n const snap = this.scope.getSnapshot();\n const value = snap.value;\n if (!value || typeof value !== 'object') return;\n const next: ClientSettingsState = { ...this.state };\n if (typeof value.enabled === 'boolean') next.enabled = value.enabled;\n const c = value.concurrency;\n if (typeof c === 'number' && Number.isFinite(c)) {\n next.concurrency = Math.min(Math.max(Math.round(c), 1), 100);\n }\n if (typeof value.aiEnabled === 'boolean') next.aiEnabled = value.aiEnabled;\n if (typeof value.bingEnabled === 'boolean') next.bingEnabled = value.bingEnabled;\n if (typeof value.baseUrl === 'string') next.baseUrl = value.baseUrl;\n if (typeof value.model === 'string') next.model = value.model;\n this.applyState(next);\n }\n\n private applyState(next: ClientSettingsState): void {\n next.aiConfigured = Boolean(next.baseUrl && next.model && this.keyConfigured);\n const enabledChanged = next.enabled !== this.state.enabled;\n this.state = next;\n if (enabledChanged) {\n try {\n chatTranslateObserver.setEnabled(this.state.enabled);\n } catch {}\n }\n this.notify();\n }\n\n private notify(): void {\n for (const listener of this.listeners) {\n try {\n listener();\n } catch {}\n }\n }\n\n /** Pull the key's status-only view and re-derive aiConfigured. */\n private async refreshKeyStatus(): Promise<void> {\n if (!this.credentials) return;\n try {\n const res = await this.credentials.describe([TRANSLATE_API_KEY_REF]);\n if (!res.ok) return;\n const info = res.value[TRANSLATE_API_KEY_REF];\n const configured = Boolean(info?.configured);\n if (configured !== this.keyConfigured) {\n this.keyConfigured = configured;\n this.applyState({ ...this.state });\n }\n } catch {\n // Credentials surface unavailable \u2014 keep the last known status.\n }\n }\n\n /**\n * Optimistically apply locally, then persist each touched field through the\n * settings scope. Writes are trailing-debounced (300ms) so typing in the\n * baseUrl/model inputs collapses into a single queued mutation instead of\n * one write per keystroke \u2014 which would otherwise flash stale mirror values\n * back into the inputs between commits. A failed write makes the scope\n * reload its mirror, which re-derives this store from the host document\n * (conflict-safe recovery).\n */\n async update(partial: Partial<ClientSettingsState>): Promise<void> {\n let sanitizedConcurrency = this.state.concurrency;\n if (typeof partial.concurrency === 'number' && Number.isFinite(partial.concurrency)) {\n sanitizedConcurrency = Math.min(Math.max(Math.round(partial.concurrency), 1), 100);\n }\n const next: ClientSettingsState = {\n ...this.state,\n ...partial,\n concurrency: sanitizedConcurrency,\n };\n this.applyState(next);\n\n if (this.scope) {\n const fields = ['enabled', 'concurrency', 'aiEnabled', 'bingEnabled', 'baseUrl', 'model'] as const;\n for (const field of fields) {\n if (partial[field] !== undefined) {\n this.pendingFields.add(field);\n }\n }\n this.scheduleWrite();\n }\n }\n\n private scheduleWrite(): void {\n if (this.writeTimer !== null || typeof window === 'undefined') return;\n this.writeTimer = window.setTimeout(() => {\n this.writeTimer = null;\n void this.flushWrite();\n }, 300);\n }\n\n private async flushWrite(): Promise<void> {\n if (!this.scope) return;\n const fields = [...this.pendingFields];\n this.pendingFields.clear();\n const writes: Promise<unknown>[] = [];\n for (const field of fields) {\n writes.push(this.scope.set(field, this.state[field as keyof ClientSettingsState]));\n }\n await Promise.all(writes).catch(() => {});\n }\n\n async testChannel(channel: string): Promise<{ ok: boolean; latencyMs: number; error?: string }> {\n return testServerChannel(channel);\n }\n\n /** Write (or clear) the API key through the credentials Remote API. */\n async saveApiKey(apiKey: string): Promise<{ ok: boolean; error?: string }> {\n if (!this.credentials) {\n return { ok: false, error: '\u51ED\u636E\u670D\u52A1\u4E0D\u53EF\u7528\uFF08\u975E\u56DE\u73AF\u9875\u9762\uFF09' };\n }\n try {\n const key = apiKey.trim();\n // Remote calls resolve with {ok:false} instead of rejecting on refusal\n // (e.g. env-shadowed refs), so the .ok check decides the outcome.\n const res = key\n ? await this.credentials.set(TRANSLATE_API_KEY_REF, key)\n : await this.credentials.unset(TRANSLATE_API_KEY_REF);\n if (!res.ok) {\n return { ok: false, error: res.error?.message || '\u4FDD\u5B58\u5931\u8D25' };\n }\n await this.refreshKeyStatus();\n return { ok: true };\n } catch (err: any) {\n return { ok: false, error: describeError(err) };\n }\n }\n\n dispose(): void {\n if (this.writeTimer !== null && typeof window !== 'undefined') {\n clearTimeout(this.writeTimer);\n this.writeTimer = null;\n }\n this.pendingFields.clear();\n if (this.unsubscribeScope) {\n this.unsubscribeScope();\n this.unsubscribeScope = null;\n }\n this.scope = null;\n this.credentials = null;\n this.listeners.clear();\n }\n}\n\nexport const settingsStore = new SettingsStore();\n", "export const SETTINGS_CSS = String.raw`\n.dsh-tidy-settings {\n display: flex;\n flex-direction: column;\n gap: 16px;\n max-width: 580px;\n padding-bottom: 32px;\n color: var(--dsw-alias-label-primary, inherit);\n font-family: inherit;\n}\n\n.dsh-tidy-card {\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.2));\n border-radius: 12px;\n padding: 16px 18px;\n background: var(--dsw-alias-bg-card, rgba(128, 128, 128, 0.05));\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.dsh-tidy-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary, inherit);\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n\n.dsh-tidy-desc {\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary, rgba(128, 128, 128, 0.8));\n}\n\n.dsh-tidy-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 14px;\n padding: 8px 0;\n}\n\n.dsh-tidy-row + .dsh-tidy-row {\n border-top: 1px solid var(--dsw-alias-border-l3, rgba(128, 128, 128, 0.1));\n}\n\n.dsh-tidy-row-info {\n flex: 1;\n min-width: 0;\n}\n\n.dsh-tidy-row-title {\n font-size: 13px;\n font-weight: 500;\n}\n\n.dsh-tidy-row-desc {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary, rgba(128, 128, 128, 0.7));\n margin-top: 2px;\n line-height: 1.4;\n}\n\n/* Switch */\n.dsh-tidy-switch {\n position: relative;\n width: 38px;\n height: 22px;\n flex: none;\n cursor: pointer;\n border-radius: 999px;\n border: none;\n background: rgba(128, 128, 128, 0.3);\n transition: background 0.15s ease;\n padding: 0;\n outline: none;\n}\n\n.dsh-tidy-switch[aria-checked=\"true\"] {\n background: #3b82f6;\n}\n\n.dsh-tidy-switch::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: #ffffff;\n transition: transform 0.15s ease;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n}\n\n.dsh-tidy-switch[aria-checked=\"true\"]::after {\n transform: translateX(16px);\n}\n\n/* Inputs */\n.dsh-tidy-input-group {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.dsh-tidy-label {\n font-size: 12px;\n font-weight: 500;\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n\n.dsh-tidy-input-row {\n display: flex;\n gap: 8px;\n}\n\n.dsh-tidy-input {\n flex: 1;\n min-width: 0;\n padding: 8px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.25));\n background: var(--dsw-alias-bg-input, rgba(0, 0, 0, 0.05));\n color: inherit;\n font-size: 12px;\n outline: none;\n transition: border-color 0.15s;\n}\n\n.dsh-tidy-input:focus {\n border-color: #3b82f6;\n}\n\n.dsh-tidy-btn {\n padding: 6px 14px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.3));\n background: transparent;\n color: inherit;\n font-size: 12px;\n cursor: pointer;\n transition: all 0.15s ease;\n white-space: nowrap;\n}\n\n.dsh-tidy-btn:hover {\n background: rgba(128, 128, 128, 0.1);\n border-color: rgba(128, 128, 128, 0.5);\n}\n\n.dsh-tidy-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.dsh-tidy-badge {\n font-size: 10px;\n padding: 2px 6px;\n border-radius: 4px;\n font-weight: 500;\n}\n\n.dsh-tidy-badge-ok {\n background: rgba(34, 197, 94, 0.15);\n color: #22c55e;\n}\n\n.dsh-tidy-badge-none {\n background: rgba(156, 163, 175, 0.15);\n color: #9ca3af;\n}\n\n.dsh-tidy-badge-warn {\n background: rgba(245, 158, 11, 0.15);\n color: #f59e0b;\n}\n\n.dsh-tidy-test-result {\n font-size: 12px;\n margin-left: 10px;\n}\n\n.dsh-tidy-test-result.ok {\n color: #22c55e;\n}\n\n.dsh-tidy-test-result.fail {\n color: #ef4444;\n}\n\n.dsh-tidy-behavior-list {\n padding-left: 18px;\n margin: 4px 0 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n/* Priority list */\n.dsh-tidy-priority-list {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.dsh-tidy-priority-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 12px;\n border-radius: 8px;\n background: rgba(128, 128, 128, 0.06);\n border: 1px solid var(--dsw-alias-border-l3, rgba(128, 128, 128, 0.12));\n}\n\n.dsh-tidy-priority-name {\n font-size: 12px;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.dsh-tidy-order-badge {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: #3b82f6;\n color: #ffffff;\n font-size: 11px;\n font-weight: 600;\n}\n\n.dsh-tidy-btn-group {\n display: flex;\n gap: 4px;\n}\n\n.dsh-tidy-icon-btn {\n width: 26px;\n height: 26px;\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 6px;\n border: 1px solid rgba(128, 128, 128, 0.2);\n background: transparent;\n color: inherit;\n cursor: pointer;\n font-size: 12px;\n}\n\n.dsh-tidy-icon-btn:hover:not(:disabled) {\n background: rgba(128, 128, 128, 0.15);\n}\n\n.dsh-tidy-icon-btn:disabled {\n opacity: 0.3;\n cursor: default;\n}\n\n/* Slider / Select */\n.dsh-tidy-select {\n padding: 6px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.3));\n background: var(--dsw-alias-bg-input, rgba(0, 0, 0, 0.05));\n color: inherit;\n font-size: 12px;\n outline: none;\n}\n`;\n", "/**\n * The shared \u300C\u9605\u8BFB\u4F53\u9A8C\u300D settings page.\n *\n * Several plugins contribute their configuration to ONE settings page, but the\n * kernel cannot declare that page jointly: `settings.section` is a list slot\n * that rejects a duplicate `id` at the same priority (\"already has an entry\n * with id\"), and a child slot may be declared exactly once (\"slot \u2026 is already\n * declared\"). Composing several cards into one page therefore takes one\n * declarer, so every participating plugin carries this same shell and the\n * FIRST one to activate claims the page; the others register their card into\n * {@link READING_ITEM_SLOT} and wait for the winner's declaration through\n * `slots.inject`. Uninstalling the winner promotes another participant on the\n * next boot, so no participant is a fixed owner.\n *\n * The page renders ONE TAB PER REGISTERED CARD: the tab roster comes from the\n * child slot's own registrations (id + `label` + `order`, the same shape the\n * kernel's own Plugins page uses for its tabs), and each panel dispatches\n * through `renderSlot(READING_ITEM_SLOT, {}, { only: id })`. Every panel stays\n * mounted but hidden, so a card's local state survives a tab switch.\n *\n * Keep this file identical across the participating plugins\n * (`dsh-smooth-stream`, `dsh-oil-sticky-prompt`, `dsh-chat-translate`).\n * Participants own their own card component, locale dictionaries, settings\n * namespace and Host half \u2014 only the page shell below is shared, because\n * cross-plugin value imports are forbidden by the client bundle purity gate.\n * It imports nothing but `react` plus type-only DSH packages on purpose, so\n * every participant's build configuration compiles it unchanged.\n */\n\nimport { createElement, useState, useSyncExternalStore } from 'react'\nimport type { Context as ClientContext } from '@deepseek-ai/cordis'\nimport type { InjectFace, LocaleNamespaceMap, PropsRenderSlots, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\n\n/** Page id claimed by the first participating plugin to activate. */\nexport const READING_PAGE_ID = 'reading'\n/** Sidebar position of the shared page; own plugins start at 110. */\nexport const READING_PAGE_ORDER = 110\n/** The page's one child slot: every participant's card registers here. */\nexport const READING_ITEM_SLOT = 'reading.settings.item'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface SlotMap {\n /** One participating plugin's card inside the shared reading page. */\n 'reading.settings.item': { kind: 'list'; scope: 'root' }\n }\n}\n\n/** One participant's tab, derived from its card registration. */\nexport interface ReadingTab {\n /** Registration id \u2014 the participant's Host settings namespace. */\n id: string\n /** Registration order, ascending in the tab bar. */\n order: number\n /** Registration label, already resolved for the active locale. */\n label: string\n}\n\n/** Live tab roster handed to the page component through its inject face. */\nexport interface ReadingTabsFace {\n getSnapshot(): readonly ReadingTab[]\n subscribe(listener: () => void): () => void\n}\n\n/** Inject face of the shared page. */\nexport interface ReadingPageFace {\n readingTabs: ReadingTabsFace\n}\n\ntype ReadingPageProps =\n PropsRuntime<'settings.section'>\n & PropsRenderSlots<typeof READING_ITEM_SLOT>\n & InjectFace<ReadingPageFace>\n\n/** A registration label is a plain string or a thunk re-read per projection. */\nfunction readLabel(label: unknown): string {\n if (typeof label === 'function') return (label as () => string)()\n return typeof label === 'string' ? label : ''\n}\n\n/**\n * Tab chrome mirrors the kernel's own settings tabs (`.tabs` / `.tab` in\n * `ui-settings-plugins`): tertiary label, 13px, 22px gutter. The marker is the\n * ACTIVE TAB'S OWN bottom border and the bar draws no rail of its own \u2014 a\n * shared underline reads as \"every tab is selected\", which is exactly the bug\n * this replaces.\n */\nconst TABLIST_STYLE = {\n display: 'flex',\n alignItems: 'flex-end',\n gap: '22px',\n marginTop: '2px',\n marginBottom: '16px',\n} as const\n\nconst TAB_STYLE = {\n appearance: 'none',\n background: 'transparent',\n // No border on ANY tab: the marker below is the active tab's own element, so\n // an inactive tab has nothing that could render a line.\n border: 'none',\n position: 'relative',\n padding: '7px 1px 11px',\n cursor: 'pointer',\n font: 'inherit',\n fontSize: '13px',\n lineHeight: '20px',\n color: 'var(--dsw-alias-label-tertiary, inherit)',\n} as const\n\nconst TAB_ACTIVE_STYLE = {\n ...TAB_STYLE,\n color: 'var(--dsw-alias-label-primary, inherit)',\n} as const\n\n/** The kernel's own tab marker: a 2px rounded bar under the active label. */\nconst TAB_MARKER_STYLE = {\n position: 'absolute',\n left: 0,\n right: 0,\n bottom: 0,\n height: '2px',\n borderRadius: '2px 2px 0 0',\n background: 'var(--dsw-alias-label-primary, currentColor)',\n} as const\n\n/** Panels stay mounted (hidden) so each card keeps its local state. */\nconst PANEL_STYLE = { listStyle: 'none', margin: 0, padding: 0 } as const\nconst PANEL_HIDDEN_STYLE = { ...PANEL_STYLE, display: 'none' } as const\n\n/**\n * Build the live tab roster over the child slot's registrations. `locale` is\n * read through `ctx.get`: a participant needs it only to re-read localized\n * labels on a language switch, and reaching an undeclared service as\n * `ctx.locale` would trip the kernel's inject guard.\n * @param ctx - browser context carrying the slot registry.\n * @returns The tab store consumed by the page component.\n */\nfunction createReadingTabs(ctx: ClientContext): ReadingTabsFace {\n const locale = ctx.get('locale') as\n | { getSnapshot(): { revision: number }; subscribe(listener: () => void): () => void }\n | undefined\n let version = -1\n let revision = -1\n let tabs: readonly ReadingTab[] = []\n return {\n getSnapshot: () => {\n const nextVersion = ctx.slots.getVersion(READING_ITEM_SLOT)\n const nextRevision = locale === undefined ? 0 : locale.getSnapshot().revision\n if (nextVersion === version && nextRevision === revision) return tabs\n version = nextVersion\n revision = nextRevision\n tabs = ctx.slots.entries(READING_ITEM_SLOT)\n .map(entry => ({\n id: entry.options.id ?? '',\n order: entry.options.order ?? 0,\n label: readLabel(entry.options.label),\n }))\n .sort((left, right) => left.order - right.order)\n return tabs\n },\n subscribe: (listener) => {\n const offSlots = ctx.slots.subscribe(READING_ITEM_SLOT, listener)\n const offLocale = locale?.subscribe(listener)\n return () => {\n offSlots()\n offLocale?.()\n }\n },\n }\n}\n\n/**\n * Page body: one tab per registered card, plus the selected card's panel.\n * The shell supplies the section's own seats and `renderSlot` bound to the\n * child slot declared at registration time.\n */\nexport function ReadingSettingsSection({ renderSlot, readingTabs }: ReadingPageProps) {\n const tabs = useSyncExternalStore(\n readingTabs.subscribe,\n readingTabs.getSnapshot,\n readingTabs.getSnapshot,\n )\n const [requested, setRequested] = useState<string | null>(null)\n const selected = requested !== null && tabs.some(tab => tab.id === requested)\n ? requested\n : tabs[0]?.id ?? null\n if (selected === null) return null\n return createElement(\n 'div',\n null,\n createElement(\n 'div',\n { role: 'tablist', style: TABLIST_STYLE },\n tabs.map(tab => createElement(\n 'button',\n {\n key: tab.id,\n type: 'button',\n role: 'tab',\n 'aria-selected': tab.id === selected,\n style: tab.id === selected ? TAB_ACTIVE_STYLE : TAB_STYLE,\n onClick: () => { setRequested(tab.id) },\n },\n tab.label,\n tab.id === selected ? createElement('span', { style: TAB_MARKER_STYLE, 'aria-hidden': true }) : null,\n )),\n ),\n tabs.map(tab => createElement(\n 'ul',\n {\n key: tab.id,\n role: 'tabpanel',\n hidden: tab.id !== selected,\n style: tab.id === selected ? PANEL_STYLE : PANEL_HIDDEN_STYLE,\n },\n renderSlot(READING_ITEM_SLOT, {}, { only: tab.id }),\n )),\n )\n}\n\n/** Whether a participant already holds the shared page. */\nexport function readingPageClaimed(ctx: ClientContext): boolean {\n return ctx.slots.entries('settings.section').some(entry => entry.options.id === READING_PAGE_ID)\n}\n\n/**\n * Claim the shared page when no participant holds it yet. Call inside\n * `ctx.slots.inject('settings.section', \u2026)`: injection order decides the\n * winner, and the losers stay silent instead of colliding with the kernel's\n * duplicate-id and duplicate-declaration guards.\n * @param ctx - browser context carrying the slot registry.\n * @param label - page label, re-read by the shell on every projection.\n * @param locale - locale namespace the label thunk translates through.\n * @returns The page registration's disposer, or a no-op when another\n * participant already holds the page.\n */\nexport function claimReadingSettingsPage(\n ctx: ClientContext,\n label: () => string,\n locale: keyof LocaleNamespaceMap & string,\n): () => void {\n if (readingPageClaimed(ctx)) return () => {}\n const readingTabs = createReadingTabs(ctx)\n return ctx.slots.register({\n name: 'settings.section',\n id: READING_PAGE_ID,\n order: READING_PAGE_ORDER,\n label,\n locale,\n inject: () => ({ readingTabs }),\n children: { 'reading.settings.item': { kind: 'list', scope: 'root' } },\n }, ReadingSettingsSection)\n}\n", "/** Locale bundles for the chat-translate card inside the shared \u300C\u9605\u8BFB\u4F53\u9A8C\u300D page. */\n\n/** Dictionary namespace owned by this plugin's settings card. */\nexport const NS = 'settings.chatTranslate';\n\n/** Locale keys this plugin renders. */\nexport type ChatTranslateLocaleKey = 'pageNav' | 'title';\n\n/** English copy. */\nexport const en: Record<ChatTranslateLocaleKey, string> = {\n pageNav: 'Reading',\n title: 'Chat translate',\n};\n\n/** Chinese copy. */\nexport const zh: Record<ChatTranslateLocaleKey, string> = {\n pageNav: '\u9605\u8BFB\u4F53\u9A8C',\n title: '\u804A\u5929\u7FFB\u8BD1',\n};\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n 'settings.chatTranslate': ChatTranslateLocaleKey;\n }\n}\n"],
|
|
5
|
+
"mappings": "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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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",
|
|
6
|
+
"names": ["cached", "import_react"]
|
|
7
7
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/** Client plugin name, shared with the browser bundle id. */
|
|
2
2
|
export declare const name = "dsh-chat-translate";
|
|
3
3
|
/**
|
|
4
|
-
* Declared services: slots for the settings
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Declared services: slots for the shared 「阅读体验」 settings page (page claim
|
|
5
|
+
* plus the card registration), settingsScope for the per-namespace settings
|
|
6
|
+
* mirror, and the remote + remote.credentials pair for the credentials Remote
|
|
7
|
+
* namespace — the runtime withholds any service not declared here, so
|
|
8
|
+
* `ctx.remote.credentials` would be undefined otherwise. The locale service is
|
|
9
|
+
* optional and read through `ctx.get`, like the renderer's slots registry.
|
|
8
10
|
*/
|
|
9
11
|
export declare const inject: string[];
|
|
10
12
|
interface ClientContext {
|
|
@@ -12,12 +14,14 @@ interface ClientContext {
|
|
|
12
14
|
get?(serviceName: string): any;
|
|
13
15
|
slots?: any;
|
|
14
16
|
settingsScope?: any;
|
|
17
|
+
locale?: any;
|
|
15
18
|
remote?: {
|
|
16
19
|
credentials?: any;
|
|
17
20
|
};
|
|
18
21
|
}
|
|
19
22
|
/**
|
|
20
|
-
* Mount the tool-call / think-summary translation observer and the settings UI
|
|
23
|
+
* Mount the tool-call / think-summary translation observer and the settings UI
|
|
24
|
+
* card inside the shared reading-settings page.
|
|
21
25
|
* @param ctx - DSH browser client context.
|
|
22
26
|
*/
|
|
23
27
|
export declare function apply(ctx: ClientContext): void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Locale bundles for the chat-translate card inside the shared 「阅读体验」 page. */
|
|
2
|
+
/** Dictionary namespace owned by this plugin's settings card. */
|
|
3
|
+
export declare const NS = "settings.chatTranslate";
|
|
4
|
+
/** Locale keys this plugin renders. */
|
|
5
|
+
export type ChatTranslateLocaleKey = 'pageNav' | 'title';
|
|
6
|
+
/** English copy. */
|
|
7
|
+
export declare const en: Record<ChatTranslateLocaleKey, string>;
|
|
8
|
+
/** Chinese copy. */
|
|
9
|
+
export declare const zh: Record<ChatTranslateLocaleKey, string>;
|
|
10
|
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
11
|
+
interface LocaleNamespaceMap {
|
|
12
|
+
'settings.chatTranslate': ChatTranslateLocaleKey;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared 「阅读体验」 settings page.
|
|
3
|
+
*
|
|
4
|
+
* Several plugins contribute their configuration to ONE settings page, but the
|
|
5
|
+
* kernel cannot declare that page jointly: `settings.section` is a list slot
|
|
6
|
+
* that rejects a duplicate `id` at the same priority ("already has an entry
|
|
7
|
+
* with id"), and a child slot may be declared exactly once ("slot … is already
|
|
8
|
+
* declared"). Composing several cards into one page therefore takes one
|
|
9
|
+
* declarer, so every participating plugin carries this same shell and the
|
|
10
|
+
* FIRST one to activate claims the page; the others register their card into
|
|
11
|
+
* {@link READING_ITEM_SLOT} and wait for the winner's declaration through
|
|
12
|
+
* `slots.inject`. Uninstalling the winner promotes another participant on the
|
|
13
|
+
* next boot, so no participant is a fixed owner.
|
|
14
|
+
*
|
|
15
|
+
* The page renders ONE TAB PER REGISTERED CARD: the tab roster comes from the
|
|
16
|
+
* child slot's own registrations (id + `label` + `order`, the same shape the
|
|
17
|
+
* kernel's own Plugins page uses for its tabs), and each panel dispatches
|
|
18
|
+
* through `renderSlot(READING_ITEM_SLOT, {}, { only: id })`. Every panel stays
|
|
19
|
+
* mounted but hidden, so a card's local state survives a tab switch.
|
|
20
|
+
*
|
|
21
|
+
* Keep this file identical across the participating plugins
|
|
22
|
+
* (`dsh-smooth-stream`, `dsh-oil-sticky-prompt`, `dsh-chat-translate`).
|
|
23
|
+
* Participants own their own card component, locale dictionaries, settings
|
|
24
|
+
* namespace and Host half — only the page shell below is shared, because
|
|
25
|
+
* cross-plugin value imports are forbidden by the client bundle purity gate.
|
|
26
|
+
* It imports nothing but `react` plus type-only DSH packages on purpose, so
|
|
27
|
+
* every participant's build configuration compiles it unchanged.
|
|
28
|
+
*/
|
|
29
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
30
|
+
import type { InjectFace, LocaleNamespaceMap, PropsRenderSlots, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
31
|
+
/** Page id claimed by the first participating plugin to activate. */
|
|
32
|
+
export declare const READING_PAGE_ID = "reading";
|
|
33
|
+
/** Sidebar position of the shared page; own plugins start at 110. */
|
|
34
|
+
export declare const READING_PAGE_ORDER = 110;
|
|
35
|
+
/** The page's one child slot: every participant's card registers here. */
|
|
36
|
+
export declare const READING_ITEM_SLOT = "reading.settings.item";
|
|
37
|
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
38
|
+
interface SlotMap {
|
|
39
|
+
/** One participating plugin's card inside the shared reading page. */
|
|
40
|
+
'reading.settings.item': {
|
|
41
|
+
kind: 'list';
|
|
42
|
+
scope: 'root';
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** One participant's tab, derived from its card registration. */
|
|
47
|
+
export interface ReadingTab {
|
|
48
|
+
/** Registration id — the participant's Host settings namespace. */
|
|
49
|
+
id: string;
|
|
50
|
+
/** Registration order, ascending in the tab bar. */
|
|
51
|
+
order: number;
|
|
52
|
+
/** Registration label, already resolved for the active locale. */
|
|
53
|
+
label: string;
|
|
54
|
+
}
|
|
55
|
+
/** Live tab roster handed to the page component through its inject face. */
|
|
56
|
+
export interface ReadingTabsFace {
|
|
57
|
+
getSnapshot(): readonly ReadingTab[];
|
|
58
|
+
subscribe(listener: () => void): () => void;
|
|
59
|
+
}
|
|
60
|
+
/** Inject face of the shared page. */
|
|
61
|
+
export interface ReadingPageFace {
|
|
62
|
+
readingTabs: ReadingTabsFace;
|
|
63
|
+
}
|
|
64
|
+
type ReadingPageProps = PropsRuntime<'settings.section'> & PropsRenderSlots<typeof READING_ITEM_SLOT> & InjectFace<ReadingPageFace>;
|
|
65
|
+
/**
|
|
66
|
+
* Page body: one tab per registered card, plus the selected card's panel.
|
|
67
|
+
* The shell supplies the section's own seats and `renderSlot` bound to the
|
|
68
|
+
* child slot declared at registration time.
|
|
69
|
+
*/
|
|
70
|
+
export declare function ReadingSettingsSection({ renderSlot, readingTabs }: ReadingPageProps): import("react").DetailedReactHTMLElement<import("react").HTMLAttributes<HTMLElement>, HTMLElement> | null;
|
|
71
|
+
/** Whether a participant already holds the shared page. */
|
|
72
|
+
export declare function readingPageClaimed(ctx: ClientContext): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Claim the shared page when no participant holds it yet. Call inside
|
|
75
|
+
* `ctx.slots.inject('settings.section', …)`: injection order decides the
|
|
76
|
+
* winner, and the losers stay silent instead of colliding with the kernel's
|
|
77
|
+
* duplicate-id and duplicate-declaration guards.
|
|
78
|
+
* @param ctx - browser context carrying the slot registry.
|
|
79
|
+
* @param label - page label, re-read by the shell on every projection.
|
|
80
|
+
* @param locale - locale namespace the label thunk translates through.
|
|
81
|
+
* @returns The page registration's disposer, or a no-op when another
|
|
82
|
+
* participant already holds the page.
|
|
83
|
+
*/
|
|
84
|
+
export declare function claimReadingSettingsPage(ctx: ClientContext, label: () => string, locale: keyof LocaleNamespaceMap & string): () => void;
|
|
85
|
+
export {};
|
|
@@ -1,3 +1,17 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
|
|
2
|
+
import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
3
|
+
/**
|
|
4
|
+
* Chat-translate's card inside the shared 「阅读体验」 settings page. The card
|
|
5
|
+
* keeps its own chrome (title rows, switches, credential form); the shared page
|
|
6
|
+
* only stacks the participants' cards.
|
|
7
|
+
* @param _props - renderer-bound seat of the `reading.settings.item` slot.
|
|
8
|
+
* @returns The card element.
|
|
9
|
+
*/
|
|
10
|
+
export declare function TidySettingsPanel(_props: PropsRuntime<'reading.settings.item'>): React.ReactElement;
|
|
11
|
+
/**
|
|
12
|
+
* Bind the settings store to DSH's native settings/credentials services and
|
|
13
|
+
* join the shared 「阅读体验」 settings page: whichever participant activates
|
|
14
|
+
* first claims the page, everyone registers a card into its child slot.
|
|
15
|
+
* @param ctx - DSH browser client context; services are resolved defensively.
|
|
16
|
+
*/
|
|
3
17
|
export declare function setupSettingsUi(ctx: any): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lynn123411/dsh-chat-translate",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Tool-call & think-summary translation with dual AI (OpenAI-compatible) + Bing channels for the DeepSeek Harness Web UI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "lynn123411",
|
|
@@ -60,12 +60,16 @@
|
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "node build.mjs",
|
|
62
62
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
63
|
-
"test": "node scripts/test-regression-suite.mjs && node scripts/test-channel-logic.mjs && node scripts/test-store.mjs && node scripts/test-toggle-restore.mjs"
|
|
63
|
+
"test": "node scripts/test-client-wiring.mjs && node scripts/test-regression-suite.mjs && node scripts/test-channel-logic.mjs && node scripts/test-store.mjs && node scripts/test-toggle-restore.mjs"
|
|
64
64
|
},
|
|
65
65
|
"engines": {
|
|
66
66
|
"node": "^22.19 || >=24"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
|
+
"@deepseek-ai/cordis": "4.0.2",
|
|
70
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.1.5-rc.1",
|
|
71
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.5-rc.1",
|
|
72
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.5-rc.1",
|
|
69
73
|
"@deepseek-ai/dsh-home-paths": "^0.1.5-rc.1",
|
|
70
74
|
"@deepseek-ai/schemastery": "^3.18.2",
|
|
71
75
|
"@eslint/js": "^9.30.0",
|