@lynn123411/dsh-chat-translate 1.3.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -8
- package/docs//351/205/215/347/275/256.md +11 -1
- package/lib/client.js +945 -344
- package/lib/client.js.map +4 -4
- package/lib/index.js +565 -173
- package/lib/index.js.map +4 -4
- package/lib/types/client/index.d.ts +6 -5
- package/lib/types/client/locales.d.ts +51 -4
- package/lib/types/client/reading-settings-page.d.ts +1 -1
- package/lib/types/client/settings/store.d.ts +17 -4
- package/lib/types/client/settings/ui.d.ts +5 -3
- package/lib/types/client/translate/api.d.ts +13 -0
- package/lib/types/client/translate/client-cache.d.ts +6 -1
- package/lib/types/client/translate/mount.d.ts +22 -0
- package/lib/types/client/translate/observer.d.ts +17 -0
- package/lib/types/client/translate/think-button.d.ts +19 -0
- package/lib/types/client/translate/think.d.ts +52 -0
- package/lib/types/index.d.ts +11 -18
- package/lib/types/server/adapters/base.d.ts +1 -1
- package/lib/types/server/adapters/openai.d.ts +2 -2
- package/lib/types/server/cache.d.ts +5 -1
- package/lib/types/server/config.d.ts +35 -7
- package/lib/types/server/dispatcher.d.ts +30 -3
- package/lib/types/server/pipeline/mask-tokens.d.ts +54 -48
- package/lib/types/server/pipeline/masking.d.ts +35 -12
- package/lib/types/server/pipeline/think.d.ts +49 -0
- package/lib/types/server/router.d.ts +24 -3
- package/lib/types/server/types.d.ts +18 -1
- package/package.json +23 -10
package/lib/client.js
CHANGED
|
@@ -35,33 +35,47 @@ __export(index_exports, {
|
|
|
35
35
|
module.exports = __toCommonJS(index_exports);
|
|
36
36
|
|
|
37
37
|
// src/server/pipeline/mask-tokens.ts
|
|
38
|
-
var MASK_TOKEN_PATTERN_SOURCE = "
|
|
38
|
+
var MASK_TOKEN_PATTERN_SOURCE = "\u27E6([a-z]{4})(\\d+)\u27E7";
|
|
39
|
+
var LEGACY_MASK_PATTERN_SOURCE = "_{1,2}DSH\\s*_*\\s*MASKx?\\s*(?:_?\\s*\\d+|_*([a-z]{2,8})\\s*_+\\s*(\\d+))_{0,2}";
|
|
39
40
|
function hasMaskResidue(text) {
|
|
40
41
|
if (!text) return false;
|
|
41
|
-
return new RegExp(MASK_TOKEN_PATTERN_SOURCE, "
|
|
42
|
+
return new RegExp(MASK_TOKEN_PATTERN_SOURCE, "iu").test(text);
|
|
43
|
+
}
|
|
44
|
+
function hasLegacyMaskResidue(text) {
|
|
45
|
+
if (!text) return false;
|
|
46
|
+
return new RegExp(LEGACY_MASK_PATTERN_SOURCE, "iu").test(text);
|
|
47
|
+
}
|
|
48
|
+
function hasAnyMaskResidue(text) {
|
|
49
|
+
return hasMaskResidue(text) || hasLegacyMaskResidue(text);
|
|
42
50
|
}
|
|
43
51
|
|
|
44
52
|
// src/server/pipeline/masking.ts
|
|
45
53
|
function isMaskLeak(translatedText) {
|
|
46
|
-
return
|
|
54
|
+
return hasAnyMaskResidue(translatedText);
|
|
47
55
|
}
|
|
48
56
|
|
|
49
57
|
// src/client/translate/client-cache.ts
|
|
50
|
-
var
|
|
51
|
-
var
|
|
58
|
+
var TITLE_CACHE_KEY = "dsh-chat-translate:cache";
|
|
59
|
+
var TITLE_MAX_ENTRIES = 500;
|
|
60
|
+
var THINK_CACHE_KEY = "dsh-chat-translate:think-cache";
|
|
61
|
+
var THINK_MAX_ENTRIES = 300;
|
|
52
62
|
var TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
53
63
|
var ClientCache = class {
|
|
54
64
|
// Map preserves insertion order in JS, enabling true LRU semantics.
|
|
55
65
|
memCache = /* @__PURE__ */ new Map();
|
|
56
66
|
dirty = false;
|
|
57
67
|
saveTimer = null;
|
|
58
|
-
|
|
68
|
+
storageKey;
|
|
69
|
+
maxEntries;
|
|
70
|
+
constructor(storageKey = TITLE_CACHE_KEY, maxEntries = TITLE_MAX_ENTRIES) {
|
|
71
|
+
this.storageKey = storageKey;
|
|
72
|
+
this.maxEntries = maxEntries;
|
|
59
73
|
this.load();
|
|
60
74
|
}
|
|
61
75
|
load() {
|
|
62
76
|
if (typeof localStorage === "undefined") return;
|
|
63
77
|
try {
|
|
64
|
-
const raw = localStorage.getItem(
|
|
78
|
+
const raw = localStorage.getItem(this.storageKey);
|
|
65
79
|
if (raw) {
|
|
66
80
|
const obj = JSON.parse(raw);
|
|
67
81
|
if (obj && typeof obj === "object") {
|
|
@@ -107,7 +121,7 @@ var ClientCache = class {
|
|
|
107
121
|
const key = text.trim().toLowerCase();
|
|
108
122
|
if (this.memCache.has(key)) {
|
|
109
123
|
this.memCache.delete(key);
|
|
110
|
-
} else if (this.memCache.size >=
|
|
124
|
+
} else if (this.memCache.size >= this.maxEntries) {
|
|
111
125
|
const oldestKey = this.memCache.keys().next().value;
|
|
112
126
|
if (oldestKey !== void 0) {
|
|
113
127
|
this.memCache.delete(oldestKey);
|
|
@@ -132,7 +146,7 @@ var ClientCache = class {
|
|
|
132
146
|
for (const [k, v] of this.memCache.entries()) {
|
|
133
147
|
obj[k] = v;
|
|
134
148
|
}
|
|
135
|
-
localStorage.setItem(
|
|
149
|
+
localStorage.setItem(this.storageKey, JSON.stringify(obj));
|
|
136
150
|
} catch {
|
|
137
151
|
}
|
|
138
152
|
}
|
|
@@ -146,6 +160,7 @@ var ClientCache = class {
|
|
|
146
160
|
}
|
|
147
161
|
};
|
|
148
162
|
var clientCache = new ClientCache();
|
|
163
|
+
var thinkClientCache = new ClientCache(THINK_CACHE_KEY, THINK_MAX_ENTRIES);
|
|
149
164
|
|
|
150
165
|
// src/describe-error.ts
|
|
151
166
|
function describeError(err) {
|
|
@@ -218,6 +233,37 @@ async function requestTranslateBatch(texts, options = {}) {
|
|
|
218
233
|
cached: false
|
|
219
234
|
}));
|
|
220
235
|
}
|
|
236
|
+
async function requestTranslateThink(blocks) {
|
|
237
|
+
const fallback = () => blocks.map((block) => ({
|
|
238
|
+
original: block,
|
|
239
|
+
translated: block,
|
|
240
|
+
ok: false,
|
|
241
|
+
cached: false,
|
|
242
|
+
channel: "fallback-client"
|
|
243
|
+
}));
|
|
244
|
+
if (!Array.isArray(blocks) || blocks.length === 0) return [];
|
|
245
|
+
try {
|
|
246
|
+
const res = await fetch("/api/dsh-chat-translate/translate-think", {
|
|
247
|
+
method: "POST",
|
|
248
|
+
headers: { "Content-Type": "application/json" },
|
|
249
|
+
body: JSON.stringify({ blocks })
|
|
250
|
+
});
|
|
251
|
+
if (res.ok) {
|
|
252
|
+
const data = await res.json();
|
|
253
|
+
if (data.ok && Array.isArray(data.results) && data.results.length === blocks.length) {
|
|
254
|
+
return data.results;
|
|
255
|
+
}
|
|
256
|
+
if (data.error) {
|
|
257
|
+
console.warn(`[dsh-chat-translate] \u601D\u8003\u94FE\u7FFB\u8BD1\u63A5\u53E3\u9519\u8BEF: ${data.error}`);
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
console.warn(`[dsh-chat-translate] \u601D\u8003\u94FE\u7FFB\u8BD1\u8BF7\u6C42\u5931\u8D25 (HTTP ${res.status})`);
|
|
261
|
+
}
|
|
262
|
+
} catch (err) {
|
|
263
|
+
console.warn(`[dsh-chat-translate] \u601D\u8003\u94FE\u7FFB\u8BD1\u8BF7\u6C42\u5F02\u5E38: ${describeError(err)}`);
|
|
264
|
+
}
|
|
265
|
+
return fallback();
|
|
266
|
+
}
|
|
221
267
|
async function testServerChannel(channel) {
|
|
222
268
|
try {
|
|
223
269
|
const res = await fetch("/api/dsh-chat-translate/test-channel", {
|
|
@@ -235,6 +281,8 @@ async function testServerChannel(channel) {
|
|
|
235
281
|
var CLASS_ORIGINAL_HIDDEN = "dsh-tidy-original-hidden";
|
|
236
282
|
var CLASS_ORIGINAL_SHOWN = "dsh-tidy-original-shown";
|
|
237
283
|
var CLASS_TRANSLATED_BLOCK = "dsh-tidy-translated-block";
|
|
284
|
+
var CLASS_RUN = "dsh-tidy-run";
|
|
285
|
+
var RUN_ATTRIBUTE = "data-tidy-run";
|
|
238
286
|
var NonDestructiveTranslationMount = class {
|
|
239
287
|
/**
|
|
240
288
|
* Mounts a translated string onto the target element non-destructively.
|
|
@@ -286,6 +334,72 @@ var NonDestructiveTranslationMount = class {
|
|
|
286
334
|
element.dataset.tidyTranslated = "true";
|
|
287
335
|
element.dataset.original = originalText;
|
|
288
336
|
}
|
|
337
|
+
/**
|
|
338
|
+
* 把一段连续的行内节点包进自建容器后挂载译文。
|
|
339
|
+
*
|
|
340
|
+
* 混合块(列表项文字 + 子列表 / 代码块)里的行内片段没有自己的元素,
|
|
341
|
+
* 只能先包一层再走同一条非侵入式挂载;嵌套的块级子节点留在容器之外,
|
|
342
|
+
* 排版原样保留。
|
|
343
|
+
* @returns 自建容器;节点不再相邻或没有共同父节点时返回 null。
|
|
344
|
+
*/
|
|
345
|
+
static mountRun(nodes, translated, options = {}) {
|
|
346
|
+
const first = nodes[0];
|
|
347
|
+
const parent = first?.parentNode;
|
|
348
|
+
if (!first || !parent || !parent.ownerDocument) return null;
|
|
349
|
+
if (!nodes.every((node) => node.parentNode === parent)) return null;
|
|
350
|
+
const originalText = options.originalText ?? nodes.map((node) => node.textContent ?? "").join("").trim();
|
|
351
|
+
if (!originalText) return null;
|
|
352
|
+
const run = parent.ownerDocument.createElement("span");
|
|
353
|
+
run.className = CLASS_RUN;
|
|
354
|
+
run.setAttribute(RUN_ATTRIBUTE, "true");
|
|
355
|
+
parent.insertBefore(run, first);
|
|
356
|
+
for (const node of nodes) run.appendChild(node);
|
|
357
|
+
this.mount(run, translated, { originalText, interactive: options.interactive });
|
|
358
|
+
return run;
|
|
359
|
+
}
|
|
360
|
+
/** 还原行内片段并撤掉自建容器。 */
|
|
361
|
+
static unmountRun(run) {
|
|
362
|
+
this.unmount(run);
|
|
363
|
+
const parent = run.parentNode;
|
|
364
|
+
if (!parent) return;
|
|
365
|
+
while (run.firstChild) parent.insertBefore(run.firstChild, run);
|
|
366
|
+
parent.removeChild(run);
|
|
367
|
+
}
|
|
368
|
+
/** 还原一个作用域内我们挂过的全部译文(元素单位与行内片段都算)。 */
|
|
369
|
+
static restore(scope) {
|
|
370
|
+
const mounted = scope.querySelectorAll('[data-tidy-translated="true"]');
|
|
371
|
+
for (const element of mounted) {
|
|
372
|
+
if (element.getAttribute(RUN_ATTRIBUTE) === "true") this.unmountRun(element);
|
|
373
|
+
else this.unmount(element);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/** 我们自己的挂载容器与还原容器,观察器必须跳过它们。 */
|
|
377
|
+
static isOwnNode(element) {
|
|
378
|
+
return element.classList?.contains(CLASS_TRANSLATED_BLOCK) === true || element.classList?.contains(CLASS_ORIGINAL_HIDDEN) === true || element.classList?.contains(CLASS_ORIGINAL_SHOWN) === true || element.classList?.contains(CLASS_RUN) === true;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* 把一个作用域里的全部挂载译文一起翻面。思考链只由卡片标题右侧的按钮调用
|
|
382
|
+
* 它,正文里的点击不再改变原文与译文。
|
|
383
|
+
*/
|
|
384
|
+
static toggleGroup(scope, showOriginal) {
|
|
385
|
+
const mounted = scope.querySelectorAll('[data-tidy-translated="true"]');
|
|
386
|
+
for (const element of mounted) {
|
|
387
|
+
const origWrapper = element.querySelector(
|
|
388
|
+
`:scope > .${CLASS_ORIGINAL_HIDDEN}, :scope > .${CLASS_ORIGINAL_SHOWN}`
|
|
389
|
+
);
|
|
390
|
+
const transWrapper = element.querySelector(`:scope > .${CLASS_TRANSLATED_BLOCK}`);
|
|
391
|
+
if (!origWrapper || !transWrapper) continue;
|
|
392
|
+
if (showOriginal) {
|
|
393
|
+
origWrapper.style.display = "inline";
|
|
394
|
+
origWrapper.className = CLASS_ORIGINAL_SHOWN;
|
|
395
|
+
transWrapper.style.display = "none";
|
|
396
|
+
} else {
|
|
397
|
+
origWrapper.style.display = "none";
|
|
398
|
+
origWrapper.className = CLASS_ORIGINAL_HIDDEN;
|
|
399
|
+
transWrapper.style.display = "inline";
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
289
403
|
/**
|
|
290
404
|
* Unmounts translation and restores original DOM nodes completely.
|
|
291
405
|
*/
|
|
@@ -542,13 +656,489 @@ var LazyTranslationQueue = class {
|
|
|
542
656
|
};
|
|
543
657
|
var lazyQueue = new LazyTranslationQueue();
|
|
544
658
|
|
|
659
|
+
// src/server/pipeline/think.ts
|
|
660
|
+
var CJK_CHAR = /[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\u3000-\u303f\uff00-\uffef]/;
|
|
661
|
+
function estimateTokens(text) {
|
|
662
|
+
let cjk = 0;
|
|
663
|
+
for (const char of text) {
|
|
664
|
+
if (CJK_CHAR.test(char)) cjk += 1;
|
|
665
|
+
}
|
|
666
|
+
return cjk + Math.ceil((text.length - cjk) / 3);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// src/client/translate/think-button.ts
|
|
670
|
+
var THINK_BUTTON_CLASS = "dsh-tidy-think-button";
|
|
671
|
+
var STYLE_TAG_ID = "@lynn123411/dsh-chat-translate/think-button.css";
|
|
672
|
+
var THINK_BUTTON_CSS = `
|
|
673
|
+
/* \u601D\u8003\u5361\u6807\u9898\u53F3\u4FA7\u7684\u7FFB\u8BD1\u6309\u94AE\uFF1A\u4E09\u6001\uFF08\u672A\u7FFB\u8BD1 / \u7FFB\u8BD1\u4E2D / \u5DF2\u7FFB\u8BD1\uFF09\u4E0E\u65CB\u8F6C\u52A8\u753B\u3002 */
|
|
674
|
+
.dsh-tidy-think-button {
|
|
675
|
+
display: inline-flex;
|
|
676
|
+
align-items: center;
|
|
677
|
+
justify-content: center;
|
|
678
|
+
position: relative;
|
|
679
|
+
flex: none;
|
|
680
|
+
width: 18px;
|
|
681
|
+
height: 18px;
|
|
682
|
+
margin: 0 6px 0 2px;
|
|
683
|
+
padding: 0;
|
|
684
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));
|
|
685
|
+
border-radius: 6px;
|
|
686
|
+
background: transparent;
|
|
687
|
+
color: var(--dsw-alias-label-tertiary, currentColor);
|
|
688
|
+
font-family: inherit;
|
|
689
|
+
font-size: 10px;
|
|
690
|
+
line-height: 1;
|
|
691
|
+
cursor: pointer;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
.dsh-tidy-think-button:hover:not([disabled]) {
|
|
695
|
+
border-color: var(--dsw-alias-label-secondary, rgba(128, 128, 128, 0.6));
|
|
696
|
+
color: var(--dsw-alias-label-primary, currentColor);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
.dsh-tidy-think-button[data-state='translated'] {
|
|
700
|
+
border-color: transparent;
|
|
701
|
+
background: color-mix(in srgb, var(--dsw-static-deepseek-500, #4d6bfe) 18%, transparent);
|
|
702
|
+
color: var(--dsw-static-deepseek-500, #4d6bfe);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
.dsh-tidy-think-button[data-state='working'] {
|
|
706
|
+
color: transparent;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
.dsh-tidy-think-button[data-state='working']::after {
|
|
710
|
+
content: '';
|
|
711
|
+
position: absolute;
|
|
712
|
+
inset: 3px;
|
|
713
|
+
border: 1.5px solid var(--dsw-alias-label-caption, rgba(128, 128, 128, 0.45));
|
|
714
|
+
border-top-color: var(--dsw-static-deepseek-500, #4d6bfe);
|
|
715
|
+
border-radius: 50%;
|
|
716
|
+
animation: dsh-tidy-think-spin 0.8s linear infinite;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
.dsh-tidy-think-button[disabled] {
|
|
720
|
+
opacity: 0.35;
|
|
721
|
+
cursor: default;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
@keyframes dsh-tidy-think-spin {
|
|
725
|
+
to { transform: rotate(360deg); }
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
@media (prefers-reduced-motion: reduce) {
|
|
729
|
+
.dsh-tidy-think-button[data-state='working']::after {
|
|
730
|
+
animation: none;
|
|
731
|
+
border-color: var(--dsw-static-deepseek-500, #4d6bfe);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
`;
|
|
735
|
+
function ensureThinkButtonStyles() {
|
|
736
|
+
if (typeof document === "undefined") return;
|
|
737
|
+
if (document.querySelector('style[data-plugin-css="' + STYLE_TAG_ID + '"]') !== null) return;
|
|
738
|
+
const style = document.createElement("style");
|
|
739
|
+
style.dataset.plugin = "@lynn123411/dsh-chat-translate";
|
|
740
|
+
style.dataset.pluginCss = STYLE_TAG_ID;
|
|
741
|
+
style.textContent = THINK_BUTTON_CSS;
|
|
742
|
+
document.head?.appendChild(style);
|
|
743
|
+
}
|
|
744
|
+
function createThinkButton(onActivate) {
|
|
745
|
+
ensureThinkButtonStyles();
|
|
746
|
+
const element = document.createElement("button");
|
|
747
|
+
element.type = "button";
|
|
748
|
+
element.className = THINK_BUTTON_CLASS;
|
|
749
|
+
element.textContent = "\u8BD1";
|
|
750
|
+
element.dataset.state = "idle";
|
|
751
|
+
element.setAttribute("aria-label", "\u7FFB\u8BD1\u601D\u8003\u94FE");
|
|
752
|
+
element.title = "\u7FFB\u8BD1\u601D\u8003\u94FE";
|
|
753
|
+
element.addEventListener("click", (event) => {
|
|
754
|
+
event.stopPropagation();
|
|
755
|
+
event.preventDefault();
|
|
756
|
+
if (element.disabled) return;
|
|
757
|
+
onActivate();
|
|
758
|
+
});
|
|
759
|
+
const setState = (state) => {
|
|
760
|
+
element.dataset.state = state;
|
|
761
|
+
const label = state === "translated" ? "\u663E\u793A\u601D\u8003\u539F\u6587" : "\u7FFB\u8BD1\u601D\u8003\u94FE";
|
|
762
|
+
element.setAttribute("aria-label", label);
|
|
763
|
+
element.title = label;
|
|
764
|
+
};
|
|
765
|
+
return {
|
|
766
|
+
element,
|
|
767
|
+
getState: () => element.dataset.state ?? "idle",
|
|
768
|
+
setState,
|
|
769
|
+
setDisabled: (disabled, reason) => {
|
|
770
|
+
element.disabled = disabled;
|
|
771
|
+
if (disabled) element.title = reason;
|
|
772
|
+
else setState(element.dataset.state ?? "idle");
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// src/client/translate/think.ts
|
|
778
|
+
var THINK_CARD_SELECTOR = '[data-variant="think"]';
|
|
779
|
+
var THINK_BODY_SELECTOR = '[class*="thinkBody" i]';
|
|
780
|
+
var EXPANDED_SELECTOR = "[data-open]";
|
|
781
|
+
var BLOCK_TAGS = /* @__PURE__ */ new Set([
|
|
782
|
+
"P",
|
|
783
|
+
"H1",
|
|
784
|
+
"H2",
|
|
785
|
+
"H3",
|
|
786
|
+
"H4",
|
|
787
|
+
"H5",
|
|
788
|
+
"H6",
|
|
789
|
+
"LI",
|
|
790
|
+
"BLOCKQUOTE",
|
|
791
|
+
"TD",
|
|
792
|
+
"TH",
|
|
793
|
+
"DT",
|
|
794
|
+
"DD",
|
|
795
|
+
"FIGCAPTION"
|
|
796
|
+
]);
|
|
797
|
+
var CONTAINER_TAGS = /* @__PURE__ */ new Set(["UL", "OL", "DL", "TABLE", "THEAD", "TBODY", "TR", "DIV", "SECTION"]);
|
|
798
|
+
var NESTED_BOUNDARY_SELECTOR = [...BLOCK_TAGS, ...CONTAINER_TAGS, "PRE"].join(",").toLowerCase();
|
|
799
|
+
var REQUEST_TOKENS = 2048;
|
|
800
|
+
var SYNC_DEBOUNCE_MS = 120;
|
|
801
|
+
function isMountedElement(element) {
|
|
802
|
+
return element.dataset?.tidyTranslated === "true";
|
|
803
|
+
}
|
|
804
|
+
function hasNestedBoundary(element) {
|
|
805
|
+
return element.querySelector(NESTED_BOUNDARY_SELECTOR) !== null;
|
|
806
|
+
}
|
|
807
|
+
function collectThinkUnits(body) {
|
|
808
|
+
const units = [];
|
|
809
|
+
const pushRun = (nodes) => {
|
|
810
|
+
if (nodes.length === 0) return;
|
|
811
|
+
const text = nodes.map((node) => node.textContent ?? "").join("").trim();
|
|
812
|
+
if (!text) return;
|
|
813
|
+
const anchor = nodes[0];
|
|
814
|
+
if (anchor === void 0) return;
|
|
815
|
+
units.push({ nodes, text, anchor });
|
|
816
|
+
};
|
|
817
|
+
const walk = (container) => {
|
|
818
|
+
let run = [];
|
|
819
|
+
const flush = () => {
|
|
820
|
+
pushRun(run);
|
|
821
|
+
run = [];
|
|
822
|
+
};
|
|
823
|
+
for (const node of Array.from(container.childNodes)) {
|
|
824
|
+
if (node.nodeType !== 1) {
|
|
825
|
+
run.push(node);
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
828
|
+
const element = node;
|
|
829
|
+
if (NonDestructiveTranslationMount.isOwnNode(element)) {
|
|
830
|
+
flush();
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
if (element.tagName === "PRE") {
|
|
834
|
+
flush();
|
|
835
|
+
continue;
|
|
836
|
+
}
|
|
837
|
+
if (BLOCK_TAGS.has(element.tagName)) {
|
|
838
|
+
flush();
|
|
839
|
+
collectBlock(element);
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
if (CONTAINER_TAGS.has(element.tagName)) {
|
|
843
|
+
flush();
|
|
844
|
+
walk(element);
|
|
845
|
+
continue;
|
|
846
|
+
}
|
|
847
|
+
run.push(element);
|
|
848
|
+
}
|
|
849
|
+
flush();
|
|
850
|
+
};
|
|
851
|
+
const collectBlock = (element) => {
|
|
852
|
+
if (isMountedElement(element)) return;
|
|
853
|
+
if (!hasNestedBoundary(element)) {
|
|
854
|
+
const text = (element.textContent ?? "").trim();
|
|
855
|
+
if (text) units.push({ element, text, anchor: element });
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
walk(element);
|
|
859
|
+
};
|
|
860
|
+
walk(body);
|
|
861
|
+
return units;
|
|
862
|
+
}
|
|
863
|
+
function chunkUnits(units, maxTokens = REQUEST_TOKENS) {
|
|
864
|
+
const chunks = [];
|
|
865
|
+
let current = [];
|
|
866
|
+
let tokens = 0;
|
|
867
|
+
for (const unit of units) {
|
|
868
|
+
const cost = Math.max(estimateTokens(unit.text), 1);
|
|
869
|
+
if (current.length > 0 && tokens + cost > maxTokens) {
|
|
870
|
+
chunks.push(current);
|
|
871
|
+
current = [];
|
|
872
|
+
tokens = 0;
|
|
873
|
+
}
|
|
874
|
+
current.push(unit);
|
|
875
|
+
tokens += cost;
|
|
876
|
+
}
|
|
877
|
+
if (current.length > 0) chunks.push(current);
|
|
878
|
+
return chunks;
|
|
879
|
+
}
|
|
880
|
+
var ThinkTranslateController = class {
|
|
881
|
+
/** 总开关与设置里的思考链开关同时打开。 */
|
|
882
|
+
enabled = false;
|
|
883
|
+
/** AI 通道开关打开且已配置;缺任何一条都不注入按钮。 */
|
|
884
|
+
configured = false;
|
|
885
|
+
states = /* @__PURE__ */ new WeakMap();
|
|
886
|
+
syncTimers = /* @__PURE__ */ new WeakMap();
|
|
887
|
+
/** 只处理当前会话的容器;由观察器在会话切换时更新。 */
|
|
888
|
+
scope = null;
|
|
889
|
+
/** 观察器把当前会话的根交给控制器,插入按钮时只看这一棵子树。 */
|
|
890
|
+
setScope(scope) {
|
|
891
|
+
this.scope = scope;
|
|
892
|
+
}
|
|
893
|
+
isEnabled() {
|
|
894
|
+
return this.enabled;
|
|
895
|
+
}
|
|
896
|
+
setEnabled(enabled) {
|
|
897
|
+
if (enabled === this.enabled) return;
|
|
898
|
+
this.enabled = enabled;
|
|
899
|
+
this.applyVisibility();
|
|
900
|
+
}
|
|
901
|
+
setConfigured(configured) {
|
|
902
|
+
if (configured === this.configured) return;
|
|
903
|
+
this.configured = configured;
|
|
904
|
+
this.applyVisibility();
|
|
905
|
+
}
|
|
906
|
+
/** 按钮是否可以存在:两个开关与 AI 通道配置缺一不可。 */
|
|
907
|
+
usable() {
|
|
908
|
+
return this.enabled && this.configured;
|
|
909
|
+
}
|
|
910
|
+
applyVisibility() {
|
|
911
|
+
if (typeof document === "undefined") return;
|
|
912
|
+
if (this.usable()) {
|
|
913
|
+
this.syncAll();
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
for (const element of document.querySelectorAll("." + THINK_BUTTON_CLASS)) {
|
|
917
|
+
element.remove();
|
|
918
|
+
}
|
|
919
|
+
for (const card of document.querySelectorAll(THINK_CARD_SELECTOR)) {
|
|
920
|
+
const state = this.states.get(card);
|
|
921
|
+
if (state !== void 0) {
|
|
922
|
+
state.button = null;
|
|
923
|
+
state.state = "idle";
|
|
924
|
+
state.pendingActivate = false;
|
|
925
|
+
state.expandedHandled = false;
|
|
926
|
+
}
|
|
927
|
+
NonDestructiveTranslationMount.restore(card);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
/** 扫描当前会话里的全部思考卡(开关、配置或会话变化后调用)。 */
|
|
931
|
+
syncAll() {
|
|
932
|
+
const scope = this.scope ?? (typeof document === "undefined" ? null : document);
|
|
933
|
+
if (scope === null) return;
|
|
934
|
+
for (const card of scope.querySelectorAll(THINK_CARD_SELECTOR)) {
|
|
935
|
+
this.syncCard(card);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
/** 防抖后同步一张卡片。 */
|
|
939
|
+
syncCard(card) {
|
|
940
|
+
if (typeof window === "undefined") return;
|
|
941
|
+
const existing = this.syncTimers.get(card);
|
|
942
|
+
if (existing !== void 0) window.clearTimeout(existing);
|
|
943
|
+
const timer = window.setTimeout(() => {
|
|
944
|
+
this.syncTimers.delete(card);
|
|
945
|
+
this.runSync(card);
|
|
946
|
+
}, SYNC_DEBOUNCE_MS);
|
|
947
|
+
this.syncTimers.set(card, timer);
|
|
948
|
+
}
|
|
949
|
+
stateFor(card) {
|
|
950
|
+
let state = this.states.get(card);
|
|
951
|
+
if (state === void 0) {
|
|
952
|
+
state = { button: null, state: "idle", expandedHandled: false, pendingActivate: false };
|
|
953
|
+
this.states.set(card, state);
|
|
954
|
+
}
|
|
955
|
+
return state;
|
|
956
|
+
}
|
|
957
|
+
runSync(card) {
|
|
958
|
+
if (!card.isConnected) return;
|
|
959
|
+
const state = this.stateFor(card);
|
|
960
|
+
if (!this.usable()) {
|
|
961
|
+
this.removeButton(card, state, true);
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
const expanded = card.querySelector(EXPANDED_SELECTOR) !== null;
|
|
965
|
+
if (!expanded) state.expandedHandled = false;
|
|
966
|
+
this.ensureButton(card, state);
|
|
967
|
+
if (state.state !== "working") {
|
|
968
|
+
state.state = this.isShowingTranslation(card) ? "translated" : "idle";
|
|
969
|
+
}
|
|
970
|
+
state.button?.setState(state.state);
|
|
971
|
+
this.updateDisabled(card, state);
|
|
972
|
+
if (state.pendingActivate) {
|
|
973
|
+
if (card.dataset.state === "running") return;
|
|
974
|
+
state.pendingActivate = false;
|
|
975
|
+
this.translateCard(card, state);
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
if (!expanded || state.expandedHandled || state.state === "working") return;
|
|
979
|
+
state.expandedHandled = true;
|
|
980
|
+
this.showCachedWhenComplete(card, state);
|
|
981
|
+
}
|
|
982
|
+
updateDisabled(card, state) {
|
|
983
|
+
const running = card.dataset.state === "running";
|
|
984
|
+
state.button?.setDisabled(running && state.state !== "working", "\u601D\u8003\u8F93\u51FA\u4E2D\uFF0C\u7ED3\u675F\u540E\u53EF\u7FFB\u8BD1");
|
|
985
|
+
}
|
|
986
|
+
ensureButton(card, state) {
|
|
987
|
+
if (state.button !== null) return;
|
|
988
|
+
const row = card.querySelector("[data-disclosure-row]");
|
|
989
|
+
if (row === null) return;
|
|
990
|
+
const button = createThinkButton(() => this.activate(card));
|
|
991
|
+
const title = row.querySelector('[class*="title" i]');
|
|
992
|
+
if (title !== null && title.parentElement === row) {
|
|
993
|
+
row.insertBefore(button.element, title.nextSibling);
|
|
994
|
+
} else {
|
|
995
|
+
row.appendChild(button.element);
|
|
996
|
+
}
|
|
997
|
+
state.button = button;
|
|
998
|
+
button.setState(state.state);
|
|
999
|
+
}
|
|
1000
|
+
removeButton(card, state, restore) {
|
|
1001
|
+
state.button?.element.remove();
|
|
1002
|
+
state.button = null;
|
|
1003
|
+
state.state = "idle";
|
|
1004
|
+
state.pendingActivate = false;
|
|
1005
|
+
if (restore) NonDestructiveTranslationMount.restore(card);
|
|
1006
|
+
}
|
|
1007
|
+
/** 卡片当前显示的是译文(至少有一个译文块可见)。 */
|
|
1008
|
+
isShowingTranslation(card) {
|
|
1009
|
+
const blocks = card.querySelectorAll("." + CLASS_TRANSLATED_BLOCK);
|
|
1010
|
+
for (const block of blocks) {
|
|
1011
|
+
if (block.style.display !== "none") return true;
|
|
1012
|
+
}
|
|
1013
|
+
return false;
|
|
1014
|
+
}
|
|
1015
|
+
bodyOf(card) {
|
|
1016
|
+
return card.querySelector(THINK_BODY_SELECTOR);
|
|
1017
|
+
}
|
|
1018
|
+
/** 展开时若整条都命中缓存,直接显示中文,不点按钮也不发请求。 */
|
|
1019
|
+
showCachedWhenComplete(card, state) {
|
|
1020
|
+
const body = this.bodyOf(card);
|
|
1021
|
+
if (body === null || state.state === "working") return;
|
|
1022
|
+
const units = collectThinkUnits(body);
|
|
1023
|
+
if (units.length === 0) return;
|
|
1024
|
+
const cached = units.map((unit) => thinkClientCache.get(unit.text));
|
|
1025
|
+
if (cached.some((value) => value === void 0)) return;
|
|
1026
|
+
units.forEach((unit, index) => this.applyUnit(unit, cached[index]));
|
|
1027
|
+
state.state = this.isShowingTranslation(card) ? "translated" : "idle";
|
|
1028
|
+
state.button?.setState(state.state);
|
|
1029
|
+
}
|
|
1030
|
+
/** 按钮被点击。 */
|
|
1031
|
+
activate(card) {
|
|
1032
|
+
const state = this.stateFor(card);
|
|
1033
|
+
if (!this.usable() || state.state === "working") return;
|
|
1034
|
+
if (card.dataset.state === "running") return;
|
|
1035
|
+
if (card.querySelector(EXPANDED_SELECTOR) === null) {
|
|
1036
|
+
state.pendingActivate = true;
|
|
1037
|
+
const row = card.querySelector("[data-disclosure-row]") ?? card;
|
|
1038
|
+
row.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
|
|
1039
|
+
this.syncCard(card);
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
if (state.state === "translated") {
|
|
1043
|
+
NonDestructiveTranslationMount.toggleGroup(card, true);
|
|
1044
|
+
state.state = "idle";
|
|
1045
|
+
state.button?.setState("idle");
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
this.translateCard(card, state);
|
|
1049
|
+
}
|
|
1050
|
+
/** 翻译这张卡片的全部单位:命中缓存的先挂上,其余按块请求。 */
|
|
1051
|
+
translateCard(card, state) {
|
|
1052
|
+
const body = this.bodyOf(card);
|
|
1053
|
+
if (body === null) return;
|
|
1054
|
+
if (card.querySelector("." + CLASS_TRANSLATED_BLOCK) !== null) {
|
|
1055
|
+
NonDestructiveTranslationMount.toggleGroup(card, false);
|
|
1056
|
+
}
|
|
1057
|
+
const units = collectThinkUnits(body);
|
|
1058
|
+
if (units.length === 0) {
|
|
1059
|
+
state.state = this.isShowingTranslation(card) ? "translated" : "idle";
|
|
1060
|
+
state.button?.setState(state.state);
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
const missing = [];
|
|
1064
|
+
for (const unit of units) {
|
|
1065
|
+
const cached = thinkClientCache.get(unit.text);
|
|
1066
|
+
if (cached === void 0) missing.push(unit);
|
|
1067
|
+
else this.applyUnit(unit, cached);
|
|
1068
|
+
}
|
|
1069
|
+
if (missing.length === 0) {
|
|
1070
|
+
state.state = this.isShowingTranslation(card) ? "translated" : "idle";
|
|
1071
|
+
state.button?.setState(state.state);
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
state.state = "working";
|
|
1075
|
+
state.button?.setState("working");
|
|
1076
|
+
void this.translateUnits(card, state, missing);
|
|
1077
|
+
}
|
|
1078
|
+
async translateUnits(card, state, units) {
|
|
1079
|
+
let failed = false;
|
|
1080
|
+
for (const chunk of chunkUnits(units, REQUEST_TOKENS)) {
|
|
1081
|
+
if (!this.usable()) {
|
|
1082
|
+
failed = true;
|
|
1083
|
+
break;
|
|
1084
|
+
}
|
|
1085
|
+
const texts = chunk.map((unit) => unit.text);
|
|
1086
|
+
let results = [];
|
|
1087
|
+
try {
|
|
1088
|
+
results = await requestTranslateThink(texts);
|
|
1089
|
+
} catch {
|
|
1090
|
+
failed = true;
|
|
1091
|
+
}
|
|
1092
|
+
const byText = new Map(results.map((result) => [result.original, result]));
|
|
1093
|
+
for (const unit of chunk) {
|
|
1094
|
+
const result = byText.get(unit.text);
|
|
1095
|
+
if (result === void 0 || !result.ok || !result.translated.trim() || result.translated.trim() === unit.text.trim()) {
|
|
1096
|
+
failed = true;
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
1099
|
+
thinkClientCache.set(unit.text, result.translated);
|
|
1100
|
+
this.applyUnit(unit, result.translated);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
state.state = !failed && this.isShowingTranslation(card) ? "translated" : "idle";
|
|
1104
|
+
state.button?.setState(state.state);
|
|
1105
|
+
this.updateDisabled(card, state);
|
|
1106
|
+
}
|
|
1107
|
+
/** 把译文挂到单位上;期间文本或节点已经变动的单位直接放弃(结果已进缓存)。 */
|
|
1108
|
+
applyUnit(unit, translated) {
|
|
1109
|
+
if (unit.element !== void 0) {
|
|
1110
|
+
const element = unit.element;
|
|
1111
|
+
if (!element.isConnected || isMountedElement(element)) return;
|
|
1112
|
+
if ((element.textContent ?? "").trim() !== unit.text) return;
|
|
1113
|
+
NonDestructiveTranslationMount.mount(element, translated, {
|
|
1114
|
+
originalText: unit.text,
|
|
1115
|
+
interactive: false
|
|
1116
|
+
});
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
const nodes = unit.nodes ?? [];
|
|
1120
|
+
if (nodes.length === 0 || nodes.some((node) => !node.isConnected)) return;
|
|
1121
|
+
const parent = nodes[0]?.parentNode ?? null;
|
|
1122
|
+
if (parent === null || !nodes.every((node) => node.parentNode === parent)) return;
|
|
1123
|
+
const text = nodes.map((node) => node.textContent ?? "").join("").trim();
|
|
1124
|
+
if (text !== unit.text) return;
|
|
1125
|
+
NonDestructiveTranslationMount.mountRun(nodes, translated, {
|
|
1126
|
+
originalText: unit.text,
|
|
1127
|
+
interactive: false
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
var thinkTranslator = new ThinkTranslateController();
|
|
1132
|
+
|
|
545
1133
|
// src/client/translate/observer.ts
|
|
546
1134
|
var TOOL_TITLE_SELECTOR = '[class*="summary" i]';
|
|
1135
|
+
var THINK_CARD_SELECTOR2 = '[data-variant="think"]';
|
|
547
1136
|
var SESSION_ROOT_SELECTOR = "[data-conversation-scroll], [data-chat-flow]";
|
|
548
1137
|
var ROOT_CHECK_INTERVAL_MS = 3e3;
|
|
549
1138
|
function isToolSummarySpan(span) {
|
|
550
1139
|
if (!span || span.nodeType !== 1) return false;
|
|
551
1140
|
if (span.hasAttribute("aria-hidden")) return false;
|
|
1141
|
+
if (span.closest(THINK_CARD_SELECTOR2)) return false;
|
|
552
1142
|
if (span.querySelector?.('[class*="title"], [class*="leading"], [class*="chevron"], [class*="sep"], [class*="summary" i]')) {
|
|
553
1143
|
return false;
|
|
554
1144
|
}
|
|
@@ -569,20 +1159,19 @@ function isToolSummarySpan(span) {
|
|
|
569
1159
|
}
|
|
570
1160
|
return false;
|
|
571
1161
|
}
|
|
572
|
-
function thinkBlockStillRunning(span) {
|
|
573
|
-
const thinkRoot = span.closest('[data-variant="think"]');
|
|
574
|
-
return !!thinkRoot && thinkRoot.dataset.state === "running";
|
|
575
|
-
}
|
|
576
1162
|
var ChatTranslateObserver = class {
|
|
577
1163
|
observer = null;
|
|
578
1164
|
rootElement = null;
|
|
579
1165
|
rootCheckTimer = null;
|
|
580
1166
|
isEnabled = true;
|
|
1167
|
+
/** 设置里的思考链开关;与总开关一起决定按钮是否存在。 */
|
|
1168
|
+
thinkEnabled = false;
|
|
581
1169
|
constructor() {
|
|
582
1170
|
this.handleMutations = this.handleMutations.bind(this);
|
|
583
1171
|
}
|
|
584
1172
|
setEnabled(enabled) {
|
|
585
1173
|
this.isEnabled = enabled;
|
|
1174
|
+
thinkTranslator.setEnabled(enabled && this.thinkEnabled);
|
|
586
1175
|
if (enabled) {
|
|
587
1176
|
lazyQueue.setEnabled(true);
|
|
588
1177
|
this.start();
|
|
@@ -594,10 +1183,22 @@ var ChatTranslateObserver = class {
|
|
|
594
1183
|
}
|
|
595
1184
|
restoreOriginals() {
|
|
596
1185
|
const scope = this.rootElement ?? document;
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
1186
|
+
NonDestructiveTranslationMount.restore(scope);
|
|
1187
|
+
}
|
|
1188
|
+
/**
|
|
1189
|
+
* 设置里的思考链开关:关掉就撤掉所有翻译按钮并还原思考卡里的译文,工具标题
|
|
1190
|
+
* 译文不动。
|
|
1191
|
+
*/
|
|
1192
|
+
setThinkEnabled(enabled) {
|
|
1193
|
+
this.thinkEnabled = enabled;
|
|
1194
|
+
thinkTranslator.setEnabled(this.isEnabled && enabled);
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* AI 通道的可用性(通道开关打开且 Key / Base URL / 模型齐全)。不可用时同样
|
|
1198
|
+
* 不注入按钮:思考链翻译只能走这条通道。
|
|
1199
|
+
*/
|
|
1200
|
+
setThinkConfigured(configured) {
|
|
1201
|
+
thinkTranslator.setConfigured(configured);
|
|
601
1202
|
}
|
|
602
1203
|
/**
|
|
603
1204
|
* The active session's scroll container. Only the currently-viewed session
|
|
@@ -633,7 +1234,9 @@ var ChatTranslateObserver = class {
|
|
|
633
1234
|
const findAndObserveRoot = () => {
|
|
634
1235
|
const root = this.findRoot(documentRef);
|
|
635
1236
|
this.rootElement = root;
|
|
1237
|
+
thinkTranslator.setScope(root);
|
|
636
1238
|
this.scanContainer(root);
|
|
1239
|
+
this.scanThink(root);
|
|
637
1240
|
if (!this.observer) {
|
|
638
1241
|
this.observer = new MutationObserver(this.handleMutations);
|
|
639
1242
|
this.observer.observe(root, {
|
|
@@ -641,7 +1244,15 @@ var ChatTranslateObserver = class {
|
|
|
641
1244
|
subtree: true,
|
|
642
1245
|
attributes: true,
|
|
643
1246
|
characterData: true,
|
|
644
|
-
attributeFilter: [
|
|
1247
|
+
attributeFilter: [
|
|
1248
|
+
"data-state",
|
|
1249
|
+
"data-tool",
|
|
1250
|
+
"data-variant",
|
|
1251
|
+
"data-sample",
|
|
1252
|
+
"aria-expanded",
|
|
1253
|
+
"data-open",
|
|
1254
|
+
"data-expanded"
|
|
1255
|
+
]
|
|
645
1256
|
});
|
|
646
1257
|
}
|
|
647
1258
|
};
|
|
@@ -672,30 +1283,36 @@ var ChatTranslateObserver = class {
|
|
|
672
1283
|
if (!this.isEnabled) return;
|
|
673
1284
|
for (const mutation of mutations) {
|
|
674
1285
|
if (mutation.type === "childList") {
|
|
1286
|
+
let addedElement = false;
|
|
675
1287
|
for (let i = 0; i < mutation.addedNodes.length; i++) {
|
|
676
1288
|
const node = mutation.addedNodes[i];
|
|
677
1289
|
if (node instanceof HTMLElement) {
|
|
678
|
-
if (
|
|
679
|
-
|
|
680
|
-
}
|
|
1290
|
+
if (NonDestructiveTranslationMount.isOwnNode(node)) continue;
|
|
1291
|
+
addedElement = true;
|
|
681
1292
|
this.scanNode(node);
|
|
1293
|
+
this.scanThink(node);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
const target = mutation.target;
|
|
1297
|
+
if (!addedElement && target instanceof HTMLElement) {
|
|
1298
|
+
if (!NonDestructiveTranslationMount.isOwnNode(target)) {
|
|
1299
|
+
this.scanNode(target);
|
|
1300
|
+
this.scanThink(target);
|
|
682
1301
|
}
|
|
683
1302
|
}
|
|
684
1303
|
} else if (mutation.type === "attributes") {
|
|
685
1304
|
const target = mutation.target;
|
|
686
1305
|
if (target instanceof HTMLElement) {
|
|
687
|
-
if (
|
|
688
|
-
continue;
|
|
689
|
-
}
|
|
1306
|
+
if (NonDestructiveTranslationMount.isOwnNode(target)) continue;
|
|
690
1307
|
this.scanNode(target);
|
|
1308
|
+
this.scanThink(target);
|
|
691
1309
|
}
|
|
692
1310
|
} else if (mutation.type === "characterData") {
|
|
693
1311
|
const parent = mutation.target.parentElement;
|
|
694
1312
|
if (parent instanceof HTMLElement) {
|
|
695
|
-
if (
|
|
696
|
-
continue;
|
|
697
|
-
}
|
|
1313
|
+
if (NonDestructiveTranslationMount.isOwnNode(parent)) continue;
|
|
698
1314
|
this.scanNode(parent);
|
|
1315
|
+
this.scanThink(parent);
|
|
699
1316
|
}
|
|
700
1317
|
}
|
|
701
1318
|
}
|
|
@@ -719,8 +1336,22 @@ var ChatTranslateObserver = class {
|
|
|
719
1336
|
}
|
|
720
1337
|
});
|
|
721
1338
|
}
|
|
1339
|
+
/**
|
|
1340
|
+
* 交给思考正文翻译控制器:命中节点所在的卡片,以及它下面(或它就是)的
|
|
1341
|
+
* 全部思考卡片。
|
|
1342
|
+
*/
|
|
1343
|
+
scanThink(node) {
|
|
1344
|
+
if (!thinkTranslator.isEnabled()) return;
|
|
1345
|
+
if (node instanceof HTMLElement) {
|
|
1346
|
+
const card = node.closest(THINK_CARD_SELECTOR2);
|
|
1347
|
+
if (card) thinkTranslator.syncCard(card);
|
|
1348
|
+
}
|
|
1349
|
+
if (!node.querySelectorAll) return;
|
|
1350
|
+
node.querySelectorAll(THINK_CARD_SELECTOR2).forEach((card) => {
|
|
1351
|
+
thinkTranslator.syncCard(card);
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
722
1354
|
processSpan(span) {
|
|
723
|
-
if (thinkBlockStillRunning(span)) return;
|
|
724
1355
|
if (NonDestructiveTranslationMount.isMounted(span)) {
|
|
725
1356
|
const original = NonDestructiveTranslationMount.getOriginal(span);
|
|
726
1357
|
if (original) {
|
|
@@ -757,8 +1388,11 @@ var chatTranslateObserver = new ChatTranslateObserver();
|
|
|
757
1388
|
|
|
758
1389
|
// src/client/settings/ui.tsx
|
|
759
1390
|
var import_react2 = require("react");
|
|
1391
|
+
var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
760
1392
|
|
|
761
1393
|
// src/client/settings/store.ts
|
|
1394
|
+
var THINK_TIMEOUT_MIN = 500;
|
|
1395
|
+
var THINK_TIMEOUT_MAX = 9e5;
|
|
762
1396
|
var SETTINGS_NAMESPACE = "dsh-chat-translate";
|
|
763
1397
|
var TRANSLATE_API_KEY_REF = "TRANSLATE_API_KEY";
|
|
764
1398
|
var DEFAULT_STATE = {
|
|
@@ -766,6 +1400,8 @@ var DEFAULT_STATE = {
|
|
|
766
1400
|
concurrency: 3,
|
|
767
1401
|
aiEnabled: true,
|
|
768
1402
|
bingEnabled: true,
|
|
1403
|
+
thinkEnabled: true,
|
|
1404
|
+
thinkTimeoutMs: 6e5,
|
|
769
1405
|
baseUrl: "",
|
|
770
1406
|
model: "",
|
|
771
1407
|
aiConfigured: false
|
|
@@ -819,6 +1455,14 @@ var SettingsStore = class {
|
|
|
819
1455
|
}
|
|
820
1456
|
if (typeof value.aiEnabled === "boolean") next.aiEnabled = value.aiEnabled;
|
|
821
1457
|
if (typeof value.bingEnabled === "boolean") next.bingEnabled = value.bingEnabled;
|
|
1458
|
+
if (typeof value.thinkEnabled === "boolean") next.thinkEnabled = value.thinkEnabled;
|
|
1459
|
+
const thinkTimeout = value.thinkTimeoutMs;
|
|
1460
|
+
if (typeof thinkTimeout === "number" && Number.isFinite(thinkTimeout)) {
|
|
1461
|
+
next.thinkTimeoutMs = Math.min(
|
|
1462
|
+
Math.max(Math.round(thinkTimeout), THINK_TIMEOUT_MIN),
|
|
1463
|
+
THINK_TIMEOUT_MAX
|
|
1464
|
+
);
|
|
1465
|
+
}
|
|
822
1466
|
if (typeof value.baseUrl === "string") next.baseUrl = value.baseUrl;
|
|
823
1467
|
if (typeof value.model === "string") next.model = value.model;
|
|
824
1468
|
this.applyState(next);
|
|
@@ -833,6 +1477,11 @@ var SettingsStore = class {
|
|
|
833
1477
|
} catch {
|
|
834
1478
|
}
|
|
835
1479
|
}
|
|
1480
|
+
try {
|
|
1481
|
+
chatTranslateObserver.setThinkEnabled(this.state.enabled && this.state.thinkEnabled);
|
|
1482
|
+
chatTranslateObserver.setThinkConfigured(this.state.aiEnabled && this.state.aiConfigured);
|
|
1483
|
+
} catch {
|
|
1484
|
+
}
|
|
836
1485
|
this.notify();
|
|
837
1486
|
}
|
|
838
1487
|
notify() {
|
|
@@ -872,14 +1521,31 @@ var SettingsStore = class {
|
|
|
872
1521
|
if (typeof partial.concurrency === "number" && Number.isFinite(partial.concurrency)) {
|
|
873
1522
|
sanitizedConcurrency = Math.min(Math.max(Math.round(partial.concurrency), 1), 100);
|
|
874
1523
|
}
|
|
1524
|
+
let sanitizedThinkTimeout = this.state.thinkTimeoutMs;
|
|
1525
|
+
if (typeof partial.thinkTimeoutMs === "number" && Number.isFinite(partial.thinkTimeoutMs)) {
|
|
1526
|
+
sanitizedThinkTimeout = Math.min(
|
|
1527
|
+
Math.max(Math.round(partial.thinkTimeoutMs), THINK_TIMEOUT_MIN),
|
|
1528
|
+
THINK_TIMEOUT_MAX
|
|
1529
|
+
);
|
|
1530
|
+
}
|
|
875
1531
|
const next = {
|
|
876
1532
|
...this.state,
|
|
877
1533
|
...partial,
|
|
878
|
-
concurrency: sanitizedConcurrency
|
|
1534
|
+
concurrency: sanitizedConcurrency,
|
|
1535
|
+
thinkTimeoutMs: sanitizedThinkTimeout
|
|
879
1536
|
};
|
|
880
1537
|
this.applyState(next);
|
|
881
1538
|
if (this.scope) {
|
|
882
|
-
const fields = [
|
|
1539
|
+
const fields = [
|
|
1540
|
+
"enabled",
|
|
1541
|
+
"concurrency",
|
|
1542
|
+
"aiEnabled",
|
|
1543
|
+
"bingEnabled",
|
|
1544
|
+
"thinkEnabled",
|
|
1545
|
+
"thinkTimeoutMs",
|
|
1546
|
+
"baseUrl",
|
|
1547
|
+
"model"
|
|
1548
|
+
];
|
|
883
1549
|
for (const field of fields) {
|
|
884
1550
|
if (partial[field] !== void 0) {
|
|
885
1551
|
this.pendingFields.add(field);
|
|
@@ -899,11 +1565,13 @@ var SettingsStore = class {
|
|
|
899
1565
|
if (!this.scope) return;
|
|
900
1566
|
const fields = [...this.pendingFields];
|
|
901
1567
|
this.pendingFields.clear();
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
1568
|
+
if (fields.length === 0) return;
|
|
1569
|
+
const ops = fields.map((field) => ({
|
|
1570
|
+
op: "set",
|
|
1571
|
+
path: [field],
|
|
1572
|
+
value: this.state[field]
|
|
1573
|
+
}));
|
|
1574
|
+
await this.scope.mutate(ops).catch(() => {
|
|
907
1575
|
});
|
|
908
1576
|
}
|
|
909
1577
|
async testChannel(channel) {
|
|
@@ -951,33 +1619,36 @@ var SETTINGS_CSS = String.raw`
|
|
|
951
1619
|
gap: 16px;
|
|
952
1620
|
max-width: 580px;
|
|
953
1621
|
padding-bottom: 32px;
|
|
954
|
-
color: var(--dsw-alias-label-primary
|
|
955
|
-
font
|
|
1622
|
+
color: var(--dsw-alias-label-primary);
|
|
1623
|
+
font: var(--dsw-font-s-14);
|
|
956
1624
|
}
|
|
957
1625
|
|
|
958
1626
|
.dsh-tidy-card {
|
|
959
|
-
border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.2));
|
|
960
|
-
border-radius: 12px;
|
|
961
|
-
padding: 16px 18px;
|
|
962
|
-
background: var(--dsw-alias-bg-card, rgba(128, 128, 128, 0.05));
|
|
963
1627
|
display: flex;
|
|
964
1628
|
flex-direction: column;
|
|
965
1629
|
gap: 12px;
|
|
1630
|
+
padding: 16px 18px;
|
|
1631
|
+
border: 0.5px solid var(--dsw-alias-border-l2);
|
|
1632
|
+
border-radius: 12px;
|
|
1633
|
+
/* The shipped settings-card surface; Input's own bg-layer-1 then reads as a
|
|
1634
|
+
recessed field in the dark theme instead of vanishing into the card. */
|
|
1635
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
966
1636
|
}
|
|
967
1637
|
|
|
968
1638
|
.dsh-tidy-title {
|
|
969
|
-
font-size: 14px;
|
|
970
|
-
font-weight: 600;
|
|
971
|
-
color: var(--dsw-alias-label-primary, inherit);
|
|
972
1639
|
display: flex;
|
|
973
1640
|
align-items: center;
|
|
974
1641
|
justify-content: space-between;
|
|
1642
|
+
font: var(--dsw-font-s-strong-14);
|
|
975
1643
|
}
|
|
976
1644
|
|
|
977
1645
|
.dsh-tidy-desc {
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1646
|
+
color: var(--dsw-alias-label-secondary);
|
|
1647
|
+
font: var(--dsw-font-xxs-12);
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
.dsh-tidy-desc code {
|
|
1651
|
+
font-family: var(--ds-font-family-code);
|
|
981
1652
|
}
|
|
982
1653
|
|
|
983
1654
|
.dsh-tidy-row {
|
|
@@ -989,7 +1660,7 @@ var SETTINGS_CSS = String.raw`
|
|
|
989
1660
|
}
|
|
990
1661
|
|
|
991
1662
|
.dsh-tidy-row + .dsh-tidy-row {
|
|
992
|
-
border-top:
|
|
1663
|
+
border-top: 0.5px solid var(--dsw-alias-border-l3);
|
|
993
1664
|
}
|
|
994
1665
|
|
|
995
1666
|
.dsh-tidy-row-info {
|
|
@@ -998,231 +1669,53 @@ var SETTINGS_CSS = String.raw`
|
|
|
998
1669
|
}
|
|
999
1670
|
|
|
1000
1671
|
.dsh-tidy-row-title {
|
|
1001
|
-
font
|
|
1002
|
-
font-weight: 500;
|
|
1672
|
+
font: var(--dsw-font-xs-strong-13);
|
|
1003
1673
|
}
|
|
1004
1674
|
|
|
1005
1675
|
.dsh-tidy-row-desc {
|
|
1006
|
-
font-size: 11px;
|
|
1007
|
-
color: var(--dsw-alias-label-secondary, rgba(128, 128, 128, 0.7));
|
|
1008
1676
|
margin-top: 2px;
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
/* Switch */
|
|
1013
|
-
.dsh-tidy-switch {
|
|
1014
|
-
position: relative;
|
|
1015
|
-
width: 38px;
|
|
1016
|
-
height: 22px;
|
|
1017
|
-
flex: none;
|
|
1018
|
-
cursor: pointer;
|
|
1019
|
-
border-radius: 999px;
|
|
1020
|
-
border: none;
|
|
1021
|
-
background: rgba(128, 128, 128, 0.3);
|
|
1022
|
-
transition: background 0.15s ease;
|
|
1023
|
-
padding: 0;
|
|
1024
|
-
outline: none;
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
.dsh-tidy-switch[aria-checked="true"] {
|
|
1028
|
-
background: #3b82f6;
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
.dsh-tidy-switch::after {
|
|
1032
|
-
content: "";
|
|
1033
|
-
position: absolute;
|
|
1034
|
-
top: 2px;
|
|
1035
|
-
left: 2px;
|
|
1036
|
-
width: 18px;
|
|
1037
|
-
height: 18px;
|
|
1038
|
-
border-radius: 50%;
|
|
1039
|
-
background: #ffffff;
|
|
1040
|
-
transition: transform 0.15s ease;
|
|
1041
|
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
.dsh-tidy-switch[aria-checked="true"]::after {
|
|
1045
|
-
transform: translateX(16px);
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
/* Inputs */
|
|
1049
|
-
.dsh-tidy-input-group {
|
|
1050
|
-
display: flex;
|
|
1051
|
-
flex-direction: column;
|
|
1052
|
-
gap: 6px;
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
.dsh-tidy-label {
|
|
1056
|
-
font-size: 12px;
|
|
1057
|
-
font-weight: 500;
|
|
1058
|
-
display: flex;
|
|
1059
|
-
align-items: center;
|
|
1060
|
-
justify-content: space-between;
|
|
1677
|
+
color: var(--dsw-alias-label-secondary);
|
|
1678
|
+
font: var(--dsw-font-xxxs-11);
|
|
1061
1679
|
}
|
|
1062
1680
|
|
|
1681
|
+
/* Field widths ride Input's wrapper span; the atom owns its own appearance. */
|
|
1063
1682
|
.dsh-tidy-input-row {
|
|
1064
1683
|
display: flex;
|
|
1684
|
+
align-items: center;
|
|
1065
1685
|
gap: 8px;
|
|
1066
1686
|
}
|
|
1067
1687
|
|
|
1068
|
-
.dsh-tidy-
|
|
1069
|
-
|
|
1070
|
-
min-width: 0;
|
|
1071
|
-
padding: 8px 12px;
|
|
1072
|
-
border-radius: 8px;
|
|
1073
|
-
border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.25));
|
|
1074
|
-
background: var(--dsw-alias-bg-input, rgba(0, 0, 0, 0.05));
|
|
1075
|
-
color: inherit;
|
|
1076
|
-
font-size: 12px;
|
|
1077
|
-
outline: none;
|
|
1078
|
-
transition: border-color 0.15s;
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
.dsh-tidy-input:focus {
|
|
1082
|
-
border-color: #3b82f6;
|
|
1688
|
+
.dsh-tidy-field-wide {
|
|
1689
|
+
width: 260px;
|
|
1083
1690
|
}
|
|
1084
1691
|
|
|
1085
|
-
.dsh-tidy-
|
|
1086
|
-
|
|
1087
|
-
border-radius: 8px;
|
|
1088
|
-
border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.3));
|
|
1089
|
-
background: transparent;
|
|
1090
|
-
color: inherit;
|
|
1091
|
-
font-size: 12px;
|
|
1092
|
-
cursor: pointer;
|
|
1093
|
-
transition: all 0.15s ease;
|
|
1094
|
-
white-space: nowrap;
|
|
1692
|
+
.dsh-tidy-field-timeout {
|
|
1693
|
+
width: 120px;
|
|
1095
1694
|
}
|
|
1096
1695
|
|
|
1097
|
-
.dsh-tidy-
|
|
1098
|
-
|
|
1099
|
-
border-color: rgba(128, 128, 128, 0.5);
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
.dsh-tidy-btn:disabled {
|
|
1103
|
-
opacity: 0.5;
|
|
1104
|
-
cursor: not-allowed;
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
.dsh-tidy-badge {
|
|
1108
|
-
font-size: 10px;
|
|
1109
|
-
padding: 2px 6px;
|
|
1110
|
-
border-radius: 4px;
|
|
1111
|
-
font-weight: 500;
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
.dsh-tidy-badge-ok {
|
|
1115
|
-
background: rgba(34, 197, 94, 0.15);
|
|
1116
|
-
color: #22c55e;
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
.dsh-tidy-badge-none {
|
|
1120
|
-
background: rgba(156, 163, 175, 0.15);
|
|
1121
|
-
color: #9ca3af;
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
.dsh-tidy-badge-warn {
|
|
1125
|
-
background: rgba(245, 158, 11, 0.15);
|
|
1126
|
-
color: #f59e0b;
|
|
1696
|
+
.dsh-tidy-field-count {
|
|
1697
|
+
width: 88px;
|
|
1127
1698
|
}
|
|
1128
1699
|
|
|
1129
1700
|
.dsh-tidy-test-result {
|
|
1130
|
-
font-size: 12px;
|
|
1131
1701
|
margin-left: 10px;
|
|
1702
|
+
font: var(--dsw-font-xxs-12);
|
|
1132
1703
|
}
|
|
1133
1704
|
|
|
1134
1705
|
.dsh-tidy-test-result.ok {
|
|
1135
|
-
color:
|
|
1706
|
+
color: var(--dsw-alias-state-success-primary);
|
|
1136
1707
|
}
|
|
1137
1708
|
|
|
1138
1709
|
.dsh-tidy-test-result.fail {
|
|
1139
|
-
color:
|
|
1710
|
+
color: var(--dsw-alias-state-error-primary);
|
|
1140
1711
|
}
|
|
1141
1712
|
|
|
1142
1713
|
.dsh-tidy-behavior-list {
|
|
1143
|
-
padding-left: 18px;
|
|
1144
|
-
margin: 4px 0 0;
|
|
1145
|
-
display: flex;
|
|
1146
|
-
flex-direction: column;
|
|
1147
|
-
gap: 4px;
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
/* Priority list */
|
|
1151
|
-
.dsh-tidy-priority-list {
|
|
1152
1714
|
display: flex;
|
|
1153
1715
|
flex-direction: column;
|
|
1154
|
-
gap: 8px;
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
.dsh-tidy-priority-item {
|
|
1158
|
-
display: flex;
|
|
1159
|
-
align-items: center;
|
|
1160
|
-
justify-content: space-between;
|
|
1161
|
-
padding: 8px 12px;
|
|
1162
|
-
border-radius: 8px;
|
|
1163
|
-
background: rgba(128, 128, 128, 0.06);
|
|
1164
|
-
border: 1px solid var(--dsw-alias-border-l3, rgba(128, 128, 128, 0.12));
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
.dsh-tidy-priority-name {
|
|
1168
|
-
font-size: 12px;
|
|
1169
|
-
font-weight: 500;
|
|
1170
|
-
display: flex;
|
|
1171
|
-
align-items: center;
|
|
1172
|
-
gap: 8px;
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
.dsh-tidy-order-badge {
|
|
1176
|
-
display: inline-flex;
|
|
1177
|
-
align-items: center;
|
|
1178
|
-
justify-content: center;
|
|
1179
|
-
width: 18px;
|
|
1180
|
-
height: 18px;
|
|
1181
|
-
border-radius: 50%;
|
|
1182
|
-
background: #3b82f6;
|
|
1183
|
-
color: #ffffff;
|
|
1184
|
-
font-size: 11px;
|
|
1185
|
-
font-weight: 600;
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
.dsh-tidy-btn-group {
|
|
1189
|
-
display: flex;
|
|
1190
1716
|
gap: 4px;
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
.dsh-tidy-icon-btn {
|
|
1194
|
-
width: 26px;
|
|
1195
|
-
height: 26px;
|
|
1196
|
-
padding: 0;
|
|
1197
|
-
display: flex;
|
|
1198
|
-
align-items: center;
|
|
1199
|
-
justify-content: center;
|
|
1200
|
-
border-radius: 6px;
|
|
1201
|
-
border: 1px solid rgba(128, 128, 128, 0.2);
|
|
1202
|
-
background: transparent;
|
|
1203
|
-
color: inherit;
|
|
1204
|
-
cursor: pointer;
|
|
1205
|
-
font-size: 12px;
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
.dsh-tidy-icon-btn:hover:not(:disabled) {
|
|
1209
|
-
background: rgba(128, 128, 128, 0.15);
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
.dsh-tidy-icon-btn:disabled {
|
|
1213
|
-
opacity: 0.3;
|
|
1214
|
-
cursor: default;
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
/* Slider / Select */
|
|
1218
|
-
.dsh-tidy-select {
|
|
1219
|
-
padding: 6px 10px;
|
|
1220
|
-
border-radius: 8px;
|
|
1221
|
-
border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.3));
|
|
1222
|
-
background: var(--dsw-alias-bg-input, rgba(0, 0, 0, 0.05));
|
|
1223
|
-
color: inherit;
|
|
1224
|
-
font-size: 12px;
|
|
1225
|
-
outline: none;
|
|
1717
|
+
margin: 4px 0 0;
|
|
1718
|
+
padding-left: 18px;
|
|
1226
1719
|
}
|
|
1227
1720
|
`;
|
|
1228
1721
|
|
|
@@ -1251,14 +1744,12 @@ var TAB_STYLE = {
|
|
|
1251
1744
|
position: "relative",
|
|
1252
1745
|
padding: "7px 1px 11px",
|
|
1253
1746
|
cursor: "pointer",
|
|
1254
|
-
font: "
|
|
1255
|
-
|
|
1256
|
-
lineHeight: "20px",
|
|
1257
|
-
color: "var(--dsw-alias-label-tertiary, inherit)"
|
|
1747
|
+
font: "var(--dsw-font-xs-13)",
|
|
1748
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
1258
1749
|
};
|
|
1259
1750
|
var TAB_ACTIVE_STYLE = {
|
|
1260
1751
|
...TAB_STYLE,
|
|
1261
|
-
color: "var(--dsw-alias-label-primary
|
|
1752
|
+
color: "var(--dsw-alias-label-primary)"
|
|
1262
1753
|
};
|
|
1263
1754
|
var TAB_MARKER_STYLE = {
|
|
1264
1755
|
position: "absolute",
|
|
@@ -1267,7 +1758,7 @@ var TAB_MARKER_STYLE = {
|
|
|
1267
1758
|
bottom: 0,
|
|
1268
1759
|
height: "2px",
|
|
1269
1760
|
borderRadius: "2px 2px 0 0",
|
|
1270
|
-
background: "var(--dsw-alias-label-primary
|
|
1761
|
+
background: "var(--dsw-alias-label-primary)"
|
|
1271
1762
|
};
|
|
1272
1763
|
var PANEL_STYLE = { listStyle: "none", margin: 0, padding: 0 };
|
|
1273
1764
|
var PANEL_HIDDEN_STYLE = { ...PANEL_STYLE, display: "none" };
|
|
@@ -1363,13 +1854,93 @@ function claimReadingSettingsPage(ctx, label, locale) {
|
|
|
1363
1854
|
|
|
1364
1855
|
// src/client/locales.ts
|
|
1365
1856
|
var NS = "settings.chatTranslate";
|
|
1366
|
-
var en = {
|
|
1367
|
-
pageNav: "Reading",
|
|
1368
|
-
title: "Chat translate"
|
|
1369
|
-
};
|
|
1370
1857
|
var zh = {
|
|
1371
1858
|
pageNav: "\u9605\u8BFB\u4F53\u9A8C",
|
|
1372
|
-
title: "\u804A\u5929\u7FFB\u8BD1"
|
|
1859
|
+
title: "\u804A\u5929\u7FFB\u8BD1",
|
|
1860
|
+
masterTitle: "\u7FFB\u8BD1\u603B\u5F00\u5173",
|
|
1861
|
+
enableTranslation: "\u542F\u7528\u7FFB\u8BD1",
|
|
1862
|
+
masterDesc: "\u81EA\u52A8\u5C06\u5F53\u524D\u4F1A\u8BDD\u4E2D\u5DE5\u5177\u8C03\u7528\u6807\u9898\uFF08\u5982 {example}\uFF09\u7FFB\u8BD1\u4E3A\u4E2D\u6587\uFF0C\u70B9\u51FB\u8BD1\u6587\u53EF\u539F\u5730\u5207\u6362\u539F\u6587/\u8BD1\u6587\u3002\u601D\u8003\u6B63\u6587\u9ED8\u8BA4\u4E0D\u7FFB\u8BD1\uFF0C\u7531 AI \u5361\u7247\u91CC\u7684\u300C\u663E\u793A\u601D\u8003\u94FE\u7FFB\u8BD1\u6309\u94AE\u300D\u63A7\u5236\uFF1A\u6253\u5F00\u540E\u6BCF\u5F20\u601D\u8003\u5361\u53F3\u4FA7\u51FA\u73B0\u6309\u94AE\uFF0C\u70B9\u51FB\u624D\u7FFB\u8BD1\u3002\u601D\u8003\u5361\u6298\u53E0\u8D77\u6765\u7684\u90A3\u4E00\u884C\u6458\u8981\u6C38\u4E0D\u7FFB\u8BD1\u3002\u4EC5\u4F5C\u7528\u4E8E\u5F53\u524D\u67E5\u770B\u7684\u4F1A\u8BDD\uFF0C\u56DE\u7B54\u6B63\u6587\u6C38\u4E0D\u7FFB\u8BD1\u3002",
|
|
1863
|
+
aiTitle: "AI \u7FFB\u8BD1\uFF08OpenAI \u517C\u5BB9\u534F\u8BAE\uFF09",
|
|
1864
|
+
enableAi: "\u542F\u7528 AI \u7FFB\u8BD1\u901A\u9053",
|
|
1865
|
+
badgeConfigured: "\u5DF2\u914D\u7F6E",
|
|
1866
|
+
badgeUnconfigured: "\u672A\u914D\u7F6E",
|
|
1867
|
+
aiFallbackNote: "\u672A\u914D\u7F6E\u65F6 AI \u901A\u9053\u81EA\u52A8\u8DF3\u8FC7\uFF0C\u7531 Bing \u515C\u5E95\u3002",
|
|
1868
|
+
apiKeyTitle: "API Key",
|
|
1869
|
+
apiKeyDesc: "\u4FDD\u5B58\u81F3 {home} \u7684 {ref}\uFF0C\u4FDD\u5B58\u540E\u7ACB\u5373\u751F\u6548\uFF1B\u7559\u7A7A\u4FDD\u5B58 = \u6E05\u9664",
|
|
1870
|
+
apiKeyPlaceholderConfigured: "\u5DF2\u914D\u7F6E\uFF08\u5982\u9700\u66F4\u6362\u8BF7\u76F4\u63A5\u8F93\u5165\uFF09",
|
|
1871
|
+
save: "\u4FDD\u5B58",
|
|
1872
|
+
saving: "\u4FDD\u5B58\u4E2D\u2026",
|
|
1873
|
+
keySaved: "\u5DF2\u4FDD\u5B58\u5230 {home}\uFF0C\u7ACB\u5373\u751F\u6548",
|
|
1874
|
+
keyCleared: "\u5DF2\u6E05\u9664 API Key\uFF08AI \u901A\u9053\u5C06\u4E0D\u53EF\u7528\uFF0CBing \u515C\u5E95\uFF09",
|
|
1875
|
+
saveFailed: "\u4FDD\u5B58\u5931\u8D25\uFF1A{error}",
|
|
1876
|
+
unknownError: "\u672A\u77E5\u9519\u8BEF",
|
|
1877
|
+
baseUrlTitle: "Base URL",
|
|
1878
|
+
baseUrlDesc: "\u4EFB\u610F OpenAI \u517C\u5BB9\u670D\u52A1\u7AEF\u70B9\uFF0C\u5982 {url1}\u3001{url2}",
|
|
1879
|
+
modelTitle: "\u6A21\u578B",
|
|
1880
|
+
modelDesc: "\u5982 {model1}\u3001{model2}\uFF1B\u7559\u7A7A\u89C6\u4E3A\u672A\u914D\u7F6E",
|
|
1881
|
+
test: "\u6D4B\u8BD5 AI \u901A\u9053",
|
|
1882
|
+
testing: "\u6D4B\u8BD5\u4E2D\u2026",
|
|
1883
|
+
testOk: "\u8FDE\u63A5\u6B63\u5E38\uFF0C\u5EF6\u8FDF {latency}ms",
|
|
1884
|
+
testFail: "\u5931\u8D25\uFF1A{error}",
|
|
1885
|
+
thinkEnableTitle: "\u663E\u793A\u601D\u8003\u94FE\u7FFB\u8BD1\u6309\u94AE",
|
|
1886
|
+
thinkEnableDesc: "\u5728\u6BCF\u5F20\u601D\u8003\u5361\u7684\u300CThink\u300D\u53F3\u4FA7\u663E\u793A\u4E00\u4E2A\u6309\u94AE\uFF0C\u70B9\u51FB\u624D\u7FFB\u8BD1\u8FD9\u6761\u601D\u8003\u6B63\u6587\uFF08\u4E0D\u4F1A\u81EA\u52A8\u7FFB\u8BD1\uFF09\uFF1B\u7FFB\u8BD1\u4E2D\u6309\u94AE\u8F6C\u5708\uFF0C\u7FFB\u597D\u540E\u5B9E\u5FC3\u9AD8\u4EAE\uFF0C\u518D\u70B9\u4E00\u4E0B\u6574\u6761\u5207\u56DE\u539F\u6587\uFF08\u6B63\u6587\u91CC\u7684\u70B9\u51FB\u4E0D\u5207\u6362\uFF09\u3002\u4EE3\u7801\u5757\u4E0E\u6298\u53E0\u6458\u8981\u4E0D\u52A8\uFF0CBing \u901A\u9053\u4E0D\u53C2\u4E0E\u3002\u5173\u95ED\u6B64\u5F00\u5173\u4F1A\u64A4\u6389\u6309\u94AE\u5E76\u8FD8\u539F\u5DF2\u7FFB\u8BD1\u5185\u5BB9\u3002",
|
|
1887
|
+
thinkTimeoutTitle: "\u601D\u8003\u94FE\u7FFB\u8BD1\u8D85\u65F6",
|
|
1888
|
+
thinkTimeoutDesc: "\u5355\u4F4D\u6BEB\u79D2\uFF0C\u8303\u56F4 500-900000\u3002\u6574\u5757\u601D\u8003\u53EF\u80FD\u8981\u51E0\u5206\u949F\uFF0C\u9ED8\u8BA4 600000\u3002",
|
|
1889
|
+
bingTitle: "Bing \u7F51\u9875\u7FFB\u8BD1\uFF08\u514D Key \u515C\u5E95\uFF09",
|
|
1890
|
+
enableBing: "\u542F\u7528 Bing \u7FFB\u8BD1\u901A\u9053",
|
|
1891
|
+
bingDesc: "\u5185\u7F6E\u514D Key \u7FFB\u8BD1\u901A\u9053\uFF0C\u65E0\u9700\u4EFB\u4F55\u914D\u7F6E\uFF0C\u53EA\u7FFB\u8BD1\u5DE5\u5177\u8C03\u7528\u6807\u9898\u3002AI \u672A\u914D\u7F6E\u6216\u8BF7\u6C42\u5931\u8D25\u65F6\u81EA\u52A8\u515C\u5E95\uFF1B\u601D\u8003\u94FE\u7FFB\u8BD1\u6C38\u8FDC\u4E0D\u8D70\u8FD9\u6761\u901A\u9053\u3002AI \u4E0E Bing \u540C\u65F6\u5173\u95ED\u5219\u5DE5\u5177\u8C03\u7528\u6807\u9898\u4E5F\u4E0D\u7FFB\u8BD1\u3002",
|
|
1892
|
+
behaviorTitle: "\u901A\u9053\u884C\u4E3A",
|
|
1893
|
+
behavior1: "AI \u5F00\u542F\u4E14\u5DF2\u914D\u7F6E \u2192 AI \u4F18\u5148\u7FFB\u8BD1\uFF0C\u5931\u8D25\u81EA\u52A8\u964D\u7EA7 Bing",
|
|
1894
|
+
behavior2: "AI \u5F00\u542F\u4F46\u672A\u914D\u7F6E + Bing \u5F00\u542F \u2192 \u7531 Bing \u7FFB\u8BD1",
|
|
1895
|
+
behavior3: "AI \u5F00\u542F\u4F46\u672A\u914D\u7F6E + Bing \u5173\u95ED \u2192 \u4E0D\u7FFB\u8BD1",
|
|
1896
|
+
behavior4: "AI \u5173\u95ED + Bing \u5F00\u542F \u2192 \u76F4\u63A5\u4F7F\u7528 Bing",
|
|
1897
|
+
behavior5: "AI \u5173\u95ED + Bing \u5173\u95ED \u2192 \u4E0D\u7FFB\u8BD1",
|
|
1898
|
+
concurrencyTitle: "\u6700\u5927\u7FFB\u8BD1\u5E76\u53D1\u6570",
|
|
1899
|
+
concurrencyDesc: "\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"
|
|
1900
|
+
};
|
|
1901
|
+
var en = {
|
|
1902
|
+
pageNav: "Reading",
|
|
1903
|
+
title: "Chat translate",
|
|
1904
|
+
masterTitle: "Translation master switch",
|
|
1905
|
+
enableTranslation: "Enable translation",
|
|
1906
|
+
masterDesc: "Translates tool-call titles in the current session (e.g. {example}) into Chinese; click a translation to switch back to the original in place. Think text stays untranslated by default and is controlled by \u201Cshow think-chain translate button\u201D in the AI card: once on, a button appears at the right of every think card and translates only when clicked. The collapsed summary line of a think card is never translated. Applies to the session you are viewing only; reply text is never translated.",
|
|
1907
|
+
aiTitle: "AI translation (OpenAI-compatible)",
|
|
1908
|
+
enableAi: "Enable the AI translation channel",
|
|
1909
|
+
badgeConfigured: "Configured",
|
|
1910
|
+
badgeUnconfigured: "Not configured",
|
|
1911
|
+
aiFallbackNote: "When unconfigured the AI channel is skipped and Bing takes over.",
|
|
1912
|
+
apiKeyTitle: "API Key",
|
|
1913
|
+
apiKeyDesc: "Saved as {ref} in {home}; takes effect immediately. Saving an empty value clears it.",
|
|
1914
|
+
apiKeyPlaceholderConfigured: "Configured (type a new value to replace it)",
|
|
1915
|
+
save: "Save",
|
|
1916
|
+
saving: "Saving\u2026",
|
|
1917
|
+
keySaved: "Saved to {home}; takes effect immediately",
|
|
1918
|
+
keyCleared: "API Key cleared (the AI channel becomes unavailable and Bing takes over)",
|
|
1919
|
+
saveFailed: "Save failed: {error}",
|
|
1920
|
+
unknownError: "Unknown error",
|
|
1921
|
+
baseUrlTitle: "Base URL",
|
|
1922
|
+
baseUrlDesc: "Any OpenAI-compatible endpoint, e.g. {url1} or {url2}",
|
|
1923
|
+
modelTitle: "Model",
|
|
1924
|
+
modelDesc: "e.g. {model1} or {model2}; empty counts as unconfigured",
|
|
1925
|
+
test: "Test the AI channel",
|
|
1926
|
+
testing: "Testing\u2026",
|
|
1927
|
+
testOk: "Connected, {latency}ms latency",
|
|
1928
|
+
testFail: "Failed: {error}",
|
|
1929
|
+
thinkEnableTitle: "Show the think-chain translate button",
|
|
1930
|
+
thinkEnableDesc: "Shows a button to the right of \u201CThink\u201D on every think card; it translates that think text only when clicked, never automatically. While translating, the button spins; once done it highlights solid, and clicking it again switches the whole block back to the original (clicks inside the text do not switch). Code blocks and the collapsed summary stay untouched, and the Bing channel is not involved. Turning this off removes the button and restores already translated content.",
|
|
1931
|
+
thinkTimeoutTitle: "Think-chain translation timeout",
|
|
1932
|
+
thinkTimeoutDesc: "In milliseconds, range 500-900000. A whole think block can take minutes; default 600000.",
|
|
1933
|
+
bingTitle: "Bing web translation (key-free fallback)",
|
|
1934
|
+
enableBing: "Enable the Bing translation channel",
|
|
1935
|
+
bingDesc: "A built-in key-free channel that needs no configuration and translates tool-call titles only. It takes over automatically when AI is unconfigured or a request fails; think-chain translation never uses this channel. With both AI and Bing off, tool-call titles are not translated either.",
|
|
1936
|
+
behaviorTitle: "Channel behavior",
|
|
1937
|
+
behavior1: "AI on and configured \u2192 AI translates first, falling back to Bing on failure",
|
|
1938
|
+
behavior2: "AI on but unconfigured + Bing on \u2192 Bing translates",
|
|
1939
|
+
behavior3: "AI on but unconfigured + Bing off \u2192 nothing is translated",
|
|
1940
|
+
behavior4: "AI off + Bing on \u2192 Bing is used directly",
|
|
1941
|
+
behavior5: "AI off + Bing off \u2192 nothing is translated",
|
|
1942
|
+
concurrencyTitle: "Max translation concurrency",
|
|
1943
|
+
concurrencyDesc: "Caps parallel requests while the viewport scrolls across multiple tool cards (range 1-100, recommended 3)."
|
|
1373
1944
|
};
|
|
1374
1945
|
|
|
1375
1946
|
// src/client/settings/ui.tsx
|
|
@@ -1383,21 +1954,17 @@ function ensureSettingsStyles() {
|
|
|
1383
1954
|
document.head.appendChild(el);
|
|
1384
1955
|
stylesInjected = true;
|
|
1385
1956
|
}
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
{
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
"aria-checked": props.checked,
|
|
1394
|
-
onClick: props.onChange,
|
|
1395
|
-
"aria-label": props.label
|
|
1396
|
-
}
|
|
1397
|
-
);
|
|
1957
|
+
var CREDENTIALS_PATH = "~/.dsh/.credentials.yaml";
|
|
1958
|
+
function withSamples(text, samples) {
|
|
1959
|
+
return text.split(/(\{\w+\})/g).map((part, index) => {
|
|
1960
|
+
const name2 = /^\{\w+\}$/.test(part) ? part.slice(1, -1) : "";
|
|
1961
|
+
const sample = name2 === "" ? void 0 : samples[name2];
|
|
1962
|
+
return sample === void 0 ? part : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: sample }, index);
|
|
1963
|
+
});
|
|
1398
1964
|
}
|
|
1399
|
-
function TidySettingsPanel(
|
|
1965
|
+
function TidySettingsPanel(props) {
|
|
1400
1966
|
ensureSettingsStyles();
|
|
1967
|
+
const { t } = props;
|
|
1401
1968
|
const [state, setState] = (0, import_react2.useState)(() => settingsStore.getState());
|
|
1402
1969
|
const [testing, setTesting] = (0, import_react2.useState)(null);
|
|
1403
1970
|
const [apiKeyInput, setApiKeyInput] = (0, import_react2.useState)("");
|
|
@@ -1411,7 +1978,7 @@ function TidySettingsPanel(_props) {
|
|
|
1411
1978
|
const runTest = async (channel) => {
|
|
1412
1979
|
setTesting({ channel, running: true });
|
|
1413
1980
|
const res = await settingsStore.testChannel(channel);
|
|
1414
|
-
const message = res.ok ?
|
|
1981
|
+
const message = res.ok ? t("testOk").replace("{latency}", String(res.latencyMs)) : t("testFail").replace("{error}", res.error || t("unknownError"));
|
|
1415
1982
|
setTesting({ channel, running: false, ok: res.ok, message });
|
|
1416
1983
|
};
|
|
1417
1984
|
const handleSaveKey = async () => {
|
|
@@ -1423,176 +1990,211 @@ function TidySettingsPanel(_props) {
|
|
|
1423
1990
|
const cleared = !apiKeyInput.trim();
|
|
1424
1991
|
setKeyMsg({
|
|
1425
1992
|
ok: true,
|
|
1426
|
-
text: cleared ? "
|
|
1993
|
+
text: cleared ? t("keyCleared") : t("keySaved").replace("{home}", CREDENTIALS_PATH)
|
|
1427
1994
|
});
|
|
1428
1995
|
setApiKeyInput("");
|
|
1429
1996
|
} else {
|
|
1430
|
-
setKeyMsg({
|
|
1997
|
+
setKeyMsg({
|
|
1998
|
+
ok: false,
|
|
1999
|
+
text: t("saveFailed").replace("{error}", res.error || t("unknownError"))
|
|
2000
|
+
});
|
|
1431
2001
|
}
|
|
1432
2002
|
};
|
|
1433
2003
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-tidy-settings", children: [
|
|
1434
2004
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-tidy-card", children: [
|
|
1435
2005
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-tidy-title", children: [
|
|
1436
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "
|
|
2006
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("masterTitle") }),
|
|
1437
2007
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1438
|
-
Switch,
|
|
2008
|
+
import_dsh_client_ui_primitives.Switch,
|
|
1439
2009
|
{
|
|
1440
2010
|
checked: state.enabled,
|
|
1441
|
-
onChange: () => settingsStore.update({ enabled:
|
|
1442
|
-
label: "
|
|
2011
|
+
onChange: (next) => settingsStore.update({ enabled: next }),
|
|
2012
|
+
label: t("enableTranslation")
|
|
1443
2013
|
}
|
|
1444
2014
|
)
|
|
1445
2015
|
] }),
|
|
1446
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
1447
|
-
"\u81EA\u52A8\u5C06\u5F53\u524D\u4F1A\u8BDD\u4E2D\u5DE5\u5177\u8C03\u7528\u6807\u9898\u4E0E\u601D\u8003\u6298\u53E0\u6458\u8981\uFF08\u5982 ",
|
|
1448
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "Locate DSH home directory structure" }),
|
|
1449
|
-
"\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"
|
|
1450
|
-
] })
|
|
2016
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-tidy-desc", children: withSamples(t("masterDesc"), { example: "Locate DSH home directory structure" }) })
|
|
1451
2017
|
] }),
|
|
1452
2018
|
state.enabled && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1453
2019
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-tidy-card", children: [
|
|
1454
2020
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-tidy-title", children: [
|
|
1455
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "
|
|
2021
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("aiTitle") }),
|
|
1456
2022
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1457
|
-
Switch,
|
|
2023
|
+
import_dsh_client_ui_primitives.Switch,
|
|
1458
2024
|
{
|
|
1459
2025
|
checked: state.aiEnabled,
|
|
1460
|
-
onChange: () => settingsStore.update({ aiEnabled:
|
|
1461
|
-
label: "
|
|
2026
|
+
onChange: (next) => settingsStore.update({ aiEnabled: next }),
|
|
2027
|
+
label: t("enableAi")
|
|
1462
2028
|
}
|
|
1463
2029
|
)
|
|
1464
2030
|
] }),
|
|
1465
2031
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-tidy-desc", children: [
|
|
1466
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2032
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: state.aiConfigured ? "success" : "warning", children: t(state.aiConfigured ? "badgeConfigured" : "badgeUnconfigured") }),
|
|
1467
2033
|
" ",
|
|
1468
|
-
"
|
|
2034
|
+
t("aiFallbackNote")
|
|
1469
2035
|
] }),
|
|
1470
2036
|
state.aiEnabled && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1471
2037
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-tidy-row", children: [
|
|
1472
2038
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-tidy-row-info", children: [
|
|
1473
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-tidy-row-title", children: "
|
|
1474
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "TRANSLATE_API_KEY" }),
|
|
1479
|
-
"\uFF0C\u4FDD\u5B58\u540E\u7ACB\u5373\u751F\u6548\uFF1B\u7559\u7A7A\u4FDD\u5B58 = \u6E05\u9664"
|
|
1480
|
-
] })
|
|
2039
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-tidy-row-title", children: t("apiKeyTitle") }),
|
|
2040
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-tidy-row-desc", children: withSamples(t("apiKeyDesc"), {
|
|
2041
|
+
home: CREDENTIALS_PATH,
|
|
2042
|
+
ref: TRANSLATE_API_KEY_REF
|
|
2043
|
+
}) })
|
|
1481
2044
|
] }),
|
|
1482
2045
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-tidy-input-row", children: [
|
|
1483
2046
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1484
|
-
|
|
2047
|
+
import_dsh_client_ui_primitives.Input,
|
|
1485
2048
|
{
|
|
1486
2049
|
type: "password",
|
|
1487
|
-
className: "dsh-tidy-
|
|
1488
|
-
placeholder: state.aiConfigured ? "
|
|
2050
|
+
className: "dsh-tidy-field-wide",
|
|
2051
|
+
placeholder: state.aiConfigured ? t("apiKeyPlaceholderConfigured") : "sk-...",
|
|
1489
2052
|
value: apiKeyInput,
|
|
1490
2053
|
onChange: (e) => setApiKeyInput(e.target.value),
|
|
1491
|
-
|
|
1492
|
-
"aria-label": "API Key"
|
|
2054
|
+
"aria-label": t("apiKeyTitle")
|
|
1493
2055
|
}
|
|
1494
2056
|
),
|
|
1495
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2057
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2058
|
+
import_dsh_client_ui_primitives.Button,
|
|
2059
|
+
{
|
|
2060
|
+
variant: "primary",
|
|
2061
|
+
size: "sm",
|
|
2062
|
+
disabled: savingKey,
|
|
2063
|
+
onClick: handleSaveKey,
|
|
2064
|
+
children: savingKey ? t("saving") : t("save")
|
|
2065
|
+
}
|
|
2066
|
+
)
|
|
1496
2067
|
] })
|
|
1497
2068
|
] }),
|
|
1498
2069
|
keyMsg && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `dsh-tidy-test-result ${keyMsg.ok ? "ok" : "fail"}`, children: keyMsg.text }),
|
|
1499
2070
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-tidy-row", children: [
|
|
1500
2071
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-tidy-row-info", children: [
|
|
1501
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-tidy-row-title", children: "
|
|
1502
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
" ",
|
|
1507
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "https://api.deepseek.com/v1" })
|
|
1508
|
-
] })
|
|
2072
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-tidy-row-title", children: t("baseUrlTitle") }),
|
|
2073
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-tidy-row-desc", children: withSamples(t("baseUrlDesc"), {
|
|
2074
|
+
url1: "https://api.openai.com/v1",
|
|
2075
|
+
url2: "https://api.deepseek.com/v1"
|
|
2076
|
+
}) })
|
|
1509
2077
|
] }),
|
|
1510
2078
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1511
|
-
|
|
2079
|
+
import_dsh_client_ui_primitives.Input,
|
|
1512
2080
|
{
|
|
1513
2081
|
type: "text",
|
|
1514
|
-
className: "dsh-tidy-
|
|
2082
|
+
className: "dsh-tidy-field-wide",
|
|
1515
2083
|
placeholder: "https://api.openai.com/v1",
|
|
1516
2084
|
value: state.baseUrl,
|
|
1517
2085
|
onChange: (e) => settingsStore.update({ baseUrl: e.target.value }),
|
|
1518
|
-
|
|
1519
|
-
"aria-label": "AI Base URL"
|
|
2086
|
+
"aria-label": t("baseUrlTitle")
|
|
1520
2087
|
}
|
|
1521
2088
|
)
|
|
1522
2089
|
] }),
|
|
1523
2090
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-tidy-row", children: [
|
|
1524
2091
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-tidy-row-info", children: [
|
|
1525
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-tidy-row-title", children: "
|
|
1526
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "deepseek-chat" }),
|
|
1531
|
-
"\uFF1B\u7559\u7A7A\u89C6\u4E3A\u672A\u914D\u7F6E"
|
|
1532
|
-
] })
|
|
2092
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-tidy-row-title", children: t("modelTitle") }),
|
|
2093
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-tidy-row-desc", children: withSamples(t("modelDesc"), {
|
|
2094
|
+
model1: "gpt-4o-mini",
|
|
2095
|
+
model2: "deepseek-chat"
|
|
2096
|
+
}) })
|
|
1533
2097
|
] }),
|
|
1534
2098
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1535
|
-
|
|
2099
|
+
import_dsh_client_ui_primitives.Input,
|
|
1536
2100
|
{
|
|
1537
2101
|
type: "text",
|
|
1538
|
-
className: "dsh-tidy-
|
|
2102
|
+
className: "dsh-tidy-field-wide",
|
|
1539
2103
|
placeholder: "gpt-4o-mini",
|
|
1540
2104
|
value: state.model,
|
|
1541
2105
|
onChange: (e) => settingsStore.update({ model: e.target.value }),
|
|
1542
|
-
|
|
1543
|
-
"aria-label": "AI \u6A21\u578B"
|
|
2106
|
+
"aria-label": t("modelTitle")
|
|
1544
2107
|
}
|
|
1545
2108
|
)
|
|
1546
2109
|
] }),
|
|
1547
2110
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-tidy-row", children: [
|
|
1548
2111
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1549
|
-
|
|
2112
|
+
import_dsh_client_ui_primitives.Button,
|
|
1550
2113
|
{
|
|
1551
|
-
|
|
1552
|
-
|
|
2114
|
+
variant: "outline",
|
|
2115
|
+
size: "sm",
|
|
1553
2116
|
disabled: testing?.running,
|
|
1554
2117
|
onClick: () => runTest("openai"),
|
|
1555
|
-
children: testing?.running ? "
|
|
2118
|
+
children: testing?.running ? t("testing") : t("test")
|
|
1556
2119
|
}
|
|
1557
2120
|
),
|
|
1558
2121
|
testing?.channel === "openai" && !testing.running && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `dsh-tidy-test-result ${testing.ok ? "ok" : "fail"}`, children: testing.message })
|
|
2122
|
+
] }),
|
|
2123
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-tidy-row", children: [
|
|
2124
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-tidy-row-info", children: [
|
|
2125
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-tidy-row-title", children: t("thinkEnableTitle") }),
|
|
2126
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-tidy-row-desc", children: t("thinkEnableDesc") })
|
|
2127
|
+
] }),
|
|
2128
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2129
|
+
import_dsh_client_ui_primitives.Switch,
|
|
2130
|
+
{
|
|
2131
|
+
checked: state.thinkEnabled,
|
|
2132
|
+
onChange: (next) => settingsStore.update({ thinkEnabled: next }),
|
|
2133
|
+
label: t("thinkEnableTitle")
|
|
2134
|
+
}
|
|
2135
|
+
)
|
|
2136
|
+
] }),
|
|
2137
|
+
state.thinkEnabled && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-tidy-row", children: [
|
|
2138
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-tidy-row-info", children: [
|
|
2139
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-tidy-row-title", children: t("thinkTimeoutTitle") }),
|
|
2140
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-tidy-row-desc", children: t("thinkTimeoutDesc") })
|
|
2141
|
+
] }),
|
|
2142
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2143
|
+
import_dsh_client_ui_primitives.Input,
|
|
2144
|
+
{
|
|
2145
|
+
type: "number",
|
|
2146
|
+
className: "dsh-tidy-field-timeout",
|
|
2147
|
+
min: 500,
|
|
2148
|
+
max: 9e5,
|
|
2149
|
+
step: 1e3,
|
|
2150
|
+
value: state.thinkTimeoutMs,
|
|
2151
|
+
onChange: (e) => {
|
|
2152
|
+
const val = parseInt(e.target.value, 10);
|
|
2153
|
+
if (!Number.isFinite(val)) return;
|
|
2154
|
+
settingsStore.update({
|
|
2155
|
+
thinkTimeoutMs: Math.min(Math.max(val, 500), 9e5)
|
|
2156
|
+
});
|
|
2157
|
+
},
|
|
2158
|
+
"aria-label": t("thinkTimeoutTitle")
|
|
2159
|
+
}
|
|
2160
|
+
)
|
|
1559
2161
|
] })
|
|
1560
2162
|
] })
|
|
1561
2163
|
] }),
|
|
1562
2164
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-tidy-card", children: [
|
|
1563
2165
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-tidy-title", children: [
|
|
1564
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "
|
|
2166
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("bingTitle") }),
|
|
1565
2167
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1566
|
-
Switch,
|
|
2168
|
+
import_dsh_client_ui_primitives.Switch,
|
|
1567
2169
|
{
|
|
1568
2170
|
checked: state.bingEnabled,
|
|
1569
|
-
onChange: () => settingsStore.update({ bingEnabled:
|
|
1570
|
-
label: "
|
|
2171
|
+
onChange: (next) => settingsStore.update({ bingEnabled: next }),
|
|
2172
|
+
label: t("enableBing")
|
|
1571
2173
|
}
|
|
1572
2174
|
)
|
|
1573
2175
|
] }),
|
|
1574
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-tidy-desc", children: "
|
|
2176
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-tidy-desc", children: t("bingDesc") })
|
|
1575
2177
|
] }),
|
|
1576
2178
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-tidy-card", children: [
|
|
1577
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-tidy-row-title", children: "
|
|
2179
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-tidy-row-title", children: t("behaviorTitle") }),
|
|
1578
2180
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", { className: "dsh-tidy-desc dsh-tidy-behavior-list", children: [
|
|
1579
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: "
|
|
1580
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: "
|
|
1581
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: "
|
|
1582
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: "
|
|
1583
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: "
|
|
2181
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: t("behavior1") }),
|
|
2182
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: t("behavior2") }),
|
|
2183
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: t("behavior3") }),
|
|
2184
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: t("behavior4") }),
|
|
2185
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: t("behavior5") })
|
|
1584
2186
|
] })
|
|
1585
2187
|
] }),
|
|
1586
2188
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-tidy-card", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-tidy-row", children: [
|
|
1587
2189
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-tidy-row-info", children: [
|
|
1588
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-tidy-row-title", children: "
|
|
1589
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-tidy-row-desc", children: "
|
|
2190
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-tidy-row-title", children: t("concurrencyTitle") }),
|
|
2191
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-tidy-row-desc", children: t("concurrencyDesc") })
|
|
1590
2192
|
] }),
|
|
1591
2193
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1592
|
-
|
|
2194
|
+
import_dsh_client_ui_primitives.Input,
|
|
1593
2195
|
{
|
|
1594
2196
|
type: "number",
|
|
1595
|
-
className: "dsh-tidy-
|
|
2197
|
+
className: "dsh-tidy-field-count",
|
|
1596
2198
|
min: 1,
|
|
1597
2199
|
max: 100,
|
|
1598
2200
|
step: 1,
|
|
@@ -1602,8 +2204,7 @@ function TidySettingsPanel(_props) {
|
|
|
1602
2204
|
if (!Number.isFinite(val)) return;
|
|
1603
2205
|
settingsStore.update({ concurrency: Math.min(Math.max(val, 1), 100) });
|
|
1604
2206
|
},
|
|
1605
|
-
|
|
1606
|
-
"aria-label": "\u6700\u5927\u7FFB\u8BD1\u5E76\u53D1\u6570"
|
|
2207
|
+
"aria-label": t("concurrencyTitle")
|
|
1607
2208
|
}
|
|
1608
2209
|
)
|
|
1609
2210
|
] }) })
|
|
@@ -1660,7 +2261,7 @@ function setupSettingsUi(ctx) {
|
|
|
1660
2261
|
|
|
1661
2262
|
// src/client/index.ts
|
|
1662
2263
|
var name = "dsh-chat-translate";
|
|
1663
|
-
var inject = ["slots", "settingsScope", "remote", "remote.credentials"];
|
|
2264
|
+
var inject = ["slots", "settingsScope", "remote", "remote.credentials", "locale"];
|
|
1664
2265
|
function apply(ctx) {
|
|
1665
2266
|
ctx.effect(() => chatTranslateObserver.start(document), "dsh-chat-translate: title translate observer");
|
|
1666
2267
|
ctx.effect(() => setupSettingsUi(ctx), "dsh-chat-translate: settings section");
|