dsh-think-translate 1.0.3 → 1.0.5
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/lib/client.js +117 -13
- package/lib/index.js +21 -8
- package/package.json +4 -1
package/lib/client.js
CHANGED
|
@@ -6,6 +6,14 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
var react = require("react");
|
|
8
8
|
var React = react.default !== undefined ? react.default : react;
|
|
9
|
+
// Official Markdown renderer (loaded via the client module graph, declared
|
|
10
|
+
// in dsh.client.external). Using it keeps official formatting — code-block
|
|
11
|
+
// copy, syntax highlighting, tables, footnotes, file mentions — while this
|
|
12
|
+
// plugin only supplies translated text. Falls back to the local Markdownish
|
|
13
|
+
// if the dependency is unavailable.
|
|
14
|
+
var primitives = null;
|
|
15
|
+
try { primitives = require("@deepseek-ai/dsh-client-ui-primitives"); } catch (e) { primitives = null; }
|
|
16
|
+
var MarkdownText = primitives && primitives.MarkdownText ? primitives.MarkdownText : null;
|
|
9
17
|
var createElement = function (type, props) {
|
|
10
18
|
var children = [];
|
|
11
19
|
for (var i = 2; i < arguments.length; i++) children.push(arguments[i]);
|
|
@@ -72,7 +80,12 @@ window.__ModuleLoader__.load({
|
|
|
72
80
|
".xl-cfg{width:100%;box-sizing:border-box;font-family:monospace;font-size:11px;line-height:1.5;padding:8px;border-radius:8px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.35));background:var(--dsw-alias-bg-base,rgba(127,127,127,.08));color:var(--dsw-alias-label-primary,inherit);outline:none}",
|
|
73
81
|
".xl-cfg:focus{border-color:var(--dsw-alias-border-l2,rgba(127,127,127,.6));box-shadow:0 0 0 2px rgba(76,154,255,.2)}",
|
|
74
82
|
".xl-progress{height:6px;border-radius:3px;background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.15));overflow:hidden;margin:4px 0}",
|
|
75
|
-
".xl-progress-bar{height:100%;background:var(--dsw-alias-brand-primary,#4c9aff);border-radius:3px;transition:width .4s}"
|
|
83
|
+
".xl-progress-bar{height:100%;background:var(--dsw-alias-brand-primary,#4c9aff);border-radius:3px;transition:width .4s}",
|
|
84
|
+
".xl-table-wrap{overflow-x:auto;margin:6px 0}",
|
|
85
|
+
".xl-table{border-collapse:collapse;min-width:100%;font-size:13px;line-height:1.5}",
|
|
86
|
+
".xl-table th,.xl-table td{border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.35));padding:6px 10px;text-align:left;vertical-align:top}",
|
|
87
|
+
".xl-table th{background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.1));font-weight:600}",
|
|
88
|
+
".xl-table code{font-family:var(--dsw-font-mono,monospace);font-size:12px}"
|
|
76
89
|
].join("\n");
|
|
77
90
|
if (typeof document !== "undefined" && document.querySelector('style[data-plugin-css="dsh-think-translate/styles.css"]') === null) {
|
|
78
91
|
var tag = document.createElement("style");
|
|
@@ -130,6 +143,7 @@ window.__ModuleLoader__.load({
|
|
|
130
143
|
providerUnavailable: "翻译服务未加载:插件的 host 半边未生效(常见于刚改过配置或更新后)。请重启 Web 再试——重启后翻译与设置将恢复正常。",
|
|
131
144
|
providerDesc: "Google / Bing / 本地模型(自动选择)",
|
|
132
145
|
labels: { google: "google gtx(免费)", bing: "bing(免费)", openai: "本地部署模型(Ollama)" },
|
|
146
|
+
copy: "复制", copied: "已复制",
|
|
133
147
|
note: "界面翻译:思考链、任务卡片、回答正文会显示为你选的语言,原文保留在会话记录中可随时对照。优先使用本地模型,其次 Google/Bing;文件路径、代码等自动跳过。",
|
|
134
148
|
},
|
|
135
149
|
"en": {
|
|
@@ -144,6 +158,7 @@ window.__ModuleLoader__.load({
|
|
|
144
158
|
providerUnavailable: "Translation service not loaded: the plugin host half is inactive (common after config changes or updates). Restart the web app to restore translation and settings.",
|
|
145
159
|
providerDesc: "Google / Bing / local model (auto)",
|
|
146
160
|
labels: { google: "google gtx (free)", bing: "bing (free)", openai: "Local model (Ollama)" },
|
|
161
|
+
copy: "Copy", copied: "Copied",
|
|
147
162
|
note: "UI translation: the thinking chain, task cards and answer text display in your chosen language; originals stay in the transcript for comparison. Local model first, then Google/Bing; file paths and code are skipped.",
|
|
148
163
|
},
|
|
149
164
|
"ja": {
|
|
@@ -237,6 +252,29 @@ window.__ModuleLoader__.load({
|
|
|
237
252
|
// system proxy automatically; endpoint returns CORS *)
|
|
238
253
|
// ------------------------------------------------------------------
|
|
239
254
|
var mem = new Map();
|
|
255
|
+
// Translation cache persisted to localStorage so switching sessions or
|
|
256
|
+
// refreshing a page reuses prior results instead of re-translating every
|
|
257
|
+
// historical message (which wedged the UI when the local model was slow,
|
|
258
|
+
// 2026-08-26). In-memory map is the fast path; localStorage is the
|
|
259
|
+
// durable cross-session floor.
|
|
260
|
+
var MEM_KEY = "dsh-xlate-cache:";
|
|
261
|
+
function cacheMemGet(key) {
|
|
262
|
+
var v = mem.get(key);
|
|
263
|
+
if (v !== undefined) return v;
|
|
264
|
+
try {
|
|
265
|
+
var lv = localStorage.getItem(MEM_KEY + key);
|
|
266
|
+
if (lv !== null) {
|
|
267
|
+
var parsed = JSON.parse(lv);
|
|
268
|
+
if (parsed && parsed.ok) { mem.set(key, parsed); return parsed; }
|
|
269
|
+
}
|
|
270
|
+
} catch (e) {}
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
function cacheMemSet(key, entry) {
|
|
274
|
+
mem.set(key, entry);
|
|
275
|
+
if (mem.size > 800) mem.delete(mem.keys().next().value);
|
|
276
|
+
try { localStorage.setItem(MEM_KEY + key, JSON.stringify(entry)); } catch (e) {}
|
|
277
|
+
}
|
|
240
278
|
var pend = new Map();
|
|
241
279
|
var stats = { ok: 0, fail: 0, lastError: null, lastProvider: null, lastModel: null };
|
|
242
280
|
// Diagnostic ledger surfaced in the settings panel (2026-08-24): bundle
|
|
@@ -365,7 +403,7 @@ window.__ModuleLoader__.load({
|
|
|
365
403
|
|
|
366
404
|
function requestTranslate(text, target) {
|
|
367
405
|
var key = target + "|" + fnv(text);
|
|
368
|
-
var hit =
|
|
406
|
+
var hit = cacheMemGet(key);
|
|
369
407
|
if (hit !== undefined) return Promise.resolve(hit);
|
|
370
408
|
var busy = pend.get(key);
|
|
371
409
|
if (busy !== undefined) return busy;
|
|
@@ -411,21 +449,20 @@ window.__ModuleLoader__.load({
|
|
|
411
449
|
return p;
|
|
412
450
|
}
|
|
413
451
|
function memSet(key, entry) {
|
|
414
|
-
|
|
415
|
-
if (mem.size > 800) mem.delete(mem.keys().next().value);
|
|
452
|
+
cacheMemSet(key, entry);
|
|
416
453
|
}
|
|
417
454
|
|
|
418
455
|
function useXlate(text, enabled) {
|
|
419
456
|
var s = useStore();
|
|
420
457
|
var active = !!(enabled && text && text.trim());
|
|
421
458
|
var key = active ? s.lang + "|" + fnv(text) : null;
|
|
422
|
-
var pair = useState(function () { return key !== null ?
|
|
459
|
+
var pair = useState(function () { return key !== null ? cacheMemGet(key) : undefined; });
|
|
423
460
|
var entry = pair[0];
|
|
424
461
|
var setEntry = pair[1];
|
|
425
462
|
useEffect(function () {
|
|
426
463
|
if (!active || key === null) { setEntry(undefined); return undefined; }
|
|
427
464
|
var alive = true;
|
|
428
|
-
var hit =
|
|
465
|
+
var hit = cacheMemGet(key);
|
|
429
466
|
if (hit !== undefined) { setEntry(hit); return undefined; }
|
|
430
467
|
requestTranslate(text, s.lang).then(function (r) {
|
|
431
468
|
if (!alive) return;
|
|
@@ -607,6 +644,21 @@ window.__ModuleLoader__.load({
|
|
|
607
644
|
if (last < s.length) out.push(s.slice(last));
|
|
608
645
|
return out;
|
|
609
646
|
}
|
|
647
|
+
function tableCells(line) {
|
|
648
|
+
var s = line.trim();
|
|
649
|
+
if (s.charAt(0) === "|") s = s.slice(1);
|
|
650
|
+
if (s.charAt(s.length - 1) === "|") s = s.slice(0, -1);
|
|
651
|
+
return s.split("|").map(function (c) { return c.trim(); });
|
|
652
|
+
}
|
|
653
|
+
function isDelimRow(line) {
|
|
654
|
+
var s = line.trim();
|
|
655
|
+
if (s.charAt(0) === "|") s = s.slice(1);
|
|
656
|
+
if (s.charAt(s.length - 1) === "|") s = s.slice(0, -1);
|
|
657
|
+
var parts = s.split("|");
|
|
658
|
+
if (!parts.length) return false;
|
|
659
|
+
for (var p = 0; p < parts.length; p++) { if (!/^[\s:-]*$/.test(parts[p])) return false; }
|
|
660
|
+
return /-/.test(s);
|
|
661
|
+
}
|
|
610
662
|
function Markdownish(props) {
|
|
611
663
|
var lines = props.text.split("\n");
|
|
612
664
|
var blocks = [];
|
|
@@ -652,6 +704,23 @@ window.__ModuleLoader__.load({
|
|
|
652
704
|
})));
|
|
653
705
|
continue;
|
|
654
706
|
}
|
|
707
|
+
if (/^\s*\|/.test(line) && i + 1 < lines.length && isDelimRow(lines[i + 1])) {
|
|
708
|
+
var headCells = tableCells(line);
|
|
709
|
+
i += 2;
|
|
710
|
+
var bodyRows = [];
|
|
711
|
+
while (i < lines.length && /^\s*\|/.test(lines[i])) { bodyRows.push(tableCells(lines[i])); i++; }
|
|
712
|
+
var thead = createElement("thead", null, createElement("tr", null, headCells.map(function (c, n) {
|
|
713
|
+
return createElement("th", { key: n }, inlineNodes(c, nk() + "h" + n));
|
|
714
|
+
})));
|
|
715
|
+
var tbody = bodyRows.map(function (r, n) {
|
|
716
|
+
return createElement("tr", { key: n }, r.map(function (c, j) {
|
|
717
|
+
return createElement("td", { key: j }, inlineNodes(c, nk() + "d" + n + "j" + j));
|
|
718
|
+
}));
|
|
719
|
+
});
|
|
720
|
+
blocks.push(createElement("div", { key: nk(), className: "xl-table-wrap" },
|
|
721
|
+
createElement("table", { className: "xl-table" }, thead, createElement("tbody", null, tbody))));
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
655
724
|
var para = [line];
|
|
656
725
|
i++;
|
|
657
726
|
while (i < lines.length && lines[i].trim() !== "" && !/^\s*(?:```|#{1,4}\s|>|[-*+]\s|\d+[.)]\s)/.test(lines[i])) { para.push(lines[i]); i++; }
|
|
@@ -661,11 +730,25 @@ window.__ModuleLoader__.load({
|
|
|
661
730
|
}
|
|
662
731
|
function TextBlock(props) {
|
|
663
732
|
var s = useStore();
|
|
733
|
+
var tt = UI_TEXT[s.lang] || UI_TEXT.en;
|
|
664
734
|
var tr = useXlate(props.text, s.ans && !props.streaming);
|
|
665
735
|
var hasTr = !!(tr && tr.ok && !tr.skipped && tr.text !== props.text);
|
|
736
|
+
// Official MarkdownText when available: keeps the full official
|
|
737
|
+
// formatting (code copy, highlighting, tables...). Translated text
|
|
738
|
+
// replaces the body when answer translation is on; original stays in
|
|
739
|
+
// a collapsible details block.
|
|
740
|
+
var codeLabels = MarkdownText ? { copyLabel: tt.copy, copiedLabel: tt.copied } : undefined;
|
|
741
|
+
var MDT = MarkdownText || Markdownish;
|
|
742
|
+
var body = hasTr ? tr.text : props.text;
|
|
666
743
|
return createElement("div", null,
|
|
667
|
-
createElement(
|
|
668
|
-
|
|
744
|
+
createElement(MDT, MarkdownText
|
|
745
|
+
? { text: body, streaming: props.streaming, codeLabels: codeLabels }
|
|
746
|
+
: { text: body, streaming: props.streaming }),
|
|
747
|
+
hasTr ? createElement("details", { className: "xl-orig" },
|
|
748
|
+
createElement("summary", null, tt.original),
|
|
749
|
+
createElement("div", null, createElement(MDT, MarkdownText
|
|
750
|
+
? { text: props.text, codeLabels: codeLabels }
|
|
751
|
+
: { text: props.text }))) : null);
|
|
669
752
|
}
|
|
670
753
|
|
|
671
754
|
function AssistantView(props) {
|
|
@@ -797,7 +880,17 @@ window.__ModuleLoader__.load({
|
|
|
797
880
|
stats.lastError ? createElement("div", { className: "xl-err" }, t.lastError + ": " + stats.lastError) : null,
|
|
798
881
|
createElement("button", {
|
|
799
882
|
className: "xl-btn", type: "button",
|
|
800
|
-
onClick: function () {
|
|
883
|
+
onClick: function () {
|
|
884
|
+
mem.clear();
|
|
885
|
+
try {
|
|
886
|
+
var keys = [];
|
|
887
|
+
for (var i = 0; i < localStorage.length; i++) {
|
|
888
|
+
var k = localStorage.key(i);
|
|
889
|
+
if (k && k.indexOf(MEM_KEY) === 0) keys.push(k);
|
|
890
|
+
}
|
|
891
|
+
for (var j = 0; j < keys.length; j++) localStorage.removeItem(keys[j]);
|
|
892
|
+
} catch (e) {}
|
|
893
|
+
}
|
|
801
894
|
}, t.clearCache));
|
|
802
895
|
}
|
|
803
896
|
|
|
@@ -812,10 +905,21 @@ window.__ModuleLoader__.load({
|
|
|
812
905
|
var models = modelsPair[0], setModels = modelsPair[1];
|
|
813
906
|
useEffect(function () {
|
|
814
907
|
var alive = true;
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
908
|
+
var tries = 0;
|
|
909
|
+
function attempt() {
|
|
910
|
+
if (!alive) return;
|
|
911
|
+
fetch("/_xlate/config").then(function (r) {
|
|
912
|
+
if (!r.ok) throw new Error("HTTP " + r.status);
|
|
913
|
+
return r.json();
|
|
914
|
+
}).then(function (c) { if (alive) setCfg(c); })
|
|
915
|
+
.catch(function () {
|
|
916
|
+
if (!alive) return;
|
|
917
|
+
tries++;
|
|
918
|
+
if (tries < 15) { setTimeout(attempt, 1500); }
|
|
919
|
+
else { setCfg(null); }
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
attempt();
|
|
819
923
|
return function () { alive = false; };
|
|
820
924
|
}, []);
|
|
821
925
|
useEffect(function () {
|
package/lib/index.js
CHANGED
|
@@ -271,14 +271,27 @@ async function viaOpenAI(text, to, cfg) {
|
|
|
271
271
|
// (2026-08-24: zh->ja via host chain returned garbage/off-topic output
|
|
272
272
|
// while direct fetch worked). Loopback bypasses the system proxy anyway,
|
|
273
273
|
// so fetch is both correct and proxy-safe here.
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
274
|
+
//
|
|
275
|
+
// A local model with insufficient VRAM can hang the request indefinitely
|
|
276
|
+
// (fetch has no default timeout), which stalls the whole host chain and
|
|
277
|
+
// every pending request. Bound it so a slow local model fails fast and the
|
|
278
|
+
// chain falls through to google/bing instead of wedging the UI (2026-08-26).
|
|
279
|
+
const controller = new AbortController()
|
|
280
|
+
const timer = setTimeout(function () { controller.abort() }, 15000)
|
|
281
|
+
let res
|
|
282
|
+
try {
|
|
283
|
+
res = await fetch(url, {
|
|
284
|
+
method: 'POST',
|
|
285
|
+
headers: {
|
|
286
|
+
'Authorization': 'Bearer ' + cfg.apiKey,
|
|
287
|
+
'Content-Type': 'application/json',
|
|
288
|
+
},
|
|
289
|
+
body,
|
|
290
|
+
signal: controller.signal,
|
|
291
|
+
})
|
|
292
|
+
} finally {
|
|
293
|
+
clearTimeout(timer)
|
|
294
|
+
}
|
|
282
295
|
if (!res.ok) throw new Error('openai HTTP ' + res.status)
|
|
283
296
|
raw = await res.text()
|
|
284
297
|
} else {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-think-translate",
|
|
3
3
|
"description": "Display-layer translation for the DSH web app: thinking chain, task cards, and optional answer text, in 8 target languages (local Ollama / Google / Bing, fail-open)",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.5",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
"immediately": true,
|
|
28
28
|
"inject": [
|
|
29
29
|
"@deepseek-ai/dsh-client-runtime"
|
|
30
|
+
],
|
|
31
|
+
"external": [
|
|
32
|
+
"@deepseek-ai/dsh-client-ui-primitives"
|
|
30
33
|
]
|
|
31
34
|
},
|
|
32
35
|
"bundle": {
|