@lynn123411/dsh-chat-translate 3.0.1 → 3.2.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 +7 -7
- package/docs//351/205/215/347/275/256.md +6 -5
- package/lib/client.js +312 -184
- package/lib/client.js.map +4 -4
- package/lib/index.js +369 -335
- package/lib/index.js.map +4 -4
- package/lib/types/client/chat/assistant-step.d.ts +7 -3
- package/lib/types/client/chat/click-guard.d.ts +10 -9
- package/lib/types/client/chat/row-plan.d.ts +26 -8
- package/lib/types/client/chat/styles.d.ts +51 -6
- package/lib/types/client/chat/translate-store.d.ts +16 -16
- package/lib/types/client/locales.d.ts +20 -2
- package/lib/types/client/translate/api.d.ts +1 -8
- package/lib/types/server/cache.d.ts +1 -1
- package/lib/types/server/dispatcher.d.ts +30 -26
- package/lib/types/server/pipeline/blocks.d.ts +7 -11
- package/lib/types/server/pipeline/segments.d.ts +68 -0
- package/lib/types/server/prompt-revision.d.ts +1 -1
- package/lib/types/server/types.d.ts +15 -13
- package/package.json +4 -2
- package/lib/types/server/pipeline/mask-tokens.d.ts +0 -64
- package/lib/types/server/pipeline/masking.d.ts +0 -45
package/lib/client.js
CHANGED
|
@@ -85,8 +85,7 @@ async function requestTranslateReply(blocks) {
|
|
|
85
85
|
original: block,
|
|
86
86
|
translated: block,
|
|
87
87
|
ok: false,
|
|
88
|
-
cached: false
|
|
89
|
-
channel: "fallback-client"
|
|
88
|
+
cached: false
|
|
90
89
|
}));
|
|
91
90
|
if (!Array.isArray(blocks) || blocks.length === 0) return [];
|
|
92
91
|
try {
|
|
@@ -545,7 +544,7 @@ var zh = {
|
|
|
545
544
|
title: "\u6B63\u6587\u7FFB\u8BD1",
|
|
546
545
|
masterTitle: "\u7FFB\u8BD1\u603B\u5F00\u5173",
|
|
547
546
|
enableTranslation: "\u542F\u7528\u7FFB\u8BD1",
|
|
548
|
-
masterDesc: "\u81EA\u52A8\u628A\u52A9\u624B\u56DE\u7B54\u6B63\u6587\u7FFB\u6210\u4E2D\u6587\uFF08\u5982 {example}\uFF09\uFF1A\u6298\u53E0\u5757\u91CC\u7684\u8FC7\u7A0B\u6B63\u6587\u4E0E\u6700\u7EC8\u6C47\u603B\u4E00\u89C6\u540C\u4EC1\uFF0C\u6574\u6BB5\u4E2D\u6587\u7684\u5757\u4E5F\u7167\u9001\uFF0C\u7531\u6A21\u578B\u4E00\u5E76\u6539\u5199\u6210\u81EA\u7136\u4E2D\u6587\u3001\u987A\u6389\u673A\u5668\u8154\u3002Think \u5361\u4E0E\u5DE5\u5177\u8C03\u7528\u884C\u4E0D\u7FFB\uFF0C\u4EE3\u7801\u5757\u539F\u6837\u4FDD\u7559\u3002\
|
|
547
|
+
masterDesc: "\u81EA\u52A8\u628A\u52A9\u624B\u56DE\u7B54\u6B63\u6587\u7FFB\u6210\u4E2D\u6587\uFF08\u5982 {example}\uFF09\uFF1A\u6298\u53E0\u5757\u91CC\u7684\u8FC7\u7A0B\u6B63\u6587\u4E0E\u6700\u7EC8\u6C47\u603B\u4E00\u89C6\u540C\u4EC1\uFF0C\u6574\u6BB5\u4E2D\u6587\u7684\u5757\u4E5F\u7167\u9001\uFF0C\u7531\u6A21\u578B\u4E00\u5E76\u6539\u5199\u6210\u81EA\u7136\u4E2D\u6587\u3001\u987A\u6389\u673A\u5668\u8154\u3002Think \u5361\u4E0E\u5DE5\u5177\u8C03\u7528\u884C\u4E0D\u7FFB\uFF0C\u4EE3\u7801\u5757\u539F\u6837\u4FDD\u7559\u3002\u6B63\u6587\u5757\u7684\u5DE6\u7F18\u4E00\u6761\u7AD6\u7EBF\u62A5\u51FA\u72B6\u6001\uFF1A\u6B63\u5728\u8BFB\u8BD1\u6587\u662F\u84DD\u7EBF\uFF0C\u5907\u7740\u8BD1\u6587\u6B63\u8BFB\u539F\u6587\u662F\u7070\u7EC6\u7EBF\uFF0C\u6CA1\u8BD1\u6210\u662F\u7EA2\u5B9E\u7EBF\uFF08\u9F20\u6807\u60AC\u505C\u70ED\u533A\u62A5\u51FA\u901A\u9053\u7EC6\u8282\u2014\u2014\u8D85\u65F6\u3001\u65AD\u6D41\u6216\u7A7A\u8FD4\u56DE\uFF1B\u8BD1\u6587\u7684\u7ED3\u6784\u6F02\u79FB\u7531\u5BBF\u4E3B\u81EA\u52A8\u4FEE\u56DE\u6216\u91CD\u63B7\uFF0C\u4E0D\u518D\u56E0\u683C\u5F0F\u95EE\u9898\u6302\u7EA2\u7EBF\uFF09\uFF0C\u6B63\u5728\u8BF7\u6C42\u662F\u7070\u8109\u52A8\u7EBF\uFF0C\u6CA1\u9001\u8FC7\u6A21\u578B\u6216\u5F00\u5173\u5173\u95ED\u6CA1\u6709\u7EBF\u3002\u5207\u6362\u4E0D\u5728\u6B63\u6587\u4E0A\uFF1A\u8D34\u7740\u90A3\u6761\u7EBF\u7684\u7A84\u5E26\u624D\u662F\u64CD\u4F5C\u533A\uFF0C\u9F20\u6807\u79FB\u5230\u5B83\u4E0A\u9762\u53D8\u624B\u578B\uFF0C\u70B9\u4E00\u4E0B\u5728\u8BD1\u6587\u4E0E\u539F\u6587\u95F4\u5207\u6362\u2014\u2014\u6B63\u6587\u6574\u5757\u4E0D\u518D\u54CD\u5E94\u70B9\u51FB\uFF0C\u9009\u5B57\u3001\u62D6\u9009\u590D\u5236\u90FD\u4E0D\u4F1A\u8BEF\u89E6\uFF1B\u6A21\u578B\u8BA4\u4E3A\u539F\u6837\u6700\u597D\u3001\u8FD4\u56DE\u4E00\u6A21\u4E00\u6837\u7684\u6587\u5B57\u65F6\u540C\u6837\u7B97\u7FFB\u8BD1\u8FC7\u3001\u540C\u6837\u53EF\u5207\u3002\u6302\u7EA2\u7EBF\u7684\u5757\u53F3\u4FA7\u5E38\u9A7B\u4E00\u4E2A\u5C0F \u21BB\uFF0C\u70B9\u90A3\u6761\u7A84\u5E26\u5373\u6574\u884C\u91CD\u65B0\u7FFB\u8BD1\uFF0C\u5DF2\u6210\u529F\u7684\u5757\u4FDD\u6301\u663E\u793A\u4E0D\u6253\u6270\u3002\u901A\u9053\u5931\u8D25\u4E0D\u81EA\u52A8\u91CD\u8BD5\uFF0C\u6062\u590D\u540E\u7531\u4F60\u70B9\u90A3\u6761\u7A84\u5E26\u6551\u6D3B\u3002\u8BD1\u6587\u6309\u539F markdown \u683C\u5F0F\u91CD\u65B0\u6E32\u67D3\uFF0C\u8868\u683C\u3001\u5217\u8868\u3001\u52A0\u7C97\u4E0E\u884C\u5185\u4EE3\u7801\u4FDD\u6301\u7ED3\u6784\u3002\u56DE\u7B54\u8FD8\u5728\u6D41\u5F0F\u8F93\u51FA\u65F6\u4E0D\u7FFB\uFF0C\u843D\u5B9A\u540E\u6309\u9605\u8BFB\u987A\u5E8F\u9010\u6BB5\u51FA\u4E2D\u6587\u3002\u53EA\u4F5C\u7528\u4E8E\u5F53\u524D\u67E5\u770B\u7684\u4F1A\u8BDD\uFF0C\u8BD1\u6587\u4E0D\u5199\u56DE\u4F1A\u8BDD\u4E0A\u4E0B\u6587\u3002\u8F93\u5165\u6846\u4E0B\u65B9\u90A3\u4E00\u884C\u6709\u540C\u6B3E\u5F00\u5173\uFF0C\u4E24\u5904\u72B6\u6001\u540C\u6B65\u3002",
|
|
549
548
|
aiTitle: "AI \u7FFB\u8BD1\uFF08OpenAI \u517C\u5BB9\u534F\u8BAE\uFF09",
|
|
550
549
|
aiRule: "\u53EA\u6709\u8FD9\u4E00\u6761\u901A\u9053\uFF1AKey / Base URL / \u6A21\u578B\u9F50\u5907\u624D\u4F1A\u7FFB\u8BD1\uFF0C\u7F3A\u4EFB\u4F55\u4E00\u9879\u90FD\u4FDD\u6301\u539F\u6587\u3002",
|
|
551
550
|
badgeConfigured: "\u5DF2\u914D\u7F6E",
|
|
@@ -570,17 +569,22 @@ var zh = {
|
|
|
570
569
|
testOk: "\u8FDE\u63A5\u6B63\u5E38\uFF0C\u5EF6\u8FDF {latency}ms",
|
|
571
570
|
testFail: "\u5931\u8D25\uFF1A{error}",
|
|
572
571
|
behaviorTitle: "\u7FFB\u8BD1\u8303\u56F4",
|
|
573
|
-
behaviorRule: "\u52A9\u624B\u56DE\u7B54\u6B63\u6587\u4E00\u5F8B\u7FFB\u8BD1\uFF0C\u5305\u62EC\u6700\u7EC8\u6C47\u603B\u4E0E\u6574\u6BB5\u4E2D\u6587\u7684\u5757\uFF1A\u4E2D\u6587\u5757\u540C\u6837\u4EA4\u7ED9\u6A21\u578B\u6539\u5199\u6210\u81EA\u7136\u4E2D\u6587\uFF0C\u610F\u4E49\u3001\u672F\u8BED\u4E0E\u6570\u5B57\u4E0D\u53D8\u3002\u601D\u8003\u94FE\u6B63\u6587\u3001\u5DE5\u5177\u8C03\u7528\u6807\u9898\u4E0E\u6298\u53E0\u6458\u8981\u4E0D\u7FFB\u8BD1\uFF0C\u4EE3\u7801\u5757\u53CA\u5176\u5185\u90E8\u539F\u6837\u4FDD\u7559\u3002",
|
|
572
|
+
behaviorRule: "\u52A9\u624B\u56DE\u7B54\u6B63\u6587\u4E00\u5F8B\u7FFB\u8BD1\uFF0C\u5305\u62EC\u6700\u7EC8\u6C47\u603B\u4E0E\u6574\u6BB5\u4E2D\u6587\u7684\u5757\uFF1A\u4E2D\u6587\u5757\u540C\u6837\u4EA4\u7ED9\u6A21\u578B\u6539\u5199\u6210\u81EA\u7136\u4E2D\u6587\uFF0C\u610F\u4E49\u3001\u672F\u8BED\u4E0E\u6570\u5B57\u4E0D\u53D8\uFF1B\u7A7A\u767D\u5757\u4E0D\u9001\u8BD1\u3001\u4E0D\u6302\u7EBF\u3002\u601D\u8003\u94FE\u6B63\u6587\u3001\u5DE5\u5177\u8C03\u7528\u6807\u9898\u4E0E\u6298\u53E0\u6458\u8981\u4E0D\u7FFB\u8BD1\uFF0C\u4EE3\u7801\u5757\u53CA\u5176\u5185\u90E8\u539F\u6837\u4FDD\u7559\u3002",
|
|
574
573
|
dockLabel: "\u8BD1",
|
|
575
574
|
dockOn: "\u6B63\u6587\u7FFB\u8BD1\u5DF2\u5F00\u542F\uFF0C\u70B9\u51FB\u5173\u95ED",
|
|
576
|
-
dockOff: "\u6B63\u6587\u7FFB\u8BD1\u5DF2\u5173\u95ED\uFF0C\u70B9\u51FB\u5F00\u542F"
|
|
575
|
+
dockOff: "\u6B63\u6587\u7FFB\u8BD1\u5DF2\u5173\u95ED\uFF0C\u70B9\u51FB\u5F00\u542F",
|
|
576
|
+
failTitle: "\u7FFB\u8BD1\u5931\u8D25",
|
|
577
|
+
retryAria: "\u7FFB\u8BD1\u5931\u8D25\uFF0C\u70B9\u5DE6\u7F18\u90A3\u6761\u7A84\u5E26\u53EF\u6309 Enter \u91CD\u8BD5",
|
|
578
|
+
toggleToOriginalAria: "\u67E5\u770B\u539F\u6587",
|
|
579
|
+
toggleToTranslatedAria: "\u67E5\u770B\u8BD1\u6587",
|
|
580
|
+
inflightAria: "\u7FFB\u8BD1\u4E2D\uFF0C\u6B64\u5904\u6682\u65E0\u52A8\u4F5C"
|
|
577
581
|
};
|
|
578
582
|
var en = {
|
|
579
583
|
pageNav: "Reading",
|
|
580
584
|
title: "Reply translate",
|
|
581
585
|
masterTitle: "Translation master switch",
|
|
582
586
|
enableTranslation: "Enable translation",
|
|
583
|
-
masterDesc: "
|
|
587
|
+
masterDesc: "Automatically translates assistant reply prose (e.g. {example}) into Chinese: text inside disclosure blocks and the final summary are treated alike, and blocks written entirely in Chinese go through the model too, which rewrites them into natural Chinese and smooths out machine-flavored phrasing. Think cards and tool-call rows are never translated, and code blocks stay untouched. Each prose block reports its state with a left-edge line: blue while its translation reads, thin grey while the original reads with a translation waiting one click away, solid red when translation failed (hover the slim strip beside the line to read the channel detail \u2014 a timeout, a broken stream or an empty return; the answer's Markdown shape drift is repaired or re-rolled by the host, so formatting no longer draws a red line), a grey pulse while a request is in flight, and nothing at all when the block was never sent or the switch is off. Switching does not live on the prose: a slim strip hugging that line is the control \u2014 the pointer turns into a hand over it, and one click swaps translation and original, while the prose itself no longer reacts to clicks, so selecting or drag-copying text never triggers a swap by accident. A reply the model returned unchanged still counts as translated and stays switchable. A red-lined block keeps a small \u21BB beside it; clicking the strip re-runs the whole row, with already-successful blocks kept on screen undisturbed. Channel failures are never retried automatically: once the channel recovers, a click on that strip revives the row. Translations are re-rendered as Markdown, so tables, lists, bold runs and inline code keep their structure. A reply that is still streaming is left alone until it settles, then prose turns to Chinese paragraph by paragraph in reading order. Applies to the session you are viewing only; translations never enter the conversation context. The same switch sits on the row below the composer, sharing its state.",
|
|
584
588
|
aiTitle: "AI translation (OpenAI-compatible)",
|
|
585
589
|
aiRule: "This is the only channel: translation runs when Key, Base URL and model are all present; missing any one leaves the text as it is.",
|
|
586
590
|
badgeConfigured: "Configured",
|
|
@@ -605,11 +609,35 @@ var en = {
|
|
|
605
609
|
testOk: "Connected, {latency}ms latency",
|
|
606
610
|
testFail: "Failed: {error}",
|
|
607
611
|
behaviorTitle: "What gets translated",
|
|
608
|
-
behaviorRule: "All assistant reply prose, the final summary and Chinese-only blocks included: Chinese blocks are also handed to the model, which rewrites them into natural Chinese while keeping meaning, terms and numbers unchanged. Think-chain text, tool-call titles and folded summaries are never translated; code blocks and their contents stay untouched.",
|
|
612
|
+
behaviorRule: "All assistant reply prose, the final summary and Chinese-only blocks included: Chinese blocks are also handed to the model, which rewrites them into natural Chinese while keeping meaning, terms and numbers unchanged; blank blocks are never sent and never marked. Think-chain text, tool-call titles and folded summaries are never translated; code blocks and their contents stay untouched.",
|
|
609
613
|
dockLabel: "Translate",
|
|
610
614
|
dockOn: "Translation is on; click to turn it off",
|
|
611
|
-
dockOff: "Translation is off; click to turn it on"
|
|
615
|
+
dockOff: "Translation is off; click to turn it on",
|
|
616
|
+
failTitle: "Translation failed",
|
|
617
|
+
retryAria: "Translation failed \u2014 focus the slim strip at the left edge and press Enter to retry",
|
|
618
|
+
toggleToOriginalAria: "Show the original",
|
|
619
|
+
toggleToTranslatedAria: "Show the translation",
|
|
620
|
+
inflightAria: "Translating; Enter does nothing yet"
|
|
612
621
|
};
|
|
622
|
+
var rowT = (key) => zh[key] ?? key;
|
|
623
|
+
function bindRowCopy(locale) {
|
|
624
|
+
if (locale && typeof locale.bind === "function") {
|
|
625
|
+
const bound = locale.bind(NS);
|
|
626
|
+
if (typeof bound === "function") rowT = bound;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
function rowCopy() {
|
|
630
|
+
return {
|
|
631
|
+
retryAria: rowT("retryAria"),
|
|
632
|
+
toggleToOriginalAria: rowT("toggleToOriginalAria"),
|
|
633
|
+
toggleToTranslatedAria: rowT("toggleToTranslatedAria"),
|
|
634
|
+
inflightAria: rowT("inflightAria"),
|
|
635
|
+
failTitle: (detail) => {
|
|
636
|
+
const label = rowT("failTitle");
|
|
637
|
+
return detail === void 0 || detail === "" || detail === null ? label : `${label} \u2014 ${detail}`;
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
}
|
|
613
641
|
|
|
614
642
|
// src/client/settings/ui.tsx
|
|
615
643
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -906,6 +934,196 @@ var React = __toESM(require("react"), 1);
|
|
|
906
934
|
var import_react4 = require("react");
|
|
907
935
|
var import_dsh_client_ui_primitives3 = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
908
936
|
|
|
937
|
+
// src/client/chat/row-plan.ts
|
|
938
|
+
function isBlankText(value) {
|
|
939
|
+
return (value ?? "").trim() === "";
|
|
940
|
+
}
|
|
941
|
+
function planAssistantRow(input) {
|
|
942
|
+
const { blocks, streaming, interrupted, groupPart, canTranslate, outcomes, rowStatus, originalKeys } = input;
|
|
943
|
+
const hasVisible = streaming || interrupted || blocks.some((block) => block.kind !== "tool-call");
|
|
944
|
+
if (!hasVisible) return { entries: null, texts: [] };
|
|
945
|
+
const texts = [];
|
|
946
|
+
if (groupPart !== "reasoning") {
|
|
947
|
+
for (const block of blocks) {
|
|
948
|
+
if (block.kind === "text" && !isBlankText(block.text)) texts.push(block.text ?? "");
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
const entries = [];
|
|
952
|
+
let textIndex = 0;
|
|
953
|
+
let imageRun = null;
|
|
954
|
+
const flushImages = () => {
|
|
955
|
+
if (imageRun !== null) {
|
|
956
|
+
entries.push({ type: "images", key: entries.length, attachments: imageRun });
|
|
957
|
+
imageRun = null;
|
|
958
|
+
}
|
|
959
|
+
};
|
|
960
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
961
|
+
const block = blocks[i];
|
|
962
|
+
if (block === void 0) continue;
|
|
963
|
+
if (groupPart === "reasoning" && block.kind !== "reasoning") continue;
|
|
964
|
+
if (groupPart === "response" && block.kind === "reasoning") continue;
|
|
965
|
+
switch (block.kind) {
|
|
966
|
+
case "text": {
|
|
967
|
+
flushImages();
|
|
968
|
+
const blank = isBlankText(block.text);
|
|
969
|
+
const index = textIndex;
|
|
970
|
+
if (!blank) textIndex += 1;
|
|
971
|
+
const key = entries.length;
|
|
972
|
+
const attemptable = !blank && canTranslate && !streaming;
|
|
973
|
+
const outcome = attemptable && outcomes !== void 0 ? outcomes[index] ?? null : null;
|
|
974
|
+
let mark = null;
|
|
975
|
+
let fail = null;
|
|
976
|
+
if (outcome !== null && outcome.ok) {
|
|
977
|
+
mark = originalKeys?.has(key) ? "original-view" : "translated";
|
|
978
|
+
} else if (outcome !== null) {
|
|
979
|
+
mark = "failed";
|
|
980
|
+
fail = outcome.detail ?? null;
|
|
981
|
+
} else if (attemptable && rowStatus === "pending") {
|
|
982
|
+
mark = "inflight";
|
|
983
|
+
}
|
|
984
|
+
entries.push({
|
|
985
|
+
type: "prose",
|
|
986
|
+
key,
|
|
987
|
+
text: block.text ?? "",
|
|
988
|
+
translated: outcome !== null && outcome.ok ? outcome.translated : null,
|
|
989
|
+
mark,
|
|
990
|
+
fail
|
|
991
|
+
});
|
|
992
|
+
break;
|
|
993
|
+
}
|
|
994
|
+
case "reasoning":
|
|
995
|
+
flushImages();
|
|
996
|
+
entries.push({ type: "reasoning", key: entries.length, blockIndex: i, text: block.text ?? "" });
|
|
997
|
+
break;
|
|
998
|
+
case "image":
|
|
999
|
+
if (imageRun === null) imageRun = [];
|
|
1000
|
+
imageRun.push(block.attachment);
|
|
1001
|
+
break;
|
|
1002
|
+
case "tool-call":
|
|
1003
|
+
flushImages();
|
|
1004
|
+
break;
|
|
1005
|
+
default:
|
|
1006
|
+
flushImages();
|
|
1007
|
+
entries.push({ type: "unknown", key: entries.length, block: block.block ?? null });
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
flushImages();
|
|
1011
|
+
const showStopped = interrupted && (groupPart === void 0 || groupPart === "response" || !blocks.some((block) => block.kind !== "reasoning" && block.kind !== "tool-call"));
|
|
1012
|
+
if (showStopped) entries.push({ type: "stopped", key: entries.length });
|
|
1013
|
+
return { entries, texts };
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// src/client/chat/click-guard.ts
|
|
1017
|
+
function isCleanHotspotClick(event, view) {
|
|
1018
|
+
if (event.target !== event.currentTarget) return false;
|
|
1019
|
+
const selection = typeof view?.getSelection === "function" ? view.getSelection() : null;
|
|
1020
|
+
if (selection !== null && selection !== void 0) {
|
|
1021
|
+
if (selection.isCollapsed === false && String(selection.toString?.() ?? "") !== "") return false;
|
|
1022
|
+
}
|
|
1023
|
+
return true;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// src/client/chat/styles.ts
|
|
1027
|
+
var STYLE_TAG_ID = "@lynn123411/dsh-chat-translate/chat-assistant.css";
|
|
1028
|
+
var ASSISTANT_CSS = [
|
|
1029
|
+
// ---- 行骨架(宿主 AssistantMarkdown.root/body 等价) ----
|
|
1030
|
+
".dsh-ct-root{display:flex;flex-direction:column;font-size:var(--dsh-content-font-size,14px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-primary)}",
|
|
1031
|
+
".dsh-ct-body{display:flex;flex-direction:column;gap:16px}",
|
|
1032
|
+
".dsh-ct-body>[data-turn-process-inline][hidden]{margin-bottom:-16px}",
|
|
1033
|
+
// 宽表格溢出内容列(宿主等价规则,类名 md-table-wide 由 MarkdownText 自己产出)
|
|
1034
|
+
".dsh-ct-body .md-table-wide{--dsh-table-spare:max(0px, calc((100cqw - var(--dsh-chat-content-width)) / 2));--dsh-table-lead:calc(var(--dsh-table-spare) + min(var(--dsh-chat-content-width), 100cqw) - 100%);box-sizing:border-box;width:calc(100% + var(--dsh-table-lead) + var(--dsh-table-spare));max-width:none;margin-left:calc(-1 * var(--dsh-table-lead));padding-left:var(--dsh-table-lead)}",
|
|
1035
|
+
".dsh-ct-body .md-table-wide>table{z-index:1;position:relative}",
|
|
1036
|
+
// ---- 已停止标记(宿主 .hWmORq_stopped 等价) ----
|
|
1037
|
+
".dsh-ct-stopped{border-radius:var(--dsw-radius-sm);background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-tertiary);align-self:flex-start;padding:0 6px;font-size:11px;line-height:18px}",
|
|
1038
|
+
// ---- 正文块:可翻译时左缘留出 12px 缩进(缩进与线无关——译文出现前后
|
|
1039
|
+
// 正文位置不变,切换总开关时整列也不横移),并成为热区的定位父级 ----
|
|
1040
|
+
".dsh-ct-prose{position:relative}",
|
|
1041
|
+
".dsh-ct-prose-indent{padding-left:12px}",
|
|
1042
|
+
// ---- 线型:粗细与色相只表示状态。蓝粗线=读译文,灰细线=读原文备着译文
|
|
1043
|
+
// (0.5px 中性 hairline),红实线=失败(只剩通道伤一种) ----
|
|
1044
|
+
".dsh-ct-prose-translated{border-left:1px solid color-mix(in srgb, var(--dsw-alias-state-business-primary) 65%, transparent)}",
|
|
1045
|
+
".dsh-ct-prose-original{border-left:0.5px solid var(--dsw-alias-border-l2)}",
|
|
1046
|
+
".dsh-ct-prose-failed{border-left:1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 65%, transparent)}",
|
|
1047
|
+
// ---- 在途:灰 1px 线脉动(缩进由 .dsh-ct-prose-indent 统一给,转蓝不跳字);
|
|
1048
|
+
// prefers-reduced-motion 降级为静态灰线 ----
|
|
1049
|
+
".dsh-ct-prose-inflight{border-left:1px solid var(--dsw-alias-border-l2);animation:1.4s ease-in-out infinite alternate dsh-ct-prose-inflight-pulse}",
|
|
1050
|
+
"@keyframes dsh-ct-prose-inflight-pulse{from{border-left-color:var(--dsw-alias-border-l2)}to{border-left-color:color-mix(in srgb, var(--dsw-alias-border-l2) 20%, transparent)}}",
|
|
1051
|
+
"@media (prefers-reduced-motion:reduce){.dsh-ct-prose-inflight{animation:none}}",
|
|
1052
|
+
// ---- 左缘热区:随块高铺满的一条透明窄带。绝对定位的偏移以 padding box 为
|
|
1053
|
+
// 基准、而线画在 border 上,所以线的左缘在 -1px:`left:-5px` 给出线外侧
|
|
1054
|
+
// 4px,`width:17px` 让右缘落在正文第一个字上(12px 缩进),不吃正文。
|
|
1055
|
+
// 切换与补跑只由它承载;正文整块不再是点击目标,选中/复制不再误触。
|
|
1056
|
+
// z-index 压过宽表格的透明出血层(那条规则给 table 挂了 z-index:1);
|
|
1057
|
+
// 出血区是透明 padding,正文格子的起点仍在缩进之外 ----
|
|
1058
|
+
".dsh-ct-hotspot{position:absolute;left:-5px;top:0;bottom:0;width:17px;margin:0;padding:0;border:0;background:0 0;cursor:pointer;z-index:2;-webkit-appearance:none;appearance:none}",
|
|
1059
|
+
// 在途只剩「已在跑」这一答复:热区保留(键盘焦点不掉),但光标不承诺能点。
|
|
1060
|
+
".dsh-ct-hotspot[data-idle]{cursor:default}",
|
|
1061
|
+
// 热区没有自己的外形:它是透明的一层。focus-visible 只对键盘聚焦成立(指针
|
|
1062
|
+
// 点出来的焦点不匹配),下面的 :has 规则把线加亮——粗细始终只表示译文/原文。
|
|
1063
|
+
".dsh-ct-hotspot:focus-visible{outline:none}",
|
|
1064
|
+
".dsh-ct-prose-translated:has(.dsh-ct-hotspot:focus-visible){border-left-color:var(--dsw-alias-state-business-primary)}",
|
|
1065
|
+
".dsh-ct-prose-original:has(.dsh-ct-hotspot:focus-visible){border-left-color:var(--dsw-alias-label-caption)}",
|
|
1066
|
+
".dsh-ct-prose-failed:has(.dsh-ct-hotspot:focus-visible){border-left-color:var(--dsw-alias-state-error-primary)}",
|
|
1067
|
+
// 在途线本就在动;CSS 动画压过普通声明,聚焦的加亮必须同时停掉动画才看得见
|
|
1068
|
+
// (停掉脉动本身就是一次可见的变化,线也从闪烁的灰换成静止的亮灰)。
|
|
1069
|
+
".dsh-ct-prose-inflight:has(.dsh-ct-hotspot:focus-visible){animation:none;border-left-color:var(--dsw-alias-label-caption)}",
|
|
1070
|
+
// ---- 失败块的补跑指引:小 ↻(官方图标)常驻在红线旁——失败不会被悬停
|
|
1071
|
+
// 发现,救活入口必须一直看得见;纯装饰(pointer-events:none),点击
|
|
1072
|
+
// 归热区,败因由 Tooltip 挂在热区上 ----
|
|
1073
|
+
".dsh-ct-retry{position:absolute;left:2px;top:6px;display:flex;color:var(--dsw-alias-state-error-primary);pointer-events:none;z-index:3}",
|
|
1074
|
+
// ---- ReasoningRow(宿主 lcKema_* 等价,前缀换成 dsh-ct-think) ----
|
|
1075
|
+
".dsh-ct-think-root{flex-direction:column;display:flex}",
|
|
1076
|
+
".dsh-ct-think-root:not([data-expanded]){contain:size layout;height:calc(24px + var(--dsh-content-font-delta,0px))}",
|
|
1077
|
+
".dsh-ct-think-row{position:relative;overflow:hidden}",
|
|
1078
|
+
".dsh-ct-think-root[data-expanded] [data-open] [data-disclosure-row]{z-index:1;background:var(--dsw-alias-bg-base);position:sticky;top:0}",
|
|
1079
|
+
'.dsh-ct-think-root[data-state=running] .dsh-ct-think-row:after{content:"";inset-block:0;background:linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%, transparent 100%);pointer-events:none;width:300px;animation:2.6s ease-out infinite dsh-ct-reasoning-sweep;position:absolute;left:0}',
|
|
1080
|
+
"@keyframes dsh-ct-reasoning-sweep{0%{left:-300px}90%,to{left:100%}}",
|
|
1081
|
+
"@media (prefers-reduced-motion:reduce){.dsh-ct-think-root[data-state=running] .dsh-ct-think-row:after{animation:none}}",
|
|
1082
|
+
".dsh-ct-think-leading{flex-shrink:0}",
|
|
1083
|
+
".dsh-ct-think-chevron{color:var(--dsw-alias-label-secondary)}",
|
|
1084
|
+
".dsh-ct-think-title{font-weight:400}",
|
|
1085
|
+
".dsh-ct-think-separator{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}",
|
|
1086
|
+
".dsh-ct-think-summary{min-width:0;color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(20px + var(--dsh-content-font-delta-secondary,0px));white-space:nowrap;flex:auto;overflow:hidden}",
|
|
1087
|
+
".dsh-ct-think-summary-text{text-overflow:ellipsis;display:block;overflow:hidden}",
|
|
1088
|
+
".dsh-ct-think-summary[data-streaming]{mask-image:linear-gradient(90deg,currentcolor calc(100% - 48px),transparent)}",
|
|
1089
|
+
".dsh-ct-think-summary[data-streaming] .dsh-ct-think-summary-text{text-overflow:clip;overflow:visible}",
|
|
1090
|
+
".dsh-ct-think-root:not([data-preview]) .dsh-ct-think-separator,.dsh-ct-think-root:not([data-preview]) .dsh-ct-think-summary{display:none}",
|
|
1091
|
+
".dsh-ct-think-body{padding:4px 0 4px calc(22px + var(--dsh-content-font-delta,0px));min-width:0}",
|
|
1092
|
+
// ---- 无障碍:屏幕阅读器专用文本 ----
|
|
1093
|
+
".dsh-ct-visually-hidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}"
|
|
1094
|
+
].join("");
|
|
1095
|
+
var PROSE_BY_MARK = {
|
|
1096
|
+
translated: { className: "dsh-ct-prose-translated", action: "toggle" },
|
|
1097
|
+
"original-view": { className: "dsh-ct-prose-original", action: "toggle" },
|
|
1098
|
+
failed: { className: "dsh-ct-prose-failed", action: "retry" },
|
|
1099
|
+
// 在途有线、有热区,但 action 为 null:按下去是空操作(脉动已是「已在跑」
|
|
1100
|
+
// 的答复),热区保留只是为了让键盘焦点不掉回页面。
|
|
1101
|
+
inflight: { className: "dsh-ct-prose-inflight", action: null }
|
|
1102
|
+
};
|
|
1103
|
+
var PROSE_BASE_CLASS = "dsh-ct-prose";
|
|
1104
|
+
function prosePresentation(mark) {
|
|
1105
|
+
return mark === null ? { className: null, action: null } : PROSE_BY_MARK[mark];
|
|
1106
|
+
}
|
|
1107
|
+
function proseClassNames(mark) {
|
|
1108
|
+
const line = prosePresentation(mark).className;
|
|
1109
|
+
return line === null ? void 0 : `${PROSE_BASE_CLASS} ${line}`;
|
|
1110
|
+
}
|
|
1111
|
+
function proseIndentClassName(canTranslate) {
|
|
1112
|
+
return canTranslate ? "dsh-ct-prose-indent" : void 0;
|
|
1113
|
+
}
|
|
1114
|
+
var HOTSPOT_CLASS = "dsh-ct-hotspot";
|
|
1115
|
+
var HOTSPOT_IDLE_ATTR = "data-idle";
|
|
1116
|
+
function ensureAssistantStyles() {
|
|
1117
|
+
if (typeof document === "undefined") return;
|
|
1118
|
+
const selector = `style[data-plugin-css=${JSON.stringify(STYLE_TAG_ID)}]`;
|
|
1119
|
+
if (document.querySelector(selector) !== null) return;
|
|
1120
|
+
const tag = document.createElement("style");
|
|
1121
|
+
tag.dataset.plugin = "@lynn123411/dsh-chat-translate";
|
|
1122
|
+
tag.dataset.pluginCss = STYLE_TAG_ID;
|
|
1123
|
+
tag.textContent = ASSISTANT_CSS;
|
|
1124
|
+
document.head.appendChild(tag);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
909
1127
|
// src/server/pipeline/blocks.ts
|
|
910
1128
|
var REPLY_MAX_INPUT_TOKENS = 4096;
|
|
911
1129
|
var CJK_CHAR = /[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\u3000-\u303f\uff00-\uffef]/;
|
|
@@ -919,7 +1137,6 @@ function estimateTokens(text) {
|
|
|
919
1137
|
|
|
920
1138
|
// src/client/chat/translate-store.ts
|
|
921
1139
|
var MAX_TRANSLATED_ROWS = 200;
|
|
922
|
-
var MAX_ROW_ATTEMPTS = 3;
|
|
923
1140
|
var REQUEST_TOKEN_BUDGET = REPLY_MAX_INPUT_TOKENS;
|
|
924
1141
|
function chunkTexts(texts) {
|
|
925
1142
|
const batches = [];
|
|
@@ -966,32 +1183,33 @@ var ChatTranslateStore = class {
|
|
|
966
1183
|
}
|
|
967
1184
|
/**
|
|
968
1185
|
* 确保该行按当前文本被翻译。
|
|
969
|
-
* @
|
|
1186
|
+
* @param manual - 用户点击失败块的左缘热区发起的补跑:已落定的同代行只有这条路会重发。
|
|
1187
|
+
* @returns 是否发出了新的请求。
|
|
970
1188
|
*/
|
|
971
|
-
ensure(rowKey, texts) {
|
|
1189
|
+
ensure(rowKey, texts, manual = false) {
|
|
972
1190
|
const current = this.rows.get(rowKey);
|
|
973
|
-
let
|
|
1191
|
+
let seed = null;
|
|
974
1192
|
if (current !== void 0 && sameTexts(current.texts, texts)) {
|
|
975
|
-
if (current.status
|
|
976
|
-
if (
|
|
977
|
-
|
|
1193
|
+
if (current.status === "pending") return false;
|
|
1194
|
+
if (!manual) return false;
|
|
1195
|
+
seed = current.outcomes.map((outcome) => outcome !== null && outcome.ok ? { ...outcome } : null);
|
|
978
1196
|
}
|
|
979
1197
|
const snapshot = [...texts];
|
|
1198
|
+
const seedOutcomes = seed ?? snapshot.map(() => null);
|
|
980
1199
|
const next = {
|
|
981
1200
|
status: "pending",
|
|
982
1201
|
texts: snapshot,
|
|
983
|
-
outcomes:
|
|
984
|
-
attempts
|
|
1202
|
+
outcomes: seedOutcomes
|
|
985
1203
|
};
|
|
986
1204
|
this.rows.delete(rowKey);
|
|
987
1205
|
this.rows.set(rowKey, next);
|
|
988
1206
|
this.prune();
|
|
989
1207
|
this.bump();
|
|
990
|
-
if (snapshot.length > 0) void this.run(rowKey, snapshot);
|
|
1208
|
+
if (snapshot.length > 0) void this.run(rowKey, snapshot, seedOutcomes);
|
|
991
1209
|
return true;
|
|
992
1210
|
}
|
|
993
|
-
async run(rowKey, texts) {
|
|
994
|
-
const outcomes =
|
|
1211
|
+
async run(rowKey, texts, initial) {
|
|
1212
|
+
const outcomes = initial.map((o) => o);
|
|
995
1213
|
let offset = 0;
|
|
996
1214
|
for (const batch of chunkTexts(texts)) {
|
|
997
1215
|
let results = [];
|
|
@@ -1005,7 +1223,12 @@ var ChatTranslateStore = class {
|
|
|
1005
1223
|
if (result !== void 0 && result.ok && result.translated && result.translated.trim()) {
|
|
1006
1224
|
outcomes[offset + i] = { translated: result.translated, ok: true };
|
|
1007
1225
|
} else {
|
|
1008
|
-
|
|
1226
|
+
const detail = typeof result?.detail === "string" && result.detail !== "" ? result.detail : void 0;
|
|
1227
|
+
outcomes[offset + i] = {
|
|
1228
|
+
translated: batch[i] ?? texts[offset + i] ?? "",
|
|
1229
|
+
ok: false,
|
|
1230
|
+
...detail === void 0 ? {} : { detail }
|
|
1231
|
+
};
|
|
1009
1232
|
}
|
|
1010
1233
|
}
|
|
1011
1234
|
offset += batch.length;
|
|
@@ -1014,19 +1237,16 @@ var ChatTranslateStore = class {
|
|
|
1014
1237
|
this.rows.set(rowKey, {
|
|
1015
1238
|
status: "pending",
|
|
1016
1239
|
texts: live.texts,
|
|
1017
|
-
outcomes: [...outcomes]
|
|
1018
|
-
attempts: live.attempts
|
|
1240
|
+
outcomes: [...outcomes]
|
|
1019
1241
|
});
|
|
1020
1242
|
this.bump();
|
|
1021
1243
|
}
|
|
1022
1244
|
const state = this.rows.get(rowKey);
|
|
1023
1245
|
if (state?.status === "pending" && sameTexts(state.texts, texts)) {
|
|
1024
|
-
const anyFailed = outcomes.some((outcome) => outcome === null || !outcome.ok);
|
|
1025
1246
|
this.rows.set(rowKey, {
|
|
1026
|
-
status:
|
|
1247
|
+
status: "settled",
|
|
1027
1248
|
texts: state.texts,
|
|
1028
|
-
outcomes
|
|
1029
|
-
attempts: state.attempts
|
|
1249
|
+
outcomes: [...outcomes]
|
|
1030
1250
|
});
|
|
1031
1251
|
this.bump();
|
|
1032
1252
|
}
|
|
@@ -1061,133 +1281,6 @@ function createTranslateStore(fetcher) {
|
|
|
1061
1281
|
}
|
|
1062
1282
|
var chatTranslate = createTranslateStore(requestTranslateReply);
|
|
1063
1283
|
|
|
1064
|
-
// src/client/chat/row-plan.ts
|
|
1065
|
-
function planAssistantRow(input) {
|
|
1066
|
-
const { blocks, streaming, interrupted, groupPart, canTranslate, outcomes } = input;
|
|
1067
|
-
const hasVisible = streaming || interrupted || blocks.some((block) => block.kind !== "tool-call");
|
|
1068
|
-
if (!hasVisible) return { entries: null, texts: [] };
|
|
1069
|
-
const texts = [];
|
|
1070
|
-
if (groupPart !== "reasoning") {
|
|
1071
|
-
for (const block of blocks) {
|
|
1072
|
-
if (block.kind === "text") texts.push(block.text ?? "");
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
const entries = [];
|
|
1076
|
-
let textIndex = 0;
|
|
1077
|
-
let imageRun = null;
|
|
1078
|
-
const flushImages = () => {
|
|
1079
|
-
if (imageRun !== null) {
|
|
1080
|
-
entries.push({ type: "images", key: entries.length, attachments: imageRun });
|
|
1081
|
-
imageRun = null;
|
|
1082
|
-
}
|
|
1083
|
-
};
|
|
1084
|
-
for (let i = 0; i < blocks.length; i++) {
|
|
1085
|
-
const block = blocks[i];
|
|
1086
|
-
if (block === void 0) continue;
|
|
1087
|
-
if (groupPart === "reasoning" && block.kind !== "reasoning") continue;
|
|
1088
|
-
if (groupPart === "response" && block.kind === "reasoning") continue;
|
|
1089
|
-
switch (block.kind) {
|
|
1090
|
-
case "text": {
|
|
1091
|
-
flushImages();
|
|
1092
|
-
const index = textIndex;
|
|
1093
|
-
textIndex += 1;
|
|
1094
|
-
const outcome = canTranslate && !streaming && outcomes !== void 0 ? outcomes[index] : null;
|
|
1095
|
-
entries.push({
|
|
1096
|
-
type: "prose",
|
|
1097
|
-
key: entries.length,
|
|
1098
|
-
text: block.text ?? "",
|
|
1099
|
-
translated: outcome !== null && outcome !== void 0 && outcome.ok ? outcome.translated : null
|
|
1100
|
-
});
|
|
1101
|
-
break;
|
|
1102
|
-
}
|
|
1103
|
-
case "reasoning":
|
|
1104
|
-
flushImages();
|
|
1105
|
-
entries.push({ type: "reasoning", key: entries.length, blockIndex: i, text: block.text ?? "" });
|
|
1106
|
-
break;
|
|
1107
|
-
case "image":
|
|
1108
|
-
if (imageRun === null) imageRun = [];
|
|
1109
|
-
imageRun.push(block.attachment);
|
|
1110
|
-
break;
|
|
1111
|
-
case "tool-call":
|
|
1112
|
-
flushImages();
|
|
1113
|
-
break;
|
|
1114
|
-
default:
|
|
1115
|
-
flushImages();
|
|
1116
|
-
entries.push({ type: "unknown", key: entries.length, block: block.block ?? null });
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
flushImages();
|
|
1120
|
-
const showStopped = interrupted && (groupPart === void 0 || groupPart === "response" || !blocks.some((block) => block.kind !== "reasoning" && block.kind !== "tool-call"));
|
|
1121
|
-
if (showStopped) entries.push({ type: "stopped", key: entries.length });
|
|
1122
|
-
return { entries, texts };
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
// src/client/chat/click-guard.ts
|
|
1126
|
-
function isBareBlockClick(event, view) {
|
|
1127
|
-
const target = event.target;
|
|
1128
|
-
const interactive = typeof target?.closest === "function" ? target.closest('a,button,[role="button"]') : null;
|
|
1129
|
-
if (interactive !== null && interactive !== void 0 && interactive !== event.currentTarget) return false;
|
|
1130
|
-
const selection = typeof view?.getSelection === "function" ? view.getSelection() : null;
|
|
1131
|
-
if (selection !== null && selection !== void 0) {
|
|
1132
|
-
if (selection.isCollapsed === false && String(selection.toString?.() ?? "") !== "") return false;
|
|
1133
|
-
}
|
|
1134
|
-
return true;
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
// src/client/chat/styles.ts
|
|
1138
|
-
var STYLE_TAG_ID = "@lynn123411/dsh-chat-translate/chat-assistant.css";
|
|
1139
|
-
var ASSISTANT_CSS = [
|
|
1140
|
-
// ---- 行骨架(宿主 AssistantMarkdown.root/body 等价) ----
|
|
1141
|
-
".dsh-ct-root{display:flex;flex-direction:column;font-size:var(--dsh-content-font-size,14px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-primary)}",
|
|
1142
|
-
".dsh-ct-body{display:flex;flex-direction:column;gap:16px}",
|
|
1143
|
-
".dsh-ct-body>[data-turn-process-inline][hidden]{margin-bottom:-16px}",
|
|
1144
|
-
// 宽表格溢出内容列(宿主等价规则,类名 md-table-wide 由 MarkdownText 自己产出)
|
|
1145
|
-
".dsh-ct-body .md-table-wide{--dsh-table-spare:max(0px, calc((100cqw - var(--dsh-chat-content-width)) / 2));--dsh-table-lead:calc(var(--dsh-table-spare) + min(var(--dsh-chat-content-width), 100cqw) - 100%);box-sizing:border-box;width:calc(100% + var(--dsh-table-lead) + var(--dsh-table-spare));max-width:none;margin-left:calc(-1 * var(--dsh-table-lead));padding-left:var(--dsh-table-lead)}",
|
|
1146
|
-
".dsh-ct-body .md-table-wide>table{z-index:1;position:relative}",
|
|
1147
|
-
// ---- 已停止标记(宿主 .hWmORq_stopped 等价) ----
|
|
1148
|
-
".dsh-ct-stopped{border-radius:var(--dsw-radius-sm);background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-tertiary);align-self:flex-start;padding:0 6px;font-size:11px;line-height:18px}",
|
|
1149
|
-
// ---- 「已翻译」交互与标记:可点态常驻(含左缘内缩);区分走色相+粗细
|
|
1150
|
-
// (0.5px 虚线会被抗锯齿糊成实线)——蓝粗线=读译文,灰细线=读原文备着译文 ----
|
|
1151
|
-
".dsh-ct-prose-clickable{padding-left:12px;cursor:pointer}",
|
|
1152
|
-
".dsh-ct-prose-clickable:focus-visible{outline:1.5px solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px;border-radius:var(--dsw-radius-sm)}",
|
|
1153
|
-
".dsh-ct-prose-translated{border-left:1px solid color-mix(in srgb, var(--dsw-alias-state-business-primary) 65%, transparent)}",
|
|
1154
|
-
".dsh-ct-prose-original{border-left:0.5px solid var(--dsw-alias-border-l2)}",
|
|
1155
|
-
// ---- ReasoningRow(宿主 lcKema_* 等价,前缀换成 dsh-ct-think) ----
|
|
1156
|
-
".dsh-ct-think-root{flex-direction:column;display:flex}",
|
|
1157
|
-
".dsh-ct-think-root:not([data-expanded]){contain:size layout;height:calc(24px + var(--dsh-content-font-delta,0px))}",
|
|
1158
|
-
".dsh-ct-think-row{position:relative;overflow:hidden}",
|
|
1159
|
-
".dsh-ct-think-root[data-expanded] [data-open] [data-disclosure-row]{z-index:1;background:var(--dsw-alias-bg-base);position:sticky;top:0}",
|
|
1160
|
-
'.dsh-ct-think-root[data-state=running] .dsh-ct-think-row:after{content:"";inset-block:0;background:linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%, transparent 100%);pointer-events:none;width:300px;animation:2.6s ease-out infinite dsh-ct-reasoning-sweep;position:absolute;left:0}',
|
|
1161
|
-
"@keyframes dsh-ct-reasoning-sweep{0%{left:-300px}90%,to{left:100%}}",
|
|
1162
|
-
"@media (prefers-reduced-motion:reduce){.dsh-ct-think-root[data-state=running] .dsh-ct-think-row:after{animation:none}}",
|
|
1163
|
-
".dsh-ct-think-leading{flex-shrink:0}",
|
|
1164
|
-
".dsh-ct-think-chevron{color:var(--dsw-alias-label-secondary)}",
|
|
1165
|
-
".dsh-ct-think-title{font-weight:400}",
|
|
1166
|
-
".dsh-ct-think-separator{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}",
|
|
1167
|
-
".dsh-ct-think-summary{min-width:0;color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(20px + var(--dsh-content-font-delta-secondary,0px));white-space:nowrap;flex:auto;overflow:hidden}",
|
|
1168
|
-
".dsh-ct-think-summary-text{text-overflow:ellipsis;display:block;overflow:hidden}",
|
|
1169
|
-
".dsh-ct-think-summary[data-streaming]{mask-image:linear-gradient(90deg,currentcolor calc(100% - 48px),transparent)}",
|
|
1170
|
-
".dsh-ct-think-summary[data-streaming] .dsh-ct-think-summary-text{text-overflow:clip;overflow:visible}",
|
|
1171
|
-
".dsh-ct-think-root:not([data-preview]) .dsh-ct-think-separator,.dsh-ct-think-root:not([data-preview]) .dsh-ct-think-summary{display:none}",
|
|
1172
|
-
".dsh-ct-think-body{padding:4px 0 4px calc(22px + var(--dsh-content-font-delta,0px));min-width:0}",
|
|
1173
|
-
// ---- 无障碍:屏幕阅读器专用文本 ----
|
|
1174
|
-
".dsh-ct-visually-hidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}"
|
|
1175
|
-
].join("");
|
|
1176
|
-
function proseClassNames(processed, showOriginal) {
|
|
1177
|
-
if (!processed) return void 0;
|
|
1178
|
-
return showOriginal ? "dsh-ct-prose-clickable dsh-ct-prose-original" : "dsh-ct-prose-clickable dsh-ct-prose-translated";
|
|
1179
|
-
}
|
|
1180
|
-
function ensureAssistantStyles() {
|
|
1181
|
-
if (typeof document === "undefined") return;
|
|
1182
|
-
const selector = `style[data-plugin-css=${JSON.stringify(STYLE_TAG_ID)}]`;
|
|
1183
|
-
if (document.querySelector(selector) !== null) return;
|
|
1184
|
-
const tag = document.createElement("style");
|
|
1185
|
-
tag.dataset.plugin = "@lynn123411/dsh-chat-translate";
|
|
1186
|
-
tag.dataset.pluginCss = STYLE_TAG_ID;
|
|
1187
|
-
tag.textContent = ASSISTANT_CSS;
|
|
1188
|
-
document.head.appendChild(tag);
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
1284
|
// src/client/chat/assistant-step.tsx
|
|
1192
1285
|
var thinkIcon = () => React.createElement(import_dsh_client_ui_primitives3.IconThinkOutlineRegular, { size: 14 });
|
|
1193
1286
|
function markdownLabels(t) {
|
|
@@ -1352,44 +1445,69 @@ var ReasoningRow = (0, import_react4.memo)(function ReasoningRow2({
|
|
|
1352
1445
|
)
|
|
1353
1446
|
);
|
|
1354
1447
|
});
|
|
1355
|
-
function blockClick(event, onToggle) {
|
|
1356
|
-
const guardEvent = {
|
|
1357
|
-
target: event.target,
|
|
1358
|
-
currentTarget: event.currentTarget
|
|
1359
|
-
};
|
|
1360
|
-
if (isBareBlockClick(guardEvent, typeof window === "undefined" ? void 0 : window)) onToggle();
|
|
1361
|
-
}
|
|
1362
1448
|
function ProseBlock({
|
|
1363
1449
|
text,
|
|
1364
1450
|
translated,
|
|
1365
|
-
|
|
1451
|
+
mark,
|
|
1452
|
+
fail,
|
|
1366
1453
|
onToggle,
|
|
1454
|
+
onRetry,
|
|
1455
|
+
canTranslate,
|
|
1367
1456
|
streaming,
|
|
1368
1457
|
labels,
|
|
1369
1458
|
mentions,
|
|
1370
1459
|
pathImages
|
|
1371
1460
|
}) {
|
|
1372
|
-
const
|
|
1373
|
-
const
|
|
1374
|
-
const
|
|
1461
|
+
const { action } = prosePresentation(mark);
|
|
1462
|
+
const clickable = action !== null;
|
|
1463
|
+
const retrying = action === "retry";
|
|
1464
|
+
const handler = retrying ? onRetry : onToggle;
|
|
1465
|
+
const source = mark === "translated" ? translated : text;
|
|
1466
|
+
const copy = rowCopy();
|
|
1467
|
+
const className = [proseClassNames(mark) ?? PROSE_BASE_CLASS, proseIndentClassName(canTranslate)].filter((name2) => name2 !== void 0).join(" ");
|
|
1468
|
+
const hotspot = mark === null ? null : React.createElement("button", {
|
|
1469
|
+
type: "button",
|
|
1470
|
+
className: HOTSPOT_CLASS,
|
|
1471
|
+
// 热区是覆盖层,读屏只应听到它的动作,正文仍由 MarkdownText 读。
|
|
1472
|
+
"aria-label": mark === "inflight" ? copy.inflightAria : retrying ? copy.retryAria : mark === "original-view" ? copy.toggleToTranslatedAria : copy.toggleToOriginalAria,
|
|
1473
|
+
...mark === "inflight" ? { [HOTSPOT_IDLE_ATTR]: "true" } : {},
|
|
1474
|
+
tabIndex: 0,
|
|
1475
|
+
// button 自己会按 Enter/Space 合成 click,若不接管就会与这段键盘处理
|
|
1476
|
+
// 叠加成两次动作:这里两个键都走 keydown 并 preventDefault,合成被掐掉。
|
|
1477
|
+
onKeyDown: clickable ? (event) => {
|
|
1478
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
1479
|
+
event.preventDefault();
|
|
1480
|
+
handler();
|
|
1481
|
+
} : void 0,
|
|
1482
|
+
onClick: clickable ? (event) => {
|
|
1483
|
+
if (isCleanHotspotClick(
|
|
1484
|
+
{ target: event.target, currentTarget: event.currentTarget },
|
|
1485
|
+
typeof window === "undefined" ? void 0 : window
|
|
1486
|
+
)) {
|
|
1487
|
+
handler();
|
|
1488
|
+
}
|
|
1489
|
+
} : void 0
|
|
1490
|
+
});
|
|
1491
|
+
const retryIcon = retrying ? React.createElement(
|
|
1492
|
+
"span",
|
|
1493
|
+
{ className: "dsh-ct-retry", "aria-hidden": true },
|
|
1494
|
+
React.createElement(import_dsh_client_ui_primitives3.IconRefreshOutlineRegular, { size: 10 })
|
|
1495
|
+
) : null;
|
|
1496
|
+
const anchor = hotspot === null ? null : React.createElement(import_dsh_client_ui_primitives3.Tooltip, {
|
|
1497
|
+
label: () => copy.failTitle(fail),
|
|
1498
|
+
side: "top",
|
|
1499
|
+
portal: true,
|
|
1500
|
+
disabled: !retrying,
|
|
1501
|
+
children: hotspot
|
|
1502
|
+
});
|
|
1375
1503
|
return React.createElement(
|
|
1376
1504
|
"div",
|
|
1377
1505
|
{
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
"data-translated": showTranslation ? "true" : void 0,
|
|
1381
|
-
// 容器内含链接等交互内容,不套 role=button(非法嵌套);可聚焦 + 键盘
|
|
1382
|
-
// Enter/Space 即切换,满足官方「键盘可达」门。
|
|
1383
|
-
tabIndex: processed ? 0 : void 0,
|
|
1384
|
-
onClick: processed ? (event) => blockClick(event, onToggle) : void 0,
|
|
1385
|
-
onKeyDown: processed ? (event) => {
|
|
1386
|
-
if (event.target !== event.currentTarget) return;
|
|
1387
|
-
if (event.key === "Enter" || event.key === " ") {
|
|
1388
|
-
event.preventDefault();
|
|
1389
|
-
onToggle();
|
|
1390
|
-
}
|
|
1391
|
-
} : void 0
|
|
1506
|
+
className,
|
|
1507
|
+
"data-translated": mark === "translated" ? "true" : void 0
|
|
1392
1508
|
},
|
|
1509
|
+
retryIcon,
|
|
1510
|
+
anchor,
|
|
1393
1511
|
React.createElement(import_dsh_client_ui_primitives3.MarkdownText, {
|
|
1394
1512
|
text: source,
|
|
1395
1513
|
streaming,
|
|
@@ -1441,7 +1559,9 @@ var AssistantStepView = (0, import_react4.memo)(function AssistantStepView2(prop
|
|
|
1441
1559
|
if (streaming || !canTranslate || !inView || plan.entries === null || plan.texts.length === 0) return;
|
|
1442
1560
|
chatTranslate.ensure(rowKey, plan.texts);
|
|
1443
1561
|
}, [streaming, canTranslate, inView, rowKey, plan]);
|
|
1444
|
-
const
|
|
1562
|
+
const [originalKeys, setOriginalKeys] = (0, import_react4.useState)(() => /* @__PURE__ */ new Set());
|
|
1563
|
+
const registered = chatTranslate.getState(rowKey);
|
|
1564
|
+
const rowState = registered !== void 0 && sameTexts(registered.texts, plan.texts) ? registered : void 0;
|
|
1445
1565
|
const displayed = (0, import_react4.useMemo)(
|
|
1446
1566
|
() => planAssistantRow({
|
|
1447
1567
|
blocks,
|
|
@@ -1449,15 +1569,16 @@ var AssistantStepView = (0, import_react4.memo)(function AssistantStepView2(prop
|
|
|
1449
1569
|
interrupted,
|
|
1450
1570
|
groupPart,
|
|
1451
1571
|
canTranslate,
|
|
1452
|
-
outcomes: rowState?.outcomes
|
|
1572
|
+
outcomes: rowState?.outcomes,
|
|
1573
|
+
rowStatus: rowState?.status,
|
|
1574
|
+
originalKeys
|
|
1453
1575
|
}),
|
|
1454
|
-
[blocks, streaming, interrupted, groupPart, canTranslate, rowState]
|
|
1576
|
+
[blocks, streaming, interrupted, groupPart, canTranslate, rowState, originalKeys]
|
|
1455
1577
|
);
|
|
1456
1578
|
const reasoningHidden = turnProcess !== void 0 && turnProcess.foldable && turnProcess.spec.answerStep === data.step && turnProcess.spec.inlineReasoning && !turnProcess.open;
|
|
1457
1579
|
const revealProcess = (0, import_react4.useCallback)(() => {
|
|
1458
1580
|
turnProcess?.setOpen(true);
|
|
1459
1581
|
}, [turnProcess]);
|
|
1460
|
-
const [originalKeys, setOriginalKeys] = (0, import_react4.useState)(() => /* @__PURE__ */ new Set());
|
|
1461
1582
|
const toggleBlock = (0, import_react4.useCallback)((key) => {
|
|
1462
1583
|
setOriginalKeys((current) => {
|
|
1463
1584
|
const next = new Set(current);
|
|
@@ -1466,6 +1587,9 @@ var AssistantStepView = (0, import_react4.memo)(function AssistantStepView2(prop
|
|
|
1466
1587
|
return next;
|
|
1467
1588
|
});
|
|
1468
1589
|
}, []);
|
|
1590
|
+
const retryRow = (0, import_react4.useCallback)(() => {
|
|
1591
|
+
chatTranslate.ensure(rowKey, plan.texts, true);
|
|
1592
|
+
}, [rowKey, plan]);
|
|
1469
1593
|
if (displayed.entries === null) return null;
|
|
1470
1594
|
const last = blocks.length - 1;
|
|
1471
1595
|
const rendered = [];
|
|
@@ -1477,8 +1601,11 @@ var AssistantStepView = (0, import_react4.memo)(function AssistantStepView2(prop
|
|
|
1477
1601
|
key: entry.key,
|
|
1478
1602
|
text: entry.text,
|
|
1479
1603
|
translated: entry.translated,
|
|
1480
|
-
|
|
1604
|
+
mark: entry.mark,
|
|
1605
|
+
fail: entry.fail,
|
|
1481
1606
|
onToggle: () => toggleBlock(entry.key),
|
|
1607
|
+
onRetry: retryRow,
|
|
1608
|
+
canTranslate,
|
|
1482
1609
|
streaming,
|
|
1483
1610
|
labels,
|
|
1484
1611
|
mentions,
|
|
@@ -1648,6 +1775,7 @@ function apply(ctx) {
|
|
|
1648
1775
|
}, "dsh-chat-translate: chat presentation source");
|
|
1649
1776
|
ctx.effect(() => setupSettingsUi(ctx), "dsh-chat-translate: settings section");
|
|
1650
1777
|
ctx.effect(() => setupComposerToggle(ctx), "dsh-chat-translate: composer dock toggle");
|
|
1778
|
+
bindRowCopy(ctx.locale);
|
|
1651
1779
|
}
|
|
1652
1780
|
return module.exports; } });
|
|
1653
1781
|
//# sourceMappingURL=client.js.map
|