@lynn123411/dsh-chat-translate 1.2.1 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@ DeepSeek Harness Web 界面的工具调用与思考摘要智能翻译插件。
4
4
 
5
5
  ## 特性
6
6
 
7
- - **工具调用与思考摘要自动翻译**:仅在渲染层将工具调用动作描述(如 `Locate DSH home directory structure`)与思考折叠摘要自动翻译为中文,对话正文永不翻译,保持思考正文原生不干扰。
7
+ - **工具调用与思考摘要自动翻译**:仅在渲染层将工具调用动作描述(如 `Locate DSH home directory structure`)与思考折叠摘要自动翻译为中文,对话正文永不翻译,保持思考正文原生不干扰。思考折叠摘要只在思考**完全结束**后才翻译,绝不翻译思考过程中的流式中间态。
8
8
  - **AI 翻译通道(OpenAI 兼容协议)**:对接任意 OpenAI 兼容的 `chat/completions` 服务(OpenAI、DeepSeek、通义、Ollama 等),Base URL 与模型可在设置面板配置;API Key 经 DSH 凭据服务读写 `~/.dsh/.credentials.yaml` 的 `TRANSLATE_API_KEY`,填写后立即生效。
9
9
  - **Bing 免 Key 兜底通道**:内置微软 Bing 网页翻译(免 Key、国内直连),AI 未配置或请求失败时自动兜底;双通道均有独立开关,同时关闭则不翻译。
10
10
  - **非侵入式 DOM 挂载与双语对照**:非侵入式包装保留原始 DOM 节点与 React Fiber 事件系统;点击译文可原地在原文与中文之间切换。
