@lynn123411/dsh-chat-translate 3.1.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/assets/icon.svg +6 -0
- package/docs//351/205/215/347/275/256.md +6 -5
- package/lib/client.js +181 -291
- package/lib/client.js.map +4 -4
- package/lib/index.js +1 -1
- package/lib/index.js.map +3 -3
- package/lib/types/client/chat/assistant-step.d.ts +6 -3
- package/lib/types/client/chat/click-guard.d.ts +10 -9
- package/lib/types/client/chat/row-plan.d.ts +3 -2
- package/lib/types/client/chat/styles.d.ts +41 -5
- package/lib/types/client/chat/translate-store.d.ts +1 -1
- package/lib/types/client/index.d.ts +11 -12
- package/lib/types/client/locales.d.ts +11 -5
- package/lib/types/client/settings/plugin-page.d.ts +18 -0
- package/lib/types/client/settings/ui.d.ts +12 -9
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +23 -14
- package/lib/types/client/reading-settings-page.d.ts +0 -92
package/lib/client.js
CHANGED
|
@@ -50,7 +50,7 @@ __export(index_exports, {
|
|
|
50
50
|
module.exports = __toCommonJS(index_exports);
|
|
51
51
|
|
|
52
52
|
// src/client/settings/ui.tsx
|
|
53
|
-
var
|
|
53
|
+
var import_react = require("react");
|
|
54
54
|
var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
55
55
|
|
|
56
56
|
// src/describe-error.ts
|
|
@@ -313,7 +313,7 @@ var SETTINGS_CSS = String.raw`
|
|
|
313
313
|
display: flex;
|
|
314
314
|
flex-direction: column;
|
|
315
315
|
gap: 16px;
|
|
316
|
-
|
|
316
|
+
width: 100%;
|
|
317
317
|
padding-bottom: 32px;
|
|
318
318
|
color: var(--dsw-alias-label-primary);
|
|
319
319
|
font: var(--dsw-font-s-14);
|
|
@@ -338,7 +338,14 @@ var SETTINGS_CSS = String.raw`
|
|
|
338
338
|
font: var(--dsw-font-s-strong-14);
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
+
/*
|
|
342
|
+
* Prose keeps a reading measure even though the card spans the host column: the
|
|
343
|
+
* master description is a 500-character paragraph, and at full width it would
|
|
344
|
+
* run to ~76 CJK characters per line. 46em tracks the element's own 12px font,
|
|
345
|
+
* so the measure survives a font-size change.
|
|
346
|
+
*/
|
|
341
347
|
.dsh-tidy-desc {
|
|
348
|
+
max-width: 46em;
|
|
342
349
|
color: var(--dsw-alias-label-secondary);
|
|
343
350
|
font: var(--dsw-font-xxs-12);
|
|
344
351
|
}
|
|
@@ -404,147 +411,12 @@ var SETTINGS_CSS = String.raw`
|
|
|
404
411
|
|
|
405
412
|
`;
|
|
406
413
|
|
|
407
|
-
// src/client/reading-settings-page.tsx
|
|
408
|
-
var import_react = require("react");
|
|
409
|
-
var READING_PAGE_ID = "reading";
|
|
410
|
-
var READING_PAGE_ORDER = 110;
|
|
411
|
-
var READING_ITEM_SLOT = "reading.settings.item";
|
|
412
|
-
function readLabel(label) {
|
|
413
|
-
if (typeof label === "function") return label();
|
|
414
|
-
return typeof label === "string" ? label : "";
|
|
415
|
-
}
|
|
416
|
-
var TABLIST_STYLE = {
|
|
417
|
-
display: "flex",
|
|
418
|
-
alignItems: "flex-end",
|
|
419
|
-
gap: "22px",
|
|
420
|
-
marginTop: "2px",
|
|
421
|
-
marginBottom: "16px"
|
|
422
|
-
};
|
|
423
|
-
var TAB_STYLE = {
|
|
424
|
-
appearance: "none",
|
|
425
|
-
background: "transparent",
|
|
426
|
-
// No border on ANY tab: the marker below is the active tab's own element, so
|
|
427
|
-
// an inactive tab has nothing that could render a line.
|
|
428
|
-
border: "none",
|
|
429
|
-
position: "relative",
|
|
430
|
-
padding: "7px 1px 11px",
|
|
431
|
-
cursor: "pointer",
|
|
432
|
-
font: "var(--dsw-font-xs-13)",
|
|
433
|
-
color: "var(--dsw-alias-label-tertiary)"
|
|
434
|
-
};
|
|
435
|
-
var TAB_ACTIVE_STYLE = {
|
|
436
|
-
...TAB_STYLE,
|
|
437
|
-
color: "var(--dsw-alias-label-primary)"
|
|
438
|
-
};
|
|
439
|
-
var TAB_MARKER_STYLE = {
|
|
440
|
-
position: "absolute",
|
|
441
|
-
left: 0,
|
|
442
|
-
right: 0,
|
|
443
|
-
bottom: 0,
|
|
444
|
-
height: "2px",
|
|
445
|
-
borderRadius: "2px 2px 0 0",
|
|
446
|
-
background: "var(--dsw-alias-label-primary)"
|
|
447
|
-
};
|
|
448
|
-
var PANEL_STYLE = { listStyle: "none", margin: 0, padding: 0 };
|
|
449
|
-
var PANEL_HIDDEN_STYLE = { ...PANEL_STYLE, display: "none" };
|
|
450
|
-
function createReadingTabs(ctx) {
|
|
451
|
-
const locale = ctx.get("locale");
|
|
452
|
-
let version = -1;
|
|
453
|
-
let revision = -1;
|
|
454
|
-
let tabs = [];
|
|
455
|
-
return {
|
|
456
|
-
getSnapshot: () => {
|
|
457
|
-
const nextVersion = ctx.slots.getVersion(READING_ITEM_SLOT);
|
|
458
|
-
const nextRevision = locale === void 0 ? 0 : locale.getSnapshot().revision;
|
|
459
|
-
if (nextVersion === version && nextRevision === revision) return tabs;
|
|
460
|
-
version = nextVersion;
|
|
461
|
-
revision = nextRevision;
|
|
462
|
-
tabs = ctx.slots.entries(READING_ITEM_SLOT).map((entry) => ({
|
|
463
|
-
id: entry.options.id ?? "",
|
|
464
|
-
order: entry.options.order ?? 0,
|
|
465
|
-
label: readLabel(entry.options.label)
|
|
466
|
-
})).sort((left, right) => left.order - right.order);
|
|
467
|
-
return tabs;
|
|
468
|
-
},
|
|
469
|
-
subscribe: (listener) => {
|
|
470
|
-
const offSlots = ctx.slots.subscribe(READING_ITEM_SLOT, listener);
|
|
471
|
-
const offLocale = locale?.subscribe(listener);
|
|
472
|
-
return () => {
|
|
473
|
-
offSlots();
|
|
474
|
-
offLocale?.();
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
function ReadingSettingsSection({ renderSlot, readingTabs }) {
|
|
480
|
-
const tabs = (0, import_react.useSyncExternalStore)(
|
|
481
|
-
readingTabs.subscribe,
|
|
482
|
-
readingTabs.getSnapshot,
|
|
483
|
-
readingTabs.getSnapshot
|
|
484
|
-
);
|
|
485
|
-
const [requested, setRequested] = (0, import_react.useState)(null);
|
|
486
|
-
const selected = requested !== null && tabs.some((tab) => tab.id === requested) ? requested : tabs[0]?.id ?? null;
|
|
487
|
-
if (selected === null) return null;
|
|
488
|
-
return (0, import_react.createElement)(
|
|
489
|
-
"div",
|
|
490
|
-
null,
|
|
491
|
-
(0, import_react.createElement)(
|
|
492
|
-
"div",
|
|
493
|
-
{ role: "tablist", style: TABLIST_STYLE },
|
|
494
|
-
tabs.map((tab) => (0, import_react.createElement)(
|
|
495
|
-
"button",
|
|
496
|
-
{
|
|
497
|
-
key: tab.id,
|
|
498
|
-
type: "button",
|
|
499
|
-
role: "tab",
|
|
500
|
-
"aria-selected": tab.id === selected,
|
|
501
|
-
style: tab.id === selected ? TAB_ACTIVE_STYLE : TAB_STYLE,
|
|
502
|
-
onClick: () => {
|
|
503
|
-
setRequested(tab.id);
|
|
504
|
-
}
|
|
505
|
-
},
|
|
506
|
-
tab.label,
|
|
507
|
-
tab.id === selected ? (0, import_react.createElement)("span", { style: TAB_MARKER_STYLE, "aria-hidden": true }) : null
|
|
508
|
-
))
|
|
509
|
-
),
|
|
510
|
-
tabs.map((tab) => (0, import_react.createElement)(
|
|
511
|
-
"ul",
|
|
512
|
-
{
|
|
513
|
-
key: tab.id,
|
|
514
|
-
role: "tabpanel",
|
|
515
|
-
hidden: tab.id !== selected,
|
|
516
|
-
style: tab.id === selected ? PANEL_STYLE : PANEL_HIDDEN_STYLE
|
|
517
|
-
},
|
|
518
|
-
renderSlot(READING_ITEM_SLOT, {}, { only: tab.id })
|
|
519
|
-
))
|
|
520
|
-
);
|
|
521
|
-
}
|
|
522
|
-
function readingPageClaimed(ctx) {
|
|
523
|
-
return ctx.slots.entries("settings.section").some((entry) => entry.options.id === READING_PAGE_ID);
|
|
524
|
-
}
|
|
525
|
-
function claimReadingSettingsPage(ctx, label, locale) {
|
|
526
|
-
if (readingPageClaimed(ctx)) return () => {
|
|
527
|
-
};
|
|
528
|
-
const readingTabs = createReadingTabs(ctx);
|
|
529
|
-
return ctx.slots.register({
|
|
530
|
-
name: "settings.section",
|
|
531
|
-
id: READING_PAGE_ID,
|
|
532
|
-
order: READING_PAGE_ORDER,
|
|
533
|
-
label,
|
|
534
|
-
locale,
|
|
535
|
-
inject: () => ({ readingTabs }),
|
|
536
|
-
children: { "reading.settings.item": { kind: "list", scope: "root" } }
|
|
537
|
-
}, ReadingSettingsSection);
|
|
538
|
-
}
|
|
539
|
-
|
|
540
414
|
// src/client/locales.ts
|
|
541
415
|
var NS = "settings.chatTranslate";
|
|
542
416
|
var zh = {
|
|
543
|
-
pageNav: "\u9605\u8BFB\u4F53\u9A8C",
|
|
544
|
-
title: "\u6B63\u6587\u7FFB\u8BD1",
|
|
545
417
|
masterTitle: "\u7FFB\u8BD1\u603B\u5F00\u5173",
|
|
546
418
|
enableTranslation: "\u542F\u7528\u7FFB\u8BD1",
|
|
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\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\
|
|
419
|
+
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",
|
|
548
420
|
aiTitle: "AI \u7FFB\u8BD1\uFF08OpenAI \u517C\u5BB9\u534F\u8BAE\uFF09",
|
|
549
421
|
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",
|
|
550
422
|
badgeConfigured: "\u5DF2\u914D\u7F6E",
|
|
@@ -574,14 +446,15 @@ var zh = {
|
|
|
574
446
|
dockOn: "\u6B63\u6587\u7FFB\u8BD1\u5DF2\u5F00\u542F\uFF0C\u70B9\u51FB\u5173\u95ED",
|
|
575
447
|
dockOff: "\u6B63\u6587\u7FFB\u8BD1\u5DF2\u5173\u95ED\uFF0C\u70B9\u51FB\u5F00\u542F",
|
|
576
448
|
failTitle: "\u7FFB\u8BD1\u5931\u8D25",
|
|
577
|
-
retryAria: "\u7FFB\u8BD1\u5931\u8D25\uFF0C\u6309 Enter \u91CD\u8BD5"
|
|
449
|
+
retryAria: "\u7FFB\u8BD1\u5931\u8D25\uFF0C\u70B9\u5DE6\u7F18\u90A3\u6761\u7A84\u5E26\u53EF\u6309 Enter \u91CD\u8BD5",
|
|
450
|
+
toggleToOriginalAria: "\u67E5\u770B\u539F\u6587",
|
|
451
|
+
toggleToTranslatedAria: "\u67E5\u770B\u8BD1\u6587",
|
|
452
|
+
inflightAria: "\u7FFB\u8BD1\u4E2D\uFF0C\u6B64\u5904\u6682\u65E0\u52A8\u4F5C"
|
|
578
453
|
};
|
|
579
454
|
var en = {
|
|
580
|
-
pageNav: "Reading",
|
|
581
|
-
title: "Reply translate",
|
|
582
455
|
masterTitle: "Translation master switch",
|
|
583
456
|
enableTranslation: "Enable translation",
|
|
584
|
-
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
|
|
457
|
+
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.",
|
|
585
458
|
aiTitle: "AI translation (OpenAI-compatible)",
|
|
586
459
|
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.",
|
|
587
460
|
badgeConfigured: "Configured",
|
|
@@ -611,7 +484,10 @@ var en = {
|
|
|
611
484
|
dockOn: "Translation is on; click to turn it off",
|
|
612
485
|
dockOff: "Translation is off; click to turn it on",
|
|
613
486
|
failTitle: "Translation failed",
|
|
614
|
-
retryAria: "Translation failed \u2014 press Enter to retry"
|
|
487
|
+
retryAria: "Translation failed \u2014 focus the slim strip at the left edge and press Enter to retry",
|
|
488
|
+
toggleToOriginalAria: "Show the original",
|
|
489
|
+
toggleToTranslatedAria: "Show the translation",
|
|
490
|
+
inflightAria: "Translating; Enter does nothing yet"
|
|
615
491
|
};
|
|
616
492
|
var rowT = (key) => zh[key] ?? key;
|
|
617
493
|
function bindRowCopy(locale) {
|
|
@@ -623,6 +499,9 @@ function bindRowCopy(locale) {
|
|
|
623
499
|
function rowCopy() {
|
|
624
500
|
return {
|
|
625
501
|
retryAria: rowT("retryAria"),
|
|
502
|
+
toggleToOriginalAria: rowT("toggleToOriginalAria"),
|
|
503
|
+
toggleToTranslatedAria: rowT("toggleToTranslatedAria"),
|
|
504
|
+
inflightAria: rowT("inflightAria"),
|
|
626
505
|
failTitle: (detail) => {
|
|
627
506
|
const label = rowT("failTitle");
|
|
628
507
|
return detail === void 0 || detail === "" || detail === null ? label : `${label} \u2014 ${detail}`;
|
|
@@ -630,6 +509,16 @@ function rowCopy() {
|
|
|
630
509
|
};
|
|
631
510
|
}
|
|
632
511
|
|
|
512
|
+
// src/client/settings/plugin-page.ts
|
|
513
|
+
var BUNDLE_NAME = "@lynn123411/dsh-chat-translate";
|
|
514
|
+
var BUNDLE_CONFIG_SLOT = "plugins.bundle.config";
|
|
515
|
+
var BUNDLE_CONFIG_KEY = BUNDLE_NAME;
|
|
516
|
+
function setupPluginPage(slots, register) {
|
|
517
|
+
if (!slots || typeof slots.inject !== "function") return () => {
|
|
518
|
+
};
|
|
519
|
+
return slots.inject(BUNDLE_CONFIG_SLOT, register);
|
|
520
|
+
}
|
|
521
|
+
|
|
633
522
|
// src/client/settings/ui.tsx
|
|
634
523
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
635
524
|
var stylesInjected = false;
|
|
@@ -650,14 +539,14 @@ function withSamples(text, samples) {
|
|
|
650
539
|
});
|
|
651
540
|
}
|
|
652
541
|
function TidySettingsPanel(props) {
|
|
653
|
-
ensureSettingsStyles();
|
|
654
542
|
const { t } = props;
|
|
655
|
-
|
|
656
|
-
const [
|
|
657
|
-
const [
|
|
658
|
-
const [
|
|
659
|
-
const [
|
|
660
|
-
(0,
|
|
543
|
+
ensureSettingsStyles();
|
|
544
|
+
const [state, setState] = (0, import_react.useState)(() => settingsStore.getState());
|
|
545
|
+
const [testing, setTesting] = (0, import_react.useState)(null);
|
|
546
|
+
const [apiKeyInput, setApiKeyInput] = (0, import_react.useState)("");
|
|
547
|
+
const [savingKey, setSavingKey] = (0, import_react.useState)(false);
|
|
548
|
+
const [keyMsg, setKeyMsg] = (0, import_react.useState)(null);
|
|
549
|
+
(0, import_react.useEffect)(() => {
|
|
661
550
|
return settingsStore.subscribe(() => {
|
|
662
551
|
setState(settingsStore.getState());
|
|
663
552
|
});
|
|
@@ -844,52 +733,34 @@ function setupSettingsUi(ctx) {
|
|
|
844
733
|
"dsh-chat-translate: locale dictionaries"
|
|
845
734
|
);
|
|
846
735
|
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
order: 30,
|
|
866
|
-
// 共享页按此标签渲染 tab
|
|
867
|
-
label: () => t("title"),
|
|
868
|
-
locale: NS
|
|
869
|
-
},
|
|
870
|
-
TidySettingsPanel
|
|
871
|
-
)
|
|
872
|
-
);
|
|
873
|
-
return () => {
|
|
874
|
-
offCard();
|
|
875
|
-
offPage();
|
|
876
|
-
};
|
|
877
|
-
}),
|
|
878
|
-
"dsh-chat-translate: shared reading page"
|
|
879
|
-
);
|
|
880
|
-
} catch (err) {
|
|
881
|
-
console.warn("[dsh-chat-translate] Failed to inject settings section:", err);
|
|
882
|
-
}
|
|
736
|
+
ctx.effect(
|
|
737
|
+
() => configForms.whileServed([SETTINGS_NAMESPACE], () => setupPluginPage(
|
|
738
|
+
ctx.slots,
|
|
739
|
+
() => ctx.slots.register(
|
|
740
|
+
{
|
|
741
|
+
name: BUNDLE_CONFIG_SLOT,
|
|
742
|
+
// The bundle's package name: the key that puts this entry on this
|
|
743
|
+
// bundle's own page, above its rows.
|
|
744
|
+
key: BUNDLE_CONFIG_KEY,
|
|
745
|
+
// The page draws the title, icon and crumb itself; the card keeps its
|
|
746
|
+
// own chrome, controls and section order.
|
|
747
|
+
locale: NS
|
|
748
|
+
},
|
|
749
|
+
TidySettingsPanel
|
|
750
|
+
)
|
|
751
|
+
)),
|
|
752
|
+
"dsh-chat-translate: plugins page config"
|
|
753
|
+
);
|
|
883
754
|
}
|
|
884
755
|
|
|
885
756
|
// src/client/composer/dock-toggle.tsx
|
|
886
|
-
var
|
|
757
|
+
var import_react2 = require("react");
|
|
887
758
|
var import_dsh_client_ui_primitives2 = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
888
759
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
889
760
|
function ComposerTranslateToggle(props) {
|
|
890
761
|
const { t } = props;
|
|
891
|
-
const [state, setState] = (0,
|
|
892
|
-
(0,
|
|
762
|
+
const [state, setState] = (0, import_react2.useState)(() => settingsStore.getState());
|
|
763
|
+
(0, import_react2.useEffect)(() => settingsStore.subscribe(() => setState(settingsStore.getState())), []);
|
|
893
764
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.Tooltip, { label: () => state.enabled ? t("dockOn") : t("dockOff"), side: "top", align: "end", portal: true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
894
765
|
import_dsh_client_ui_primitives2.Pill,
|
|
895
766
|
{
|
|
@@ -922,7 +793,7 @@ function setupComposerToggle(ctx) {
|
|
|
922
793
|
|
|
923
794
|
// src/client/chat/assistant-step.tsx
|
|
924
795
|
var React = __toESM(require("react"), 1);
|
|
925
|
-
var
|
|
796
|
+
var import_react3 = require("react");
|
|
926
797
|
var import_dsh_client_ui_primitives3 = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
927
798
|
|
|
928
799
|
// src/client/chat/row-plan.ts
|
|
@@ -1005,10 +876,8 @@ function planAssistantRow(input) {
|
|
|
1005
876
|
}
|
|
1006
877
|
|
|
1007
878
|
// src/client/chat/click-guard.ts
|
|
1008
|
-
function
|
|
1009
|
-
|
|
1010
|
-
const interactive = typeof target?.closest === "function" ? target.closest('a,button,[role="button"]') : null;
|
|
1011
|
-
if (interactive !== null && interactive !== void 0 && interactive !== event.currentTarget) return false;
|
|
879
|
+
function isCleanHotspotClick(event, view) {
|
|
880
|
+
if (event.target !== event.currentTarget) return false;
|
|
1012
881
|
const selection = typeof view?.getSelection === "function" ? view.getSelection() : null;
|
|
1013
882
|
if (selection !== null && selection !== void 0) {
|
|
1014
883
|
if (selection.isCollapsed === false && String(selection.toString?.() ?? "") !== "") return false;
|
|
@@ -1028,25 +897,42 @@ var ASSISTANT_CSS = [
|
|
|
1028
897
|
".dsh-ct-body .md-table-wide>table{z-index:1;position:relative}",
|
|
1029
898
|
// ---- 已停止标记(宿主 .hWmORq_stopped 等价) ----
|
|
1030
899
|
".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}",
|
|
1031
|
-
// ----
|
|
1032
|
-
//
|
|
1033
|
-
".dsh-ct-prose
|
|
1034
|
-
".dsh-ct-prose-
|
|
900
|
+
// ---- 正文块:可翻译时左缘留出 12px 缩进(缩进与线无关——译文出现前后
|
|
901
|
+
// 正文位置不变,切换总开关时整列也不横移),并成为热区的定位父级 ----
|
|
902
|
+
".dsh-ct-prose{position:relative}",
|
|
903
|
+
".dsh-ct-prose-indent{padding-left:12px}",
|
|
904
|
+
// ---- 线型:粗细与色相只表示状态。蓝粗线=读译文,灰细线=读原文备着译文
|
|
905
|
+
// (0.5px 中性 hairline),红实线=失败(只剩通道伤一种) ----
|
|
1035
906
|
".dsh-ct-prose-translated{border-left:1px solid color-mix(in srgb, var(--dsw-alias-state-business-primary) 65%, transparent)}",
|
|
1036
907
|
".dsh-ct-prose-original{border-left:0.5px solid var(--dsw-alias-border-l2)}",
|
|
1037
|
-
// ---- 失败标记:1px error 色红实线(只剩通道伤一种,悬停报细节);
|
|
1038
|
-
// 可点,点=整行手动补跑 ----
|
|
1039
908
|
".dsh-ct-prose-failed{border-left:1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 65%, transparent)}",
|
|
1040
|
-
// ---- 在途:灰 1px
|
|
909
|
+
// ---- 在途:灰 1px 线脉动(缩进由 .dsh-ct-prose-indent 统一给,转蓝不跳字);
|
|
1041
910
|
// prefers-reduced-motion 降级为静态灰线 ----
|
|
1042
|
-
".dsh-ct-prose-inflight{
|
|
911
|
+
".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}",
|
|
1043
912
|
"@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)}}",
|
|
1044
913
|
"@media (prefers-reduced-motion:reduce){.dsh-ct-prose-inflight{animation:none}}",
|
|
1045
|
-
// ----
|
|
1046
|
-
//
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
914
|
+
// ---- 左缘热区:随块高铺满的一条透明窄带。绝对定位的偏移以 padding box 为
|
|
915
|
+
// 基准、而线画在 border 上,所以线的左缘在 -1px:`left:-5px` 给出线外侧
|
|
916
|
+
// 4px,`width:17px` 让右缘落在正文第一个字上(12px 缩进),不吃正文。
|
|
917
|
+
// 切换与补跑只由它承载;正文整块不再是点击目标,选中/复制不再误触。
|
|
918
|
+
// z-index 压过宽表格的透明出血层(那条规则给 table 挂了 z-index:1);
|
|
919
|
+
// 出血区是透明 padding,正文格子的起点仍在缩进之外 ----
|
|
920
|
+
".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}",
|
|
921
|
+
// 在途只剩「已在跑」这一答复:热区保留(键盘焦点不掉),但光标不承诺能点。
|
|
922
|
+
".dsh-ct-hotspot[data-idle]{cursor:default}",
|
|
923
|
+
// 热区没有自己的外形:它是透明的一层。focus-visible 只对键盘聚焦成立(指针
|
|
924
|
+
// 点出来的焦点不匹配),下面的 :has 规则把线加亮——粗细始终只表示译文/原文。
|
|
925
|
+
".dsh-ct-hotspot:focus-visible{outline:none}",
|
|
926
|
+
".dsh-ct-prose-translated:has(.dsh-ct-hotspot:focus-visible){border-left-color:var(--dsw-alias-state-business-primary)}",
|
|
927
|
+
".dsh-ct-prose-original:has(.dsh-ct-hotspot:focus-visible){border-left-color:var(--dsw-alias-label-caption)}",
|
|
928
|
+
".dsh-ct-prose-failed:has(.dsh-ct-hotspot:focus-visible){border-left-color:var(--dsw-alias-state-error-primary)}",
|
|
929
|
+
// 在途线本就在动;CSS 动画压过普通声明,聚焦的加亮必须同时停掉动画才看得见
|
|
930
|
+
// (停掉脉动本身就是一次可见的变化,线也从闪烁的灰换成静止的亮灰)。
|
|
931
|
+
".dsh-ct-prose-inflight:has(.dsh-ct-hotspot:focus-visible){animation:none;border-left-color:var(--dsw-alias-label-caption)}",
|
|
932
|
+
// ---- 失败块的补跑指引:小 ↻(官方图标)常驻在红线旁——失败不会被悬停
|
|
933
|
+
// 发现,救活入口必须一直看得见;纯装饰(pointer-events:none),点击
|
|
934
|
+
// 归热区,败因由 Tooltip 挂在热区上 ----
|
|
935
|
+
".dsh-ct-retry{position:absolute;left:2px;top:6px;display:flex;color:var(--dsw-alias-state-error-primary);pointer-events:none;z-index:3}",
|
|
1050
936
|
// ---- ReasoningRow(宿主 lcKema_* 等价,前缀换成 dsh-ct-think) ----
|
|
1051
937
|
".dsh-ct-think-root{flex-direction:column;display:flex}",
|
|
1052
938
|
".dsh-ct-think-root:not([data-expanded]){contain:size layout;height:calc(24px + var(--dsh-content-font-delta,0px))}",
|
|
@@ -1068,31 +954,27 @@ var ASSISTANT_CSS = [
|
|
|
1068
954
|
// ---- 无障碍:屏幕阅读器专用文本 ----
|
|
1069
955
|
".dsh-ct-visually-hidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}"
|
|
1070
956
|
].join("");
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
957
|
+
var PROSE_BY_MARK = {
|
|
958
|
+
translated: { className: "dsh-ct-prose-translated", action: "toggle" },
|
|
959
|
+
"original-view": { className: "dsh-ct-prose-original", action: "toggle" },
|
|
960
|
+
failed: { className: "dsh-ct-prose-failed", action: "retry" },
|
|
961
|
+
// 在途有线、有热区,但 action 为 null:按下去是空操作(脉动已是「已在跑」
|
|
962
|
+
// 的答复),热区保留只是为了让键盘焦点不掉回页面。
|
|
963
|
+
inflight: { className: "dsh-ct-prose-inflight", action: null }
|
|
964
|
+
};
|
|
965
|
+
var PROSE_BASE_CLASS = "dsh-ct-prose";
|
|
966
|
+
function prosePresentation(mark) {
|
|
967
|
+
return mark === null ? { className: null, action: null } : PROSE_BY_MARK[mark];
|
|
1081
968
|
}
|
|
1082
969
|
function proseClassNames(mark) {
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
return "dsh-ct-prose-clickable dsh-ct-prose-translated";
|
|
1086
|
-
case "original-view":
|
|
1087
|
-
return "dsh-ct-prose-clickable dsh-ct-prose-original";
|
|
1088
|
-
case "failed":
|
|
1089
|
-
return "dsh-ct-prose-clickable dsh-ct-prose-failed dsh-ct-prose-retryable";
|
|
1090
|
-
case "inflight":
|
|
1091
|
-
return "dsh-ct-prose-inflight";
|
|
1092
|
-
default:
|
|
1093
|
-
return void 0;
|
|
1094
|
-
}
|
|
970
|
+
const line = prosePresentation(mark).className;
|
|
971
|
+
return line === null ? void 0 : `${PROSE_BASE_CLASS} ${line}`;
|
|
1095
972
|
}
|
|
973
|
+
function proseIndentClassName(canTranslate) {
|
|
974
|
+
return canTranslate ? "dsh-ct-prose-indent" : void 0;
|
|
975
|
+
}
|
|
976
|
+
var HOTSPOT_CLASS = "dsh-ct-hotspot";
|
|
977
|
+
var HOTSPOT_IDLE_ATTR = "data-idle";
|
|
1096
978
|
function ensureAssistantStyles() {
|
|
1097
979
|
if (typeof document === "undefined") return;
|
|
1098
980
|
const selector = `style[data-plugin-css=${JSON.stringify(STYLE_TAG_ID)}]`;
|
|
@@ -1163,7 +1045,7 @@ var ChatTranslateStore = class {
|
|
|
1163
1045
|
}
|
|
1164
1046
|
/**
|
|
1165
1047
|
* 确保该行按当前文本被翻译。
|
|
1166
|
-
* @param manual -
|
|
1048
|
+
* @param manual - 用户点击失败块的左缘热区发起的补跑:已落定的同代行只有这条路会重发。
|
|
1167
1049
|
* @returns 是否发出了新的请求。
|
|
1168
1050
|
*/
|
|
1169
1051
|
ensure(rowKey, texts, manual = false) {
|
|
@@ -1343,8 +1225,8 @@ var subscribeSettings = (listener) => settingsStore.subscribe(listener);
|
|
|
1343
1225
|
var subscribeTranslate = (listener) => chatTranslate.subscribe(listener);
|
|
1344
1226
|
function useSettledInView(ref, armed) {
|
|
1345
1227
|
const noObserver = typeof IntersectionObserver === "undefined";
|
|
1346
|
-
const [inView, setInView] = (0,
|
|
1347
|
-
(0,
|
|
1228
|
+
const [inView, setInView] = (0, import_react3.useState)(noObserver);
|
|
1229
|
+
(0, import_react3.useEffect)(() => {
|
|
1348
1230
|
if (noObserver || !armed) return;
|
|
1349
1231
|
const element = ref.current;
|
|
1350
1232
|
if (element === null) return;
|
|
@@ -1361,7 +1243,7 @@ function useSettledInView(ref, armed) {
|
|
|
1361
1243
|
}, [armed, noObserver, ref]);
|
|
1362
1244
|
return inView;
|
|
1363
1245
|
}
|
|
1364
|
-
var ReasoningRow = (0,
|
|
1246
|
+
var ReasoningRow = (0, import_react3.memo)(function ReasoningRow2({
|
|
1365
1247
|
text,
|
|
1366
1248
|
running,
|
|
1367
1249
|
usePresentation,
|
|
@@ -1369,13 +1251,13 @@ var ReasoningRow = (0, import_react4.memo)(function ReasoningRow2({
|
|
|
1369
1251
|
t
|
|
1370
1252
|
}) {
|
|
1371
1253
|
const { expanded, toggle } = useDisclosure();
|
|
1372
|
-
const labels = (0,
|
|
1254
|
+
const labels = (0, import_react3.useMemo)(() => markdownLabels(t), [t]);
|
|
1373
1255
|
const summaryText = running ? latestCompletedParagraphFirstLine(text) : firstLine(text);
|
|
1374
|
-
const summary = (0,
|
|
1256
|
+
const summary = (0, import_react3.useMemo)(() => summaryText.replaceAll("**", ""), [summaryText]);
|
|
1375
1257
|
const preview = usePresentation(
|
|
1376
1258
|
(policy2) => !expanded && summary !== "" && (running || policy2.settledReasoningPreview)
|
|
1377
1259
|
);
|
|
1378
|
-
const collapsedContent = (0,
|
|
1260
|
+
const collapsedContent = (0, import_react3.useMemo)(
|
|
1379
1261
|
() => React.createElement(
|
|
1380
1262
|
React.Fragment,
|
|
1381
1263
|
null,
|
|
@@ -1388,7 +1270,7 @@ var ReasoningRow = (0, import_react4.memo)(function ReasoningRow2({
|
|
|
1388
1270
|
),
|
|
1389
1271
|
[running, summary]
|
|
1390
1272
|
);
|
|
1391
|
-
const content = (0,
|
|
1273
|
+
const content = (0, import_react3.useMemo)(
|
|
1392
1274
|
() => expanded ? React.createElement(
|
|
1393
1275
|
"div",
|
|
1394
1276
|
{ className: "dsh-ct-think-body" },
|
|
@@ -1425,13 +1307,6 @@ var ReasoningRow = (0, import_react4.memo)(function ReasoningRow2({
|
|
|
1425
1307
|
)
|
|
1426
1308
|
);
|
|
1427
1309
|
});
|
|
1428
|
-
function blockClick(event, action) {
|
|
1429
|
-
const guardEvent = {
|
|
1430
|
-
target: event.target,
|
|
1431
|
-
currentTarget: event.currentTarget
|
|
1432
|
-
};
|
|
1433
|
-
if (isBareBlockClick(guardEvent, typeof window === "undefined" ? void 0 : window)) action();
|
|
1434
|
-
}
|
|
1435
1310
|
function ProseBlock({
|
|
1436
1311
|
text,
|
|
1437
1312
|
translated,
|
|
@@ -1439,41 +1314,62 @@ function ProseBlock({
|
|
|
1439
1314
|
fail,
|
|
1440
1315
|
onToggle,
|
|
1441
1316
|
onRetry,
|
|
1317
|
+
canTranslate,
|
|
1442
1318
|
streaming,
|
|
1443
1319
|
labels,
|
|
1444
1320
|
mentions,
|
|
1445
1321
|
pathImages
|
|
1446
1322
|
}) {
|
|
1447
|
-
const action =
|
|
1323
|
+
const { action } = prosePresentation(mark);
|
|
1448
1324
|
const clickable = action !== null;
|
|
1449
1325
|
const retrying = action === "retry";
|
|
1450
1326
|
const handler = retrying ? onRetry : onToggle;
|
|
1451
1327
|
const source = mark === "translated" ? translated : text;
|
|
1452
1328
|
const copy = rowCopy();
|
|
1453
|
-
const
|
|
1329
|
+
const className = [proseClassNames(mark) ?? PROSE_BASE_CLASS, proseIndentClassName(canTranslate)].filter((name2) => name2 !== void 0).join(" ");
|
|
1330
|
+
const hotspot = mark === null ? null : React.createElement("button", {
|
|
1331
|
+
type: "button",
|
|
1332
|
+
className: HOTSPOT_CLASS,
|
|
1333
|
+
// 热区是覆盖层,读屏只应听到它的动作,正文仍由 MarkdownText 读。
|
|
1334
|
+
"aria-label": mark === "inflight" ? copy.inflightAria : retrying ? copy.retryAria : mark === "original-view" ? copy.toggleToTranslatedAria : copy.toggleToOriginalAria,
|
|
1335
|
+
...mark === "inflight" ? { [HOTSPOT_IDLE_ATTR]: "true" } : {},
|
|
1336
|
+
tabIndex: 0,
|
|
1337
|
+
// button 自己会按 Enter/Space 合成 click,若不接管就会与这段键盘处理
|
|
1338
|
+
// 叠加成两次动作:这里两个键都走 keydown 并 preventDefault,合成被掐掉。
|
|
1339
|
+
onKeyDown: clickable ? (event) => {
|
|
1340
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
1341
|
+
event.preventDefault();
|
|
1342
|
+
handler();
|
|
1343
|
+
} : void 0,
|
|
1344
|
+
onClick: clickable ? (event) => {
|
|
1345
|
+
if (isCleanHotspotClick(
|
|
1346
|
+
{ target: event.target, currentTarget: event.currentTarget },
|
|
1347
|
+
typeof window === "undefined" ? void 0 : window
|
|
1348
|
+
)) {
|
|
1349
|
+
handler();
|
|
1350
|
+
}
|
|
1351
|
+
} : void 0
|
|
1352
|
+
});
|
|
1353
|
+
const retryIcon = retrying ? React.createElement(
|
|
1354
|
+
"span",
|
|
1355
|
+
{ className: "dsh-ct-retry", "aria-hidden": true },
|
|
1356
|
+
React.createElement(import_dsh_client_ui_primitives3.IconRefreshOutlineRegular, { size: 10 })
|
|
1357
|
+
) : null;
|
|
1358
|
+
const anchor = hotspot === null ? null : React.createElement(import_dsh_client_ui_primitives3.Tooltip, {
|
|
1359
|
+
label: () => copy.failTitle(fail),
|
|
1360
|
+
side: "top",
|
|
1361
|
+
portal: true,
|
|
1362
|
+
disabled: !retrying,
|
|
1363
|
+
children: hotspot
|
|
1364
|
+
});
|
|
1365
|
+
return React.createElement(
|
|
1454
1366
|
"div",
|
|
1455
1367
|
{
|
|
1456
|
-
className
|
|
1457
|
-
"data-translated": mark === "translated" ? "true" : void 0
|
|
1458
|
-
// 容器内含链接等交互内容,不套 role=button(非法嵌套);可聚焦 + 键盘
|
|
1459
|
-
// Enter/Space 即切换/重试,满足官方「键盘可达」门。在途块也保持可聚焦
|
|
1460
|
-
// (Enter 空操作)——补跑把红线折成脉动时,键盘焦点不致从行上掉回 body。
|
|
1461
|
-
tabIndex: clickable || mark === "inflight" ? 0 : void 0,
|
|
1462
|
-
onClick: clickable ? (event) => blockClick(event, handler) : void 0,
|
|
1463
|
-
onKeyDown: clickable ? (event) => {
|
|
1464
|
-
if (event.target !== event.currentTarget) return;
|
|
1465
|
-
if (event.key === "Enter" || event.key === " ") {
|
|
1466
|
-
event.preventDefault();
|
|
1467
|
-
handler();
|
|
1468
|
-
}
|
|
1469
|
-
} : void 0
|
|
1368
|
+
className,
|
|
1369
|
+
"data-translated": mark === "translated" ? "true" : void 0
|
|
1470
1370
|
},
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
{ className: "dsh-ct-retry", "aria-hidden": true },
|
|
1474
|
-
React.createElement(import_dsh_client_ui_primitives3.IconRefreshOutlineRegular, { size: 10 })
|
|
1475
|
-
),
|
|
1476
|
-
retrying && React.createElement("span", { className: "dsh-ct-visually-hidden" }, copy.retryAria),
|
|
1371
|
+
retryIcon,
|
|
1372
|
+
anchor,
|
|
1477
1373
|
React.createElement(import_dsh_client_ui_primitives3.MarkdownText, {
|
|
1478
1374
|
text: source,
|
|
1479
1375
|
streaming,
|
|
@@ -1482,40 +1378,33 @@ function ProseBlock({
|
|
|
1482
1378
|
pathImages
|
|
1483
1379
|
})
|
|
1484
1380
|
);
|
|
1485
|
-
if (!retrying) return block;
|
|
1486
|
-
return React.createElement(import_dsh_client_ui_primitives3.Tooltip, {
|
|
1487
|
-
label: () => copy.failTitle(fail),
|
|
1488
|
-
side: "right",
|
|
1489
|
-
portal: true,
|
|
1490
|
-
children: block
|
|
1491
|
-
});
|
|
1492
1381
|
}
|
|
1493
|
-
var AssistantStepView = (0,
|
|
1382
|
+
var AssistantStepView = (0, import_react3.memo)(function AssistantStepView2(props) {
|
|
1494
1383
|
ensureAssistantStyles();
|
|
1495
1384
|
const { node, groupPart, useDisclosure, useTurnData, turnProcess, openFile, renderMessageImages, fileMentions, usePresentation, t } = props;
|
|
1496
1385
|
const data = node.data;
|
|
1497
1386
|
const streaming = data.status === "running";
|
|
1498
1387
|
const interrupted = data.status === "interrupted";
|
|
1499
|
-
const enabled = (0,
|
|
1500
|
-
const aiConfigured = (0,
|
|
1501
|
-
(0,
|
|
1388
|
+
const enabled = (0, import_react3.useSyncExternalStore)(subscribeSettings, () => settingsStore.getState().enabled);
|
|
1389
|
+
const aiConfigured = (0, import_react3.useSyncExternalStore)(subscribeSettings, () => settingsStore.getState().aiConfigured);
|
|
1390
|
+
(0, import_react3.useSyncExternalStore)(subscribeTranslate, () => chatTranslate.getVersion());
|
|
1502
1391
|
const canTranslate = enabled && aiConfigured;
|
|
1503
1392
|
const rowKey = `a${node.anchorSeq ?? `t${data.turn}:s${data.step}`}`;
|
|
1504
|
-
const labels = (0,
|
|
1505
|
-
const pathImages = (0,
|
|
1393
|
+
const labels = (0, import_react3.useMemo)(() => markdownLabels(t), [t]);
|
|
1394
|
+
const pathImages = (0, import_react3.useMemo)(
|
|
1506
1395
|
() => ({ resolve: (value) => localPathMediaUrl(document.baseURI, value) }),
|
|
1507
1396
|
[]
|
|
1508
1397
|
);
|
|
1509
1398
|
const turn = node.location?.kind === "turn" || node.location?.kind === "step" ? node.location.turn : void 0;
|
|
1510
1399
|
const tail = useTurnData("turn-tail");
|
|
1511
|
-
const owner = (0,
|
|
1400
|
+
const owner = (0, import_react3.useMemo)(() => {
|
|
1512
1401
|
if (turn?.status !== "closed" || data.finalNode === void 0) return void 0;
|
|
1513
1402
|
if (tail?.closing?.finalNode?.seq !== data.finalNode.seq) return void 0;
|
|
1514
1403
|
return { turn, seq: data.finalNode.seq, openFile };
|
|
1515
1404
|
}, [data.finalNode, openFile, tail, turn]);
|
|
1516
|
-
const mentions = (0,
|
|
1517
|
-
const blocks = (0,
|
|
1518
|
-
const plan = (0,
|
|
1405
|
+
const mentions = (0, import_react3.useMemo)(() => owner === void 0 ? void 0 : fileMentions(owner), [fileMentions, owner]);
|
|
1406
|
+
const blocks = (0, import_react3.useMemo)(() => data.blocks ?? [], [data.blocks]);
|
|
1407
|
+
const plan = (0, import_react3.useMemo)(
|
|
1519
1408
|
() => planAssistantRow({
|
|
1520
1409
|
blocks,
|
|
1521
1410
|
streaming,
|
|
@@ -1526,16 +1415,16 @@ var AssistantStepView = (0, import_react4.memo)(function AssistantStepView2(prop
|
|
|
1526
1415
|
}),
|
|
1527
1416
|
[blocks, streaming, interrupted, groupPart, canTranslate]
|
|
1528
1417
|
);
|
|
1529
|
-
const rootRef = (0,
|
|
1418
|
+
const rootRef = (0, import_react3.useRef)(null);
|
|
1530
1419
|
const inView = useSettledInView(rootRef, !streaming && canTranslate && plan.entries !== null && plan.texts.length > 0);
|
|
1531
|
-
(0,
|
|
1420
|
+
(0, import_react3.useEffect)(() => {
|
|
1532
1421
|
if (streaming || !canTranslate || !inView || plan.entries === null || plan.texts.length === 0) return;
|
|
1533
1422
|
chatTranslate.ensure(rowKey, plan.texts);
|
|
1534
1423
|
}, [streaming, canTranslate, inView, rowKey, plan]);
|
|
1535
|
-
const [originalKeys, setOriginalKeys] = (0,
|
|
1424
|
+
const [originalKeys, setOriginalKeys] = (0, import_react3.useState)(() => /* @__PURE__ */ new Set());
|
|
1536
1425
|
const registered = chatTranslate.getState(rowKey);
|
|
1537
1426
|
const rowState = registered !== void 0 && sameTexts(registered.texts, plan.texts) ? registered : void 0;
|
|
1538
|
-
const displayed = (0,
|
|
1427
|
+
const displayed = (0, import_react3.useMemo)(
|
|
1539
1428
|
() => planAssistantRow({
|
|
1540
1429
|
blocks,
|
|
1541
1430
|
streaming,
|
|
@@ -1549,10 +1438,10 @@ var AssistantStepView = (0, import_react4.memo)(function AssistantStepView2(prop
|
|
|
1549
1438
|
[blocks, streaming, interrupted, groupPart, canTranslate, rowState, originalKeys]
|
|
1550
1439
|
);
|
|
1551
1440
|
const reasoningHidden = turnProcess !== void 0 && turnProcess.foldable && turnProcess.spec.answerStep === data.step && turnProcess.spec.inlineReasoning && !turnProcess.open;
|
|
1552
|
-
const revealProcess = (0,
|
|
1441
|
+
const revealProcess = (0, import_react3.useCallback)(() => {
|
|
1553
1442
|
turnProcess?.setOpen(true);
|
|
1554
1443
|
}, [turnProcess]);
|
|
1555
|
-
const toggleBlock = (0,
|
|
1444
|
+
const toggleBlock = (0, import_react3.useCallback)((key) => {
|
|
1556
1445
|
setOriginalKeys((current) => {
|
|
1557
1446
|
const next = new Set(current);
|
|
1558
1447
|
if (next.has(key)) next.delete(key);
|
|
@@ -1560,7 +1449,7 @@ var AssistantStepView = (0, import_react4.memo)(function AssistantStepView2(prop
|
|
|
1560
1449
|
return next;
|
|
1561
1450
|
});
|
|
1562
1451
|
}, []);
|
|
1563
|
-
const retryRow = (0,
|
|
1452
|
+
const retryRow = (0, import_react3.useCallback)(() => {
|
|
1564
1453
|
chatTranslate.ensure(rowKey, plan.texts, true);
|
|
1565
1454
|
}, [rowKey, plan]);
|
|
1566
1455
|
if (displayed.entries === null) return null;
|
|
@@ -1578,6 +1467,7 @@ var AssistantStepView = (0, import_react4.memo)(function AssistantStepView2(prop
|
|
|
1578
1467
|
fail: entry.fail,
|
|
1579
1468
|
onToggle: () => toggleBlock(entry.key),
|
|
1580
1469
|
onRetry: retryRow,
|
|
1470
|
+
canTranslate,
|
|
1581
1471
|
streaming,
|
|
1582
1472
|
labels,
|
|
1583
1473
|
mentions,
|
|
@@ -1745,7 +1635,7 @@ function apply(ctx) {
|
|
|
1745
1635
|
chatPresentation.attach(form);
|
|
1746
1636
|
return () => chatPresentation.detach();
|
|
1747
1637
|
}, "dsh-chat-translate: chat presentation source");
|
|
1748
|
-
ctx.effect(() => setupSettingsUi(ctx), "dsh-chat-translate:
|
|
1638
|
+
ctx.effect(() => setupSettingsUi(ctx), "dsh-chat-translate: plugins page config");
|
|
1749
1639
|
ctx.effect(() => setupComposerToggle(ctx), "dsh-chat-translate: composer dock toggle");
|
|
1750
1640
|
bindRowCopy(ctx.locale);
|
|
1751
1641
|
}
|