package/lib/client.js CHANGED
@@ -205,7 +205,6 @@ var NonDestructiveTranslationMount = class {
205
205
  if (transWrapper && origWrapper) {
206
206
  transWrapper.textContent = translated;
207
207
  element.dataset.tidyTranslated = "true";
208
- if (options.isThink) element.dataset.tidyThink = "true";
209
208
  if (options.originalText) element.dataset.original = options.originalText;
210
209
  return;
211
210
  }
@@ -242,9 +241,6 @@ var NonDestructiveTranslationMount = class {
242
241
  element.appendChild(origWrapper);
243
242
  element.dataset.tidyTranslated = "true";
244
243
  element.dataset.original = originalText;
245
- if (options.isThink) {
246
- element.dataset.tidyThink = "true";
247
- }
248
244
  }
249
245
  /**
250
246
  * Unmounts translation and restores original DOM nodes completely.
@@ -269,7 +265,6 @@ var NonDestructiveTranslationMount = class {
269
265
  }
270
266
  delete element.dataset.tidyTranslated;
271
267
  delete element.dataset.original;
272
- delete element.dataset.tidyThink;
273
268
  }
274
269
  /**
275
270
  * Checks if an element has non-destructive translation mounted.
@@ -331,11 +326,9 @@ var StreamDebounceViewportObserver = class {
331
326
  const el = entry.target;
332
327
  this.intersectionObserver?.unobserve(el);
333
328
  const text = el.dataset.tidyPendingText || el.textContent?.trim() || "";
334
- const isThink = el.dataset.tidyPendingThink === "true";
335
329
  if (text) {
336
330
  delete el.dataset.tidyPendingText;
337
- delete el.dataset.tidyPendingThink;
338
- this.enqueueBatch(el, text, isThink);
331
+ this.enqueueBatch(el, text);
339
332
  }
340
333
  }
341
334
  }
@@ -352,7 +345,7 @@ var StreamDebounceViewportObserver = class {
352
345
  * Observe an element with streaming debounce.
353
346
  * If streaming updates characterData repeatedly within debounceMs, the timer resets.
354
347
  */
355
- observeWithDebounce(element, text, immediate = false, isThink = false) {
348
+ observeWithDebounce(element, text, immediate = false) {
356
349
  if (!element || !text) return;
357
350
  const existingTimer = this.streamingTimers.get(element);
358
351
  if (existingTimer !== void 0) {
@@ -360,30 +353,29 @@ var StreamDebounceViewportObserver = class {
360
353
  this.streamingTimers.delete(element);
361
354
  }
362
355
  if (immediate || this.options.debounceMs <= 0) {
363
- this.registerForViewport(element, text, isThink);
356
+ this.registerForViewport(element, text);
364
357
  return;
365
358
  }
366
359
  const timer = window.setTimeout(() => {
367
360
  this.streamingTimers.delete(element);
368
361
  if (element.isConnected) {
369
362
  const latestText = element.textContent?.trim() || text;
370
- this.registerForViewport(element, latestText, isThink);
363
+ this.registerForViewport(element, latestText);
371
364
  }
372
365
  }, this.options.debounceMs);
373
366
  this.streamingTimers.set(element, timer);
374
367
  }
375
- registerForViewport(element, text, isThink = false) {
368
+ registerForViewport(element, text) {
376
369
  if (!element.isConnected) return;
377
370
  if (!this.intersectionObserver) {
378
- this.enqueueBatch(element, text, isThink);
371
+ this.enqueueBatch(element, text);
379
372
  return;
380
373
  }
381
374
  element.dataset.tidyPendingText = text;
382
- if (isThink) element.dataset.tidyPendingThink = "true";
383
375
  this.intersectionObserver.observe(element);
384
376
  }
385
- enqueueBatch(element, text, isThink = false) {
386
- this.pendingQueue.push({ element, text, isThink });
377
+ enqueueBatch(element, text) {
378
+ this.pendingQueue.push({ element, text });
387
379
  if (this.batchFlushTimer === null && typeof window !== "undefined") {
388
380
  this.batchFlushTimer = window.setTimeout(() => {
389
381
  this.batchFlushTimer = null;
@@ -404,7 +396,6 @@ var StreamDebounceViewportObserver = class {
404
396
  this.streamingTimers.delete(element);
405
397
  }
406
398
  delete element.dataset.tidyPendingText;
407
- delete element.dataset.tidyPendingThink;
408
399
  if (this.intersectionObserver) {
409
400
  this.intersectionObserver.unobserve(element);
410
401
  }
@@ -439,14 +430,14 @@ var LazyTranslationQueue = class {
439
430
  this.viewportObserver.disconnect();
440
431
  }
441
432
  }
442
- observe(element, text, immediate = false, isThink = false) {
433
+ observe(element, text, immediate = false) {
443
434
  if (!this.enabled || !element.isConnected) return;
444
435
  const cached = clientCache.get(text);
445
436
  if (cached) {
446
- this.applyTranslation(element, cached, text, isThink);
437
+ this.applyTranslation(element, cached, text);
447
438
  return;
448
439
  }
449
- this.viewportObserver.observeWithDebounce(element, text, immediate, isThink);
440
+ this.viewportObserver.observeWithDebounce(element, text, immediate);
450
441
  }
451
442
  async handleVisibleBatch(items) {
452
443
  if (!this.enabled || items.length === 0) return;
@@ -460,11 +451,11 @@ var LazyTranslationQueue = class {
460
451
  if (!item.element.isConnected) continue;
461
452
  const cached = clientCache.get(item.text);
462
453
  if (cached) {
463
- this.applyTranslation(item.element, cached, item.text, item.isThink);
454
+ this.applyTranslation(item.element, cached, item.text);
464
455
  continue;
465
456
  }
466
457
  const list = textMap.get(item.text) || [];
467
- list.push({ element: item.element, isThink: item.isThink });
458
+ list.push(item.element);
468
459
  textMap.set(item.text, list);
469
460
  }
470
461
  const uniqueTexts = Array.from(textMap.keys());
@@ -475,8 +466,8 @@ var LazyTranslationQueue = class {
475
466
  clientCache.set(res.original, res.translated);
476
467
  const entries = textMap.get(res.original) || [];
477
468
  for (const entry of entries) {
478
- if (entry.element.isConnected && this.enabled) {
479
- this.applyTranslation(entry.element, res.translated, res.original, entry.isThink);
469
+ if (entry.isConnected && this.enabled) {
470
+ this.applyTranslation(entry, res.translated, res.original);
480
471
  }
481
472
  }
482
473
  } else if (res.channel === "fallback" || res.channel === "fallback-client") {
@@ -484,11 +475,10 @@ var LazyTranslationQueue = class {
484
475
  }
485
476
  }
486
477
  }
487
- applyTranslation(element, translated, original, isThink) {
478
+ applyTranslation(element, translated, original) {
488
479
  if (!element.isConnected || !this.enabled) return;
489
480
  NonDestructiveTranslationMount.mount(element, translated, {
490
- originalText: original,
491
- isThink: isThink || element.dataset.tidyThink === "true"
481
+ originalText: original
492
482
  });
493
483
  }
494
484
  disconnect() {
@@ -498,13 +488,13 @@ var LazyTranslationQueue = class {
498
488
  var lazyQueue = new LazyTranslationQueue();
499
489
 
500
490
  // src/client/translate/observer.ts
501
- var TOOL_TITLE_SELECTOR = '[class*="summary"]';
491
+ var TOOL_TITLE_SELECTOR = '[class*="summary" i]';
502
492
  var SESSION_ROOT_SELECTOR = "[data-conversation-scroll], [data-chat-flow]";
503
493
  var ROOT_CHECK_INTERVAL_MS = 3e3;
504
494
  function isToolSummarySpan(span) {
505
495
  if (!span || span.nodeType !== 1) return false;
506
496
  if (span.hasAttribute("aria-hidden")) return false;
507
- if (span.querySelector?.('[class*="title"], [class*="leading"], [class*="chevron"], [class*="sep"], [class*="summary"]')) {
497
+ if (span.querySelector?.('[class*="title"], [class*="leading"], [class*="chevron"], [class*="sep"], [class*="summary" i]')) {
508
498
  return false;
509
499
  }
510
500
  const cls = span.className || "";
@@ -524,6 +514,10 @@ function isToolSummarySpan(span) {
524
514
  }
525
515
  return false;
526
516
  }
517
+ function thinkBlockStillRunning(span) {
518
+ const thinkRoot = span.closest('[data-variant="think"]');
519
+ return !!thinkRoot && thinkRoot.dataset.state === "running";
520
+ }
527
521
  var ChatTranslateObserver = class {
528
522
  observer = null;
529
523
  rootElement = null;
@@ -671,6 +665,7 @@ var ChatTranslateObserver = class {
671
665
  });
672
666
  }
673
667
  processSpan(span) {
668
+ if (thinkBlockStillRunning(span)) return;
674
669
  if (NonDestructiveTranslationMount.isMounted(span)) {
675
670
  const original = NonDestructiveTranslationMount.getOriginal(span);
676
671
  if (original) {
@@ -1250,7 +1245,7 @@ function TidySettingsPanel() {
1250
1245
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-tidy-desc", children: [
1251
1246
  "\u81EA\u52A8\u5C06\u5F53\u524D\u4F1A\u8BDD\u4E2D\u5DE5\u5177\u8C03\u7528\u6807\u9898\u4E0E\u601D\u8003\u6298\u53E0\u6458\u8981\uFF08\u5982 ",
1252
1247
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "Locate DSH home directory structure" }),
1253
- "\uFF09\u7FFB\u8BD1\u4E3A\u4E2D\u6587\uFF0C \u70B9\u51FB\u8BD1\u6587\u53EF\u539F\u5730\u5207\u6362\u539F\u6587/\u8BD1\u6587\u3002\u4EC5\u4F5C\u7528\u4E8E\u5F53\u524D\u67E5\u770B\u7684\u4F1A\u8BDD\uFF0C\u5BF9\u8BDD\u6B63\u6587\u6C38\u4E0D\u7FFB\u8BD1\u3002"
1248
+ "\uFF09\u7FFB\u8BD1\u4E3A\u4E2D\u6587\uFF0C \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 \u4EC5\u4F5C\u7528\u4E8E\u5F53\u524D\u67E5\u770B\u7684\u4F1A\u8BDD\uFF0C\u5BF9\u8BDD\u6B63\u6587\u6C38\u4E0D\u7FFB\u8BD1\u3002"
1254
1249
  ] })
1255
1250
  ] }),
1256
1251
  state.enabled && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
package/lib/client.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/client/index.ts", "../src/client/translate/client-cache.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();", "export 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: err?.message || String(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 isThink?: boolean;\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.isThink) element.dataset.tidyThink = '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 if (options.isThink) {\n element.dataset.tidyThink = 'true';\n }\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 delete element.dataset.tidyThink;\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; isThink?: boolean }>) => void;\n}\n\nexport class StreamDebounceViewportObserver {\n private intersectionObserver: IntersectionObserver | null = null;\n private streamingTimers = new WeakMap<HTMLElement, number>();\n private pendingQueue: Array<{ element: HTMLElement; text: string; isThink?: boolean }> = [];\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 const isThink = el.dataset.tidyPendingThink === 'true';\n if (text) {\n delete el.dataset.tidyPendingText;\n delete el.dataset.tidyPendingThink;\n this.enqueueBatch(el, text, isThink);\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, isThink = 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, isThink);\n return;\n }\n\n const timer = window.setTimeout(() => {\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, isThink);\n }\n }, this.options.debounceMs);\n\n this.streamingTimers.set(element, timer);\n }\n\n private registerForViewport(element: HTMLElement, text: string, isThink = false): void {\n if (!element.isConnected) return;\n\n if (!this.intersectionObserver) {\n // Fallback if IntersectionObserver is unsupported: enqueue immediately\n this.enqueueBatch(element, text, isThink);\n return;\n }\n\n element.dataset.tidyPendingText = text;\n if (isThink) element.dataset.tidyPendingThink = 'true';\n this.intersectionObserver.observe(element);\n }\n\n private enqueueBatch(element: HTMLElement, text: string, isThink = false): void {\n this.pendingQueue.push({ element, text, isThink });\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 delete element.dataset.tidyPendingThink;\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 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 isThink?: boolean;\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, isThink = 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, isThink);\n return;\n }\n\n // 2. Delegate to streaming-debounced viewport observer\n this.viewportObserver.observeWithDebounce(element, text, immediate, isThink);\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, Array<{ element: HTMLElement; isThink?: boolean }>>();\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, item.isThink);\n continue;\n }\n\n const list = textMap.get(item.text) || [];\n list.push({ element: item.element, isThink: item.isThink });\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.element.isConnected && this.enabled) {\n this.applyTranslation(entry.element, res.translated, res.original, entry.isThink);\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, isThink?: boolean): void {\n if (!element.isConnected || !this.enabled) return;\n NonDestructiveTranslationMount.mount(element, translated, {\n originalText: original,\n isThink: isThink || element.dataset.tidyThink === 'true',\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\nconst TOOL_TITLE_SELECTOR = '[class*=\"summary\"]';\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\"]')) {\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\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 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\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 { 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: err?.message || String(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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",
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();", "export 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: err?.message || String(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 private streamingTimers = new WeakMap<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 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 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 { 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: err?.message || String(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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",
6
6
  "names": ["cached"]
7
7
  }
@@ -3,7 +3,7 @@ declare class LazyTranslationQueue {
3
3
  private viewportObserver;
4
4
  constructor();
5
5
  setEnabled(enabled: boolean): void;
6
- observe(element: HTMLElement, text: string, immediate?: boolean, isThink?: boolean): void;
6
+ observe(element: HTMLElement, text: string, immediate?: boolean): void;
7
7
  private handleVisibleBatch;
8
8
  private applyTranslation;
9
9
  disconnect(): void;
@@ -13,7 +13,6 @@ export declare const CLASS_ORIGINAL_SHOWN = "dsh-tidy-original-shown";
13
13
  export declare const CLASS_TRANSLATED_BLOCK = "dsh-tidy-translated-block";
14
14
  export interface MountOptions {
15
15
  originalText?: string;
16
- isThink?: boolean;
17
16
  interactive?: boolean;
18
17
  }
19
18
  export declare class NonDestructiveTranslationMount {
@@ -12,7 +12,6 @@ export interface ViewportObserverOptions {
12
12
  onVisibleBatch: (items: Array<{
13
13
  element: HTMLElement;
14
14
  text: string;
15
- isThink?: boolean;
16
15
  }>) => void;
17
16
  }
18
17
  export declare class StreamDebounceViewportObserver {
@@ -27,7 +26,7 @@ export declare class StreamDebounceViewportObserver {
27
26
  * Observe an element with streaming debounce.
28
27
  * If streaming updates characterData repeatedly within debounceMs, the timer resets.
29
28
  */
30
- observeWithDebounce(element: HTMLElement, text: string, immediate?: boolean, isThink?: boolean): void;
29
+ observeWithDebounce(element: HTMLElement, text: string, immediate?: boolean): void;
31
30
  private registerForViewport;
32
31
  private enqueueBatch;
33
32
  private flushQueue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lynn123411/dsh-chat-translate",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
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",