dsh-better-display 0.1.1 → 0.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.
Files changed (214) hide show
  1. package/CHANGELOG.md +188 -4
  2. package/README.en.md +19 -3
  3. package/README.md +19 -3
  4. package/compat/harness-rc2.json +59 -0
  5. package/docs/official-rendering-bridge.md +65 -0
  6. package/lib/client.js +8827 -7639
  7. package/lib/client.js.map +1 -1
  8. package/lib/dsh-better-display.js +51 -55
  9. package/lib/types/client/Blocks.d.ts +6 -2
  10. package/lib/types/client/Blocks.d.ts.map +1 -1
  11. package/lib/types/client/Blocks.js +13 -6
  12. package/lib/types/client/Blocks.js.map +1 -1
  13. package/lib/types/client/ChoreographedFlow.d.ts.map +1 -1
  14. package/lib/types/client/ChoreographedFlow.js +68 -20
  15. package/lib/types/client/ChoreographedFlow.js.map +1 -1
  16. package/lib/types/client/ClosedProcessSummary.d.ts.map +1 -1
  17. package/lib/types/client/ClosedProcessSummary.js +3 -2
  18. package/lib/types/client/ClosedProcessSummary.js.map +1 -1
  19. package/lib/types/client/DiffPanel.d.ts +13 -0
  20. package/lib/types/client/DiffPanel.d.ts.map +1 -0
  21. package/lib/types/client/DiffPanel.js +115 -0
  22. package/lib/types/client/DiffPanel.js.map +1 -0
  23. package/lib/types/client/OfficialContent.d.ts +25 -0
  24. package/lib/types/client/OfficialContent.d.ts.map +1 -0
  25. package/lib/types/client/OfficialContent.js +42 -0
  26. package/lib/types/client/OfficialContent.js.map +1 -0
  27. package/lib/types/client/Reader.d.ts.map +1 -1
  28. package/lib/types/client/Reader.js +123 -57
  29. package/lib/types/client/Reader.js.map +1 -1
  30. package/lib/types/client/ReasoningCard.d.ts.map +1 -1
  31. package/lib/types/client/ReasoningCard.js +29 -8
  32. package/lib/types/client/ReasoningCard.js.map +1 -1
  33. package/lib/types/client/SettingsSection.d.ts +35 -0
  34. package/lib/types/client/SettingsSection.d.ts.map +1 -0
  35. package/lib/types/client/SettingsSection.js +62 -0
  36. package/lib/types/client/SettingsSection.js.map +1 -0
  37. package/lib/types/client/StickyLane.d.ts.map +1 -1
  38. package/lib/types/client/StickyLane.js +16 -5
  39. package/lib/types/client/StickyLane.js.map +1 -1
  40. package/lib/types/client/ToolActivity.d.ts.map +1 -1
  41. package/lib/types/client/ToolActivity.js +59 -7
  42. package/lib/types/client/ToolActivity.js.map +1 -1
  43. package/lib/types/client/WaitClock.d.ts +16 -0
  44. package/lib/types/client/WaitClock.d.ts.map +1 -0
  45. package/lib/types/client/WaitClock.js +29 -0
  46. package/lib/types/client/WaitClock.js.map +1 -0
  47. package/lib/types/client/WaitingClock.d.ts +15 -0
  48. package/lib/types/client/WaitingClock.d.ts.map +1 -0
  49. package/lib/types/client/WaitingClock.js +30 -0
  50. package/lib/types/client/WaitingClock.js.map +1 -0
  51. package/lib/types/client/WaitingStatus.d.ts.map +1 -1
  52. package/lib/types/client/WaitingStatus.js +4 -14
  53. package/lib/types/client/WaitingStatus.js.map +1 -1
  54. package/lib/types/client/fold-choreography.d.ts +12 -0
  55. package/lib/types/client/fold-choreography.d.ts.map +1 -1
  56. package/lib/types/client/fold-choreography.js +18 -1
  57. package/lib/types/client/fold-choreography.js.map +1 -1
  58. package/lib/types/client/fold-intensity.d.ts +9 -0
  59. package/lib/types/client/fold-intensity.d.ts.map +1 -0
  60. package/lib/types/client/fold-intensity.js +25 -0
  61. package/lib/types/client/fold-intensity.js.map +1 -0
  62. package/lib/types/client/index.d.ts.map +1 -1
  63. package/lib/types/client/index.js +62 -20
  64. package/lib/types/client/index.js.map +1 -1
  65. package/lib/types/client/live-turn.d.ts.map +1 -1
  66. package/lib/types/client/live-turn.js +46 -0
  67. package/lib/types/client/live-turn.js.map +1 -1
  68. package/lib/types/client/markdown/MarkdownText.d.ts +3 -1
  69. package/lib/types/client/markdown/MarkdownText.d.ts.map +1 -1
  70. package/lib/types/client/markdown/MarkdownText.js +14 -7
  71. package/lib/types/client/markdown/MarkdownText.js.map +1 -1
  72. package/lib/types/client/markdown/render.d.ts +2 -0
  73. package/lib/types/client/markdown/render.d.ts.map +1 -1
  74. package/lib/types/client/markdown/render.js +22 -6
  75. package/lib/types/client/markdown/render.js.map +1 -1
  76. package/lib/types/client/motion.d.ts.map +1 -1
  77. package/lib/types/client/motion.js +102 -16
  78. package/lib/types/client/motion.js.map +1 -1
  79. package/lib/types/client/native/ContextInjectionRow.d.ts +12 -3
  80. package/lib/types/client/native/ContextInjectionRow.d.ts.map +1 -1
  81. package/lib/types/client/native/ContextInjectionRow.js +6 -3
  82. package/lib/types/client/native/ContextInjectionRow.js.map +1 -1
  83. package/lib/types/client/official-slots.d.ts +71 -0
  84. package/lib/types/client/official-slots.d.ts.map +1 -0
  85. package/lib/types/client/official-slots.js +183 -0
  86. package/lib/types/client/official-slots.js.map +1 -0
  87. package/lib/types/client/open-file.d.ts +6 -1
  88. package/lib/types/client/open-file.d.ts.map +1 -0
  89. package/lib/types/client/open-file.js +71 -0
  90. package/lib/types/client/open-file.js.map +1 -0
  91. package/lib/types/client/pending-submission.d.ts +28 -0
  92. package/lib/types/client/pending-submission.d.ts.map +1 -0
  93. package/lib/types/client/pending-submission.js +28 -0
  94. package/lib/types/client/pending-submission.js.map +1 -0
  95. package/lib/types/client/platform-media.d.ts +6 -0
  96. package/lib/types/client/platform-media.d.ts.map +1 -0
  97. package/lib/types/client/platform-media.js +13 -0
  98. package/lib/types/client/platform-media.js.map +1 -0
  99. package/lib/types/client/reasoning-follow.d.ts +5 -1
  100. package/lib/types/client/reasoning-follow.d.ts.map +1 -1
  101. package/lib/types/client/reasoning-follow.js +13 -4
  102. package/lib/types/client/reasoning-follow.js.map +1 -1
  103. package/lib/types/client/resource-address.d.ts +3 -0
  104. package/lib/types/client/resource-address.d.ts.map +1 -0
  105. package/lib/types/client/resource-address.js +33 -0
  106. package/lib/types/client/resource-address.js.map +1 -0
  107. package/lib/types/client/settings-copy.d.ts +3 -2
  108. package/lib/types/client/settings-copy.d.ts.map +1 -0
  109. package/lib/types/client/settings-copy.js +49 -0
  110. package/lib/types/client/settings-copy.js.map +1 -0
  111. package/lib/types/client/settings-slots.d.ts +7 -0
  112. package/lib/types/client/settings-slots.d.ts.map +1 -0
  113. package/lib/types/client/settings-slots.js +2 -0
  114. package/lib/types/client/settings-slots.js.map +1 -0
  115. package/lib/types/client/settings.d.ts +4 -0
  116. package/lib/types/client/settings.d.ts.map +1 -0
  117. package/lib/types/client/settings.js +60 -0
  118. package/lib/types/client/settings.js.map +1 -0
  119. package/lib/types/client/skill-status.d.ts +25 -0
  120. package/lib/types/client/skill-status.d.ts.map +1 -0
  121. package/lib/types/client/skill-status.js +44 -0
  122. package/lib/types/client/skill-status.js.map +1 -0
  123. package/lib/types/client/store.d.ts +15 -0
  124. package/lib/types/client/store.d.ts.map +1 -1
  125. package/lib/types/client/store.js +24 -2
  126. package/lib/types/client/store.js.map +1 -1
  127. package/lib/types/client/stream-buffer.d.ts +11 -3
  128. package/lib/types/client/stream-buffer.d.ts.map +1 -1
  129. package/lib/types/client/stream-buffer.js +39 -2
  130. package/lib/types/client/stream-buffer.js.map +1 -1
  131. package/lib/types/client/streaming.d.ts.map +1 -1
  132. package/lib/types/client/streaming.js +9 -2
  133. package/lib/types/client/streaming.js.map +1 -1
  134. package/lib/types/client/tool-activity.d.ts +47 -1
  135. package/lib/types/client/tool-activity.d.ts.map +1 -1
  136. package/lib/types/client/tool-activity.js +125 -2
  137. package/lib/types/client/tool-activity.js.map +1 -1
  138. package/lib/types/client/types.d.ts +28 -5
  139. package/lib/types/client/types.d.ts.map +1 -1
  140. package/lib/types/client/waiting-clock.d.ts +34 -2
  141. package/lib/types/client/waiting-clock.d.ts.map +1 -1
  142. package/lib/types/client/waiting-clock.js +62 -6
  143. package/lib/types/client/waiting-clock.js.map +1 -1
  144. package/lib/types/client/word-motion.d.ts.map +1 -1
  145. package/lib/types/client/word-motion.js +2 -1
  146. package/lib/types/client/word-motion.js.map +1 -1
  147. package/lib/types/client/word-timeline.d.ts +4 -0
  148. package/lib/types/client/word-timeline.d.ts.map +1 -1
  149. package/lib/types/client/word-timeline.js +21 -2
  150. package/lib/types/client/word-timeline.js.map +1 -1
  151. package/lib/types/dsh-better-display.d.ts.map +1 -1
  152. package/lib/types/dsh-better-display.js +42 -2
  153. package/lib/types/dsh-better-display.js.map +1 -1
  154. package/lib/types/skill-roots.d.ts +22 -0
  155. package/lib/types/skill-roots.d.ts.map +1 -0
  156. package/lib/types/skill-roots.js +126 -0
  157. package/lib/types/skill-roots.js.map +1 -0
  158. package/lib/types/skill-status.d.ts +36 -0
  159. package/lib/types/skill-status.d.ts.map +1 -0
  160. package/lib/types/skill-status.js +51 -0
  161. package/lib/types/skill-status.js.map +1 -0
  162. package/package.json +22 -16
  163. package/scripts/check-harness-compat.mjs +71 -0
  164. package/scripts/link-harness-dependencies.mjs +1 -0
  165. package/scripts/verify-auto-fold.mjs +114 -0
  166. package/scripts/verify-official-bridge.mjs +119 -0
  167. package/src/client/Blocks.tsx +13 -5
  168. package/src/client/ChoreographedFlow.tsx +68 -18
  169. package/src/client/ClosedProcessSummary.tsx +6 -2
  170. package/src/client/DiffPanel.tsx +124 -0
  171. package/src/client/OfficialContent.tsx +57 -0
  172. package/src/client/Reader.module.css +341 -13
  173. package/src/client/Reader.tsx +113 -49
  174. package/src/client/ReasoningCard.tsx +27 -6
  175. package/src/client/SettingsSection.module.css +37 -0
  176. package/src/client/SettingsSection.tsx +64 -6
  177. package/src/client/StickyLane.tsx +13 -3
  178. package/src/client/ToolActivity.tsx +67 -7
  179. package/src/client/WaitClock.tsx +33 -0
  180. package/src/client/WaitingStatus.tsx +6 -15
  181. package/src/client/fold-choreography.ts +18 -1
  182. package/src/client/fold-intensity.ts +28 -0
  183. package/src/client/index.tsx +29 -7
  184. package/src/client/live-turn.ts +43 -0
  185. package/src/client/markdown/MarkdownText.tsx +14 -6
  186. package/src/client/markdown/render.tsx +23 -7
  187. package/src/client/motion.tsx +91 -19
  188. package/src/client/native/ContextInjectionRow.tsx +19 -7
  189. package/src/client/official-slots.tsx +211 -0
  190. package/src/client/platform-media.ts +11 -0
  191. package/src/client/reasoning-follow.ts +20 -4
  192. package/src/client/settings-copy.ts +21 -0
  193. package/src/client/settings.tsx +1 -1
  194. package/src/client/store.ts +42 -2
  195. package/src/client/stream-buffer.ts +38 -2
  196. package/src/client/streaming.tsx +9 -1
  197. package/src/client/tool-activity.ts +148 -2
  198. package/src/client/types.ts +27 -6
  199. package/src/client/waiting-clock.ts +64 -6
  200. package/src/client/word-motion.tsx +2 -1
  201. package/src/client/word-timeline.ts +19 -2
  202. package/tests/browser-auto-fold.tsx +64 -0
  203. package/tests/browser-official-bridge.tsx +144 -0
  204. package/tests/fold-choreography.test.ts +16 -0
  205. package/tests/fold-intensity.test.ts +94 -0
  206. package/tests/footer-precedence.test.ts +129 -0
  207. package/tests/official-slots.test.ts +112 -0
  208. package/tests/open-file.test.ts +11 -0
  209. package/tests/platform-media.test.ts +17 -0
  210. package/tests/stock-install.test.ts +8 -0
  211. package/tests/stream-buffer.test.ts +8 -3
  212. package/tests/tool-diff.test.ts +57 -0
  213. package/tests/waiting-clock.test.ts +78 -19
  214. package/tests/word-timeline.test.ts +14 -4
@@ -0,0 +1,115 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useLayoutEffect, useMemo, useRef, useState } from 'react';
3
+ import { DiffBlock, diffTotals } from '@deepseek-ai/dsh-client-ui-primitives';
4
+ import css from './Reader.module.css';
5
+ import { diffBlockLabels } from './primitive-labels.js';
6
+ import { FOLD_TIMING } from './fold-choreography.js';
7
+ import { foldDiffHunks } from './tool-activity.js';
8
+ /**
9
+ * Reveals and withdraws its child by animating real height, in the plugin's own
10
+ * motion language. Height is the animated property rather than transform because
11
+ * the panel pushes the transcript down and the reading scroll measures that
12
+ * growth; the curve and duration are the fold choreography's, so this reads as
13
+ * the same gesture as opening a digest.
14
+ *
15
+ * Closing animates too: the child stays mounted until the shrink finishes, which
16
+ * is why the owner keeps it alive on an `open` flag instead of unmounting it.
17
+ */
18
+ function MorphPanel({ open, children, onSettled, onClosed }) {
19
+ const ref = useRef(null);
20
+ useLayoutEffect(() => {
21
+ const element = ref.current;
22
+ if (!element)
23
+ return;
24
+ element.dispatchEvent(new CustomEvent('reader-layout-start', { bubbles: true }));
25
+ let released = false;
26
+ const release = () => { if (!released) {
27
+ released = true;
28
+ element.dispatchEvent(new CustomEvent('reader-layout-end', { bubbles: true }));
29
+ } };
30
+ if (typeof element.animate !== 'function') {
31
+ release();
32
+ if (!open)
33
+ onClosed();
34
+ return;
35
+ }
36
+ const height = element.scrollHeight;
37
+ const frames = open
38
+ ? [{ height: '0px', opacity: 0, transform: 'translateY(-6px)' },
39
+ { height: `${height}px`, opacity: 1, transform: 'translateY(0)' }]
40
+ : [{ height: `${height}px`, opacity: 1 },
41
+ { height: '0px', opacity: 0 }];
42
+ const animation = element.animate(frames, {
43
+ duration: FOLD_TIMING.reveal + FOLD_TIMING.settle,
44
+ easing: 'cubic-bezier(.4,0,.2,1)',
45
+ });
46
+ let settled = false;
47
+ const settle = () => {
48
+ if (settled)
49
+ return;
50
+ settled = true;
51
+ release();
52
+ if (open) {
53
+ element.style.height = 'auto';
54
+ element.style.overflow = 'visible';
55
+ onSettled?.(element);
56
+ }
57
+ else {
58
+ onClosed();
59
+ }
60
+ };
61
+ animation.onfinish = settle;
62
+ const deadline = window.setTimeout(settle, FOLD_TIMING.reveal + FOLD_TIMING.settle + 240);
63
+ return () => { window.clearTimeout(deadline); animation.cancel(); release(); };
64
+ }, [open]);
65
+ return _jsx("div", { ref: ref, className: css.diffOverlay, children: children });
66
+ }
67
+ /**
68
+ * Keeps a newly opened panel on screen.
69
+ *
70
+ * Opening grows the row downward, so a badge near the fold reveals its card below
71
+ * the viewport and the reader has to chase it. The scroll container is not known
72
+ * here, so the nearest scrollable ancestor is found and the card's head is brought
73
+ * to the top of it — the same thing a jump-to-anchor does.
74
+ */
75
+ function revealPanel(element) {
76
+ let port = element.parentElement;
77
+ while (port && port.scrollHeight <= port.clientHeight + 1)
78
+ port = port.parentElement;
79
+ if (!port) {
80
+ element.scrollIntoView({ block: 'nearest' });
81
+ return;
82
+ }
83
+ const gap = element.getBoundingClientRect().top - port.getBoundingClientRect().top;
84
+ // Already fully visible: leave the scroll alone rather than jumping on every open.
85
+ if (gap >= 0 && element.getBoundingClientRect().bottom <= port.getBoundingClientRect().bottom)
86
+ return;
87
+ port.scrollTop += gap - 8;
88
+ }
89
+ /**
90
+ * Line counts for everything a folded run of steps changed.
91
+ *
92
+ * A run that touched no files renders nothing, so an ordinary process digest is
93
+ * unchanged. Clicking the counts opens the same diff surface the official tool
94
+ * row uses — one file at a time, red removed / green added, expandable.
95
+ */
96
+ export function DiffStat({ steps, label }) {
97
+ const hunks = useMemo(() => foldDiffHunks(steps), [steps]);
98
+ const [open, setOpen] = useState(false);
99
+ const [visible, setVisible] = useState(false);
100
+ const totals = useMemo(() => diffTotals(hunks), [hunks]);
101
+ const [active, setActive] = useState(0);
102
+ if (!hunks.length)
103
+ return null;
104
+ const current = hunks[Math.min(active, hunks.length - 1)];
105
+ return _jsxs(_Fragment, { children: [_jsx("span", { className: css.diffStatRoot, children: _jsxs("button", { type: "button", className: css.diffStatButton, "aria-expanded": open, "aria-label": `${label} · 改动 ${totals.added} 行,删除 ${totals.removed} 行`, onClick: event => {
106
+ event.stopPropagation();
107
+ if (open) {
108
+ setOpen(false);
109
+ return;
110
+ }
111
+ setVisible(true);
112
+ setOpen(true);
113
+ }, children: [_jsx("span", { className: css.diffCaret, "aria-hidden": true, children: open ? '\u25be' : '\u25b8' }), totals.added > 0 && _jsxs("span", { className: css.diffAdded, children: ["+", totals.added] }), totals.removed > 0 && _jsxs("span", { className: css.diffRemoved, children: ["-", totals.removed] })] }) }), visible && _jsx("div", { className: css.diffOverlayRow, children: _jsxs(MorphPanel, { open: open, onSettled: revealPanel, onClosed: () => setVisible(false), children: [hunks.length > 1 && _jsx("div", { className: css.diffTabs, children: hunks.map((hunk, index) => _jsx("button", { type: "button", className: css.diffTab, "data-active": index === active || undefined, onClick: () => setActive(index), title: hunk.path, children: hunk.path.split(/[/\\]+/).filter(Boolean).slice(-1)[0] ?? hunk.path }, `${hunk.path}:${index}`)) }), _jsx("div", { className: css.diffScrollArea, children: _jsx(DiffBlock, { diffs: [current], maxLines: 36, labels: diffBlockLabels }) })] }) })] });
114
+ }
115
+ //# sourceMappingURL=DiffPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DiffPanel.js","sourceRoot":"","sources":["../../../src/client/DiffPanel.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { ChatConversationViewNode, TurnTailOwnerProps } from '@deepseek-ai/dsh-client-ui-chat/client';
3
+ import type { ToolCallBlock } from '@deepseek-ai/dsh-client-ui-conversation/client';
4
+ import type { AssistantActionOwnerProps } from '@deepseek-ai/dsh-client-ui-chat/client';
5
+ type MessageId = AssistantActionOwnerProps['messageId'];
6
+ import type { BlockRenderProps } from './types.js';
7
+ export declare function OfficialActions({ official, messageId }: Pick<BlockRenderProps, 'official'> & {
8
+ messageId?: MessageId;
9
+ }): import("react").JSX.Element | null;
10
+ export declare function OfficialTool({ official, block, toolName, cwd, openFile, fallback }: BlockRenderProps & {
11
+ official: NonNullable<BlockRenderProps['official']>;
12
+ block: ToolCallBlock;
13
+ toolName: string;
14
+ fallback: ReactNode;
15
+ }): import("react").JSX.Element;
16
+ export declare function OfficialNode({ node, fallback, ...render }: BlockRenderProps & {
17
+ node: ChatConversationViewNode;
18
+ fallback: ReactNode;
19
+ }): string | number | boolean | Iterable<ReactNode> | import("react").JSX.Element | null | undefined;
20
+ export declare function OfficialTail({ official, owner, produced }: Pick<BlockRenderProps, 'official'> & {
21
+ owner?: TurnTailOwnerProps;
22
+ produced: readonly string[];
23
+ }): import("react").JSX.Element | null;
24
+ export {};
25
+ //# sourceMappingURL=OfficialContent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OfficialContent.d.ts","sourceRoot":"","sources":["../../../src/client/OfficialContent.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC7D,OAAO,KAAK,EAAE,wBAAwB,EAAsB,kBAAkB,EAAE,MAAM,wCAAwC,CAAC;AAC/H,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gDAAgD,CAAC;AACpF,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,wCAAwC,CAAC;AACxF,KAAK,SAAS,GAAG,yBAAyB,CAAC,WAAW,CAAC,CAAC;AAExD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,wBAAgB,eAAe,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,GAAG;IAAE,SAAS,CAAC,EAAE,SAAS,CAAA;CAAE,sCAKtH;AAED,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,gBAAgB,GAAG;IACtG,QAAQ,EAAE,WAAW,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC,CAAC;IAAC,KAAK,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAC;CAClH,+BAUA;AAED,wBAAgB,YAAY,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,MAAM,EAAE,EAAE,gBAAgB,GAAG;IAAE,IAAI,EAAE,wBAAwB,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAA;CAAE,oGAoBrI;AAED,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,GAAG;IAAE,KAAK,CAAC,EAAE,kBAAkB,CAAC;IAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,sCAK3J"}
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useSyncExternalStore } from 'react';
3
+ import { OFFICIAL_SEATS } from './official-slots.js';
4
+ export function OfficialActions({ official, messageId }) {
5
+ if (!official || !messageId)
6
+ return null;
7
+ return _jsx("span", { "data-reader-official-actions": true, style: { display: 'contents' }, children: official.renderSlot(OFFICIAL_SEATS.actions, { messageId }) });
8
+ }
9
+ export function OfficialTool({ official, block, toolName, cwd, openFile, fallback }) {
10
+ const home = useSyncExternalStore(official.officialHost.subscribe, official.officialHost.getSnapshot).home;
11
+ return _jsx("div", { "data-reader-tool-official": true, children: official.renderSlot(OFFICIAL_SEATS.tools, {
12
+ callId: block.callId, toolName, block, cwd, home,
13
+ openFile: openFile ?? (() => { }),
14
+ loadImage: official.officialImageLoader,
15
+ inspect: () => official.openView('trajectory', block.callId),
16
+ }, { entryKey: toolName, fallback }) });
17
+ }
18
+ export function OfficialNode({ node, fallback, ...render }) {
19
+ const official = render.official;
20
+ if (!official)
21
+ return fallback;
22
+ const owner = {
23
+ cwd: render.cwd,
24
+ openFile: render.openFile ?? (() => { }),
25
+ forkAt: render.forkAt ?? (() => { }),
26
+ inspectCall: callId => official.openView('trajectory', callId),
27
+ loadImage: official.officialImageLoader,
28
+ renderMessageImages: images => official.renderSlot(OFFICIAL_SEATS.images, {
29
+ ...images, loadImage: official.officialImageLoader,
30
+ }),
31
+ fileMentions: official.officialFileMentions,
32
+ };
33
+ const turn = node.location.kind === 'turn' || node.location.kind === 'step' ? node.location.turn : undefined;
34
+ // The runtime node domain is open; the public SlotMap enumerates the known kinds.
35
+ const renderNode = official.renderSlot;
36
+ return _jsx("div", { "data-reader-official-node": node.kind, "data-chat-anchor-key": node.key, "data-chat-flow-kind": node.kind, children: renderNode(OFFICIAL_SEATS.nodes, { ...owner, node }, { entryKey: node.kind, hookContext: turn?.data, fallback }) });
37
+ }
38
+ export function OfficialTail({ official, owner, produced }) {
39
+ const tailOwner = owner && official ? { ...owner, openFile: official.officialPreviewFile, readerProducedPaths: produced } : undefined;
40
+ return official && tailOwner ? _jsx("div", { "data-reader-official-tail": true, style: { display: 'contents' }, children: official.renderSlotChain(OFFICIAL_SEATS.tail, tailOwner) }) : null;
41
+ }
42
+ //# sourceMappingURL=OfficialContent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OfficialContent.js","sourceRoot":"","sources":["../../../src/client/OfficialContent.tsx"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"Reader.d.ts","sourceRoot":"","sources":["../../../src/client/Reader.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAoB,WAAW,EAAE,MAAM,YAAY,CAAC;AA4hBhE,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,+BAyOxC"}
1
+ {"version":3,"file":"Reader.d.ts","sourceRoot":"","sources":["../../../src/client/Reader.tsx"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAoB,WAAW,EAAE,MAAM,YAAY,CAAC;AA+kBhE,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,+BAiQxC"}
@@ -1,21 +1,25 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Fragment, memo, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from 'react';
2
+ import { Fragment, memo, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
3
3
  import { JsonBlock, MarkdownText } from '@deepseek-ai/dsh-client-ui-primitives';
4
4
  import { BlockBoundary, Blocks, contentBlocks, CopyAnswer, UserMessageActions } from './Blocks.js';
5
5
  import { ReasoningCard } from './ReasoningCard.js';
6
6
  import { ToolActivity, ToolMedia } from './ToolActivity.js';
7
+ import { OfficialActions, OfficialNode, OfficialTail } from './OfficialContent.js';
7
8
  import { preparingLabel, readerFlow } from './tool-activity.js';
8
9
  import { Disclosure, ProcessFragment, RetiringContent, StatusText, useMotionAllowed, usePinnedSelection, useReadingScroll } from './motion.js';
9
10
  import { StreamMotionContext } from './streaming.js';
10
11
  import { assistantSegments, boundaryOf, forkAnchorSeq, groupNodes, hasProcessContent, hasVisibleBody, isEarlierNarration, processChoiceKey, processExpanded, terminalLabel } from './projection.js';
11
12
  import { basename, createProducedFileMentions, dirname, getTurnDeliverables, showDeliverablesRow } from './deliverables.js';
13
+ import { deliverableOpenModeOf } from './open-file.js';
14
+ import { asReadonlyArray, pendingSubmissionImages } from './pending-submission.js';
12
15
  import { WaitingStatus } from './WaitingStatus.js';
13
- import { waitingAnchor } from './waiting-clock.js';
16
+ import { handsBackToModel, waitingAnchor } from './waiting-clock.js';
14
17
  import { ContextInjectionRow } from './native/ContextInjectionRow.js';
15
18
  import { TimelineRail } from './TimelineRail.js';
16
19
  import { landTurn, scrollerOf } from './conversation-scroll.js';
17
20
  import { mergeTimelineItems } from './timeline.js';
18
21
  import { presentLiveTurn, segmentLiveTurn } from './live-turn.js';
22
+ import { frostedGlassOf } from './fold-intensity.js';
19
23
  import { ChoreographedFlow, useFlowChat } from './ChoreographedFlow.js';
20
24
  import { ClosedProcessSummary } from './ClosedProcessSummary.js';
21
25
  import { StickyLane } from './StickyLane.js';
@@ -111,7 +115,7 @@ const AssistantNode = memo(function AssistantNode({ useChat, nodeKey, boundary,
111
115
  // of its own; passing it would fork the whole session instead.
112
116
  const anchor = forkAnchorSeq([data.finalNode, { seq: render.forkSeq }]);
113
117
  return render.forkAt && anchor !== undefined ? () => render.forkAt(anchor) : undefined;
114
- })(), metrics: render.metrics }))] }) }, part.start);
118
+ })(), metrics: render.metrics, extraActions: _jsx(OfficialActions, { official: render.official, messageId: data.finalNode?.messageId }) }))] }) }, part.start);
115
119
  }) });
116
120
  });
117
121
  const MainNode = memo(function MainNode({ useChat, nodeKey, boundary, pinned, processOpen = false, ...render }) {
@@ -137,6 +141,8 @@ const MainNode = memo(function MainNode({ useChat, nodeKey, boundary, pinned, pr
137
141
  if (isNode(node, 'model-retry'))
138
142
  return node.data.current.retryState === 'scheduled'
139
143
  ? _jsx("div", { className: css.notice, role: "status", children: "\u6A21\u578B\u8BF7\u6C42\u672A\u6210\u529F\uFF0C\u6B63\u5728\u7B49\u5F85\u91CD\u8BD5\u3002\u8BE6\u60C5\u4FDD\u7559\u5728\u6267\u884C\u8FC7\u7A0B\u4E2D\u3002" }) : null;
144
+ if (isNode(node, 'command') && render.official)
145
+ return _jsx(OfficialNode, { ...render, node: node, fallback: _jsx(JsonBlock, { label: "\u547D\u4EE4\u8BB0\u5F55", payload: node.data, truncatedLabel: truncatedJsonLabel }) });
140
146
  if (isNode(node, 'command')) {
141
147
  if (node.data.outcome?.kind === 'error')
142
148
  return _jsxs("div", { className: css.error, role: "alert", children: [_jsxs("svg", { className: css.errorIcon, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", children: [_jsx("circle", { cx: "8", cy: "8", r: "6.5", strokeWidth: "1.2" }), _jsx("path", { d: "M8 5v3.5M8 11.2h.01", strokeWidth: "1.5", strokeLinecap: "round" })] }), _jsxs("div", { className: css.errorCopy, children: [_jsx("div", { className: css.errorTitle, children: _jsx("strong", { children: "\u547D\u4EE4\u6267\u884C\u672A\u6210\u529F" }) }), _jsx("p", { className: css.errorMessage, children: node.data.outcome.text ?? node.data.name ?? '查看原对话中的命令记录' })] })] });
@@ -146,12 +152,31 @@ const MainNode = memo(function MainNode({ useChat, nodeKey, boundary, pinned, pr
146
152
  return null;
147
153
  if (node.kind === 'context' || node.kind === 'turn-tail' || node.kind === 'system-prompt' || node.kind === 'turn-process')
148
154
  return null;
149
- if (node.kind === 'command-input') {
150
- const data = node.data;
151
- return _jsxs("div", { className: css.user, "data-reader-anchor": true, "data-reader-key": nodeKey, children: [_jsx("p", { className: css.meta, children: "\u547D\u4EE4\u8F93\u5165" }), _jsx("p", { className: css.commandInput, children: data.text })] });
152
- }
153
- return _jsxs("div", { className: css.unknown, "data-reader-anchor": true, children: [_jsxs("p", { children: ["\u6B64\u8BB0\u5F55\u7C7B\u578B\u6682\u672A\u63A5\u5165\u9605\u8BFB\u9875\uFF1A", node.kind] }), _jsx(JsonBlock, { label: "\u67E5\u770B\u539F\u59CB\u8BB0\u5F55", payload: node.data, truncatedLabel: truncatedJsonLabel })] });
155
+ // No 'command-input' branch: that string is not a chat node kind in any released
156
+ // host (it appears nowhere in the installed packages), so a slash command arrives
157
+ // as 'command' above and is rendered there. Keeping the branch only made the file
158
+ // look like it handled a case that cannot occur.
159
+ return _jsx(OfficialNode, { ...render, node: node, fallback: _jsxs("div", { className: css.unknown, "data-reader-anchor": true, children: [_jsxs("p", { children: ["\u6B64\u8BB0\u5F55\u7C7B\u578B\u6682\u672A\u63A5\u5165\u9605\u8BFB\u9875\uFF1A", node.kind] }), render.official && _jsx("button", { type: "button", className: css.textButton, onClick: () => render.official.openView('chat', node.key), children: "\u5728\u5BF9\u8BDD\u4E2D\u67E5\u770B" }), _jsx(JsonBlock, { label: "\u67E5\u770B\u539F\u59CB\u8BB0\u5F55", payload: node.data, truncatedLabel: truncatedJsonLabel })] }) });
154
160
  });
161
+ /**
162
+ * Sub-agents this turn dispatched, read from the host's own Turn-process row.
163
+ *
164
+ * The host already derives this from the dispatch tree (`subagentCount` on
165
+ * `TurnProcessChatData`), so counting calls here would only risk disagreeing with
166
+ * it. The row is otherwise hidden by the reader, which is why this is the one place
167
+ * its counters are read.
168
+ */
169
+ function subagentCount(snapshot, keys) {
170
+ for (const key of keys) {
171
+ const node = snapshot.nodes.get(key);
172
+ if (!node || node.kind !== 'turn-process')
173
+ continue;
174
+ const value = node.data.subagentCount;
175
+ if (typeof value === 'number' && Number.isFinite(value) && value > 0)
176
+ return value;
177
+ }
178
+ return 0;
179
+ }
155
180
  function GroupStatus({ group, sessionId, useChat, useSessionPendingInteraction, motion }) {
156
181
  const pending = useSessionPendingInteraction(snapshot => snapshot.get(sessionId));
157
182
  const text = useChat(snapshot => {
@@ -168,8 +193,10 @@ function GroupStatus({ group, sessionId, useChat, useSessionPendingInteraction,
168
193
  return '等待你的操作';
169
194
  const current = turn.steps.at(-1)?.data.get('assistant-step');
170
195
  const last = current?.blocks.at(-1);
171
- if (current?.status === 'running' && last?.kind === 'tool-call')
172
- return preparingLabel(last.name);
196
+ if (current?.status === 'running' && last?.kind === 'tool-call') {
197
+ const spawned = subagentCount(snapshot, group.keys);
198
+ return spawned ? `${preparingLabel(last.name)}·${spawned} 个子代理` : preparingLabel(last.name);
199
+ }
173
200
  for (let index = group.keys.length - 1; index >= 0; index--) {
174
201
  const node = snapshot.nodes.get(group.keys[index]);
175
202
  if (!node)
@@ -186,7 +213,7 @@ function GroupStatus({ group, sessionId, useChat, useSessionPendingInteraction,
186
213
  const busy = useChat(snapshot => group.turn !== null && snapshot.timeline.turns.get(group.turn)?.status === 'open' && pending === undefined);
187
214
  return _jsx(StatusText, { text: text, motion: motion, shimmer: busy });
188
215
  }
189
- const DeliverableChip = memo(function DeliverableChip({ path, openFile, revealFile }) {
216
+ const DeliverableChip = memo(function DeliverableChip({ path, openFile, revealFile, openMode }) {
190
217
  const [status, setStatus] = useState('idle');
191
218
  const timer = useRef();
192
219
  const flash = (next) => {
@@ -231,9 +258,9 @@ const DeliverableChip = memo(function DeliverableChip({ path, openFile, revealFi
231
258
  };
232
259
  const name = basename(path);
233
260
  const folder = dirname(path);
234
- return (_jsxs("div", { className: css.deliverableChip, "data-status": status, title: path, children: [_jsxs("button", { type: "button", className: css.chipMain, onClick: onOpen, onDoubleClick: onOpen, "aria-label": `直接在编辑器中打开 ${path}`, children: [status === 'opened' ? (_jsx("svg", { className: css.statusIcon, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", children: _jsx("path", { d: "M3.5 8.5l3 3 6-7", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) })) : (_jsxs("svg", { className: css.deliverableIcon, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", children: [_jsx("path", { d: "M4 2.5h5l3 3V13.5H4V2.5z", strokeWidth: "1.2", strokeLinejoin: "round" }), _jsx("path", { d: "M9 2.5v3h3", strokeWidth: "1.2", strokeLinejoin: "round" })] })), _jsx("span", { className: css.deliverableName, children: status === 'opened' ? '已在外部打开' : name })] }), _jsxs("div", { className: css.chipActions, "aria-label": "\u6587\u4EF6\u64CD\u4F5C", children: [_jsx("button", { type: "button", className: css.chipActionBtn, title: `在访达中定位所在目录 (${folder})`, "aria-label": "\u5728\u8BBF\u8FBE\u4E2D\u663E\u793A\u6240\u5728\u76EE\u5F55", onClick: onReveal, children: status === 'revealed' ? (_jsx("svg", { className: css.actionIcon, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", children: _jsx("path", { d: "M3.5 8.5l3 3 6-7", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) })) : (_jsx("svg", { className: css.actionIcon, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", children: _jsx("path", { d: "M2 4.5h4l1.5 2H14v6.5H2V4.5z", strokeWidth: "1.2", strokeLinejoin: "round" }) })) }), _jsx("button", { type: "button", className: css.chipActionBtn, title: "\u590D\u5236\u76F8\u5BF9\u8DEF\u5F84", "aria-label": "\u590D\u5236\u76F8\u5BF9\u8DEF\u5F84", onClick: onCopy, children: status === 'copied' ? (_jsx("svg", { className: css.actionIcon, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", children: _jsx("path", { d: "M3.5 8.5l3 3 6-7", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) })) : (_jsxs("svg", { className: css.actionIcon, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", children: [_jsx("rect", { x: "5.5", y: "5.5", width: "8", height: "8", rx: "1.5", strokeWidth: "1.2" }), _jsx("path", { d: "M4 10.5H3a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v1", strokeWidth: "1.2", strokeLinecap: "round" })] })) })] })] }));
261
+ return (_jsxs("div", { className: css.deliverableChip, "data-status": status, title: path, children: [_jsxs("button", { type: "button", className: css.chipMain, onClick: onOpen, onDoubleClick: onOpen, "aria-label": openMode === 'sidebar' ? `在内置面板中打开 ${path}` : `用系统应用打开 ${path}`, children: [status === 'opened' ? (_jsx("svg", { className: css.statusIcon, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", children: _jsx("path", { d: "M3.5 8.5l3 3 6-7", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) })) : (_jsxs("svg", { className: css.deliverableIcon, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", children: [_jsx("path", { d: "M4 2.5h5l3 3V13.5H4V2.5z", strokeWidth: "1.2", strokeLinejoin: "round" }), _jsx("path", { d: "M9 2.5v3h3", strokeWidth: "1.2", strokeLinejoin: "round" })] })), _jsx("span", { className: css.deliverableName, children: status === 'opened' ? (openMode === 'sidebar' ? '已在面板打开' : '已在外部打开') : name })] }), _jsxs("div", { className: css.chipActions, "aria-label": "\u6587\u4EF6\u64CD\u4F5C", children: [_jsx("button", { type: "button", className: css.chipActionBtn, title: `在访达中定位所在目录 (${folder})`, "aria-label": "\u5728\u8BBF\u8FBE\u4E2D\u663E\u793A\u6240\u5728\u76EE\u5F55", onClick: onReveal, children: status === 'revealed' ? (_jsx("svg", { className: css.actionIcon, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", children: _jsx("path", { d: "M3.5 8.5l3 3 6-7", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) })) : (_jsx("svg", { className: css.actionIcon, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", children: _jsx("path", { d: "M2 4.5h4l1.5 2H14v6.5H2V4.5z", strokeWidth: "1.2", strokeLinejoin: "round" }) })) }), _jsx("button", { type: "button", className: css.chipActionBtn, title: "\u590D\u5236\u76F8\u5BF9\u8DEF\u5F84", "aria-label": "\u590D\u5236\u76F8\u5BF9\u8DEF\u5F84", onClick: onCopy, children: status === 'copied' ? (_jsx("svg", { className: css.actionIcon, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", children: _jsx("path", { d: "M3.5 8.5l3 3 6-7", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) })) : (_jsxs("svg", { className: css.actionIcon, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", children: [_jsx("rect", { x: "5.5", y: "5.5", width: "8", height: "8", rx: "1.5", strokeWidth: "1.2" }), _jsx("path", { d: "M4 10.5H3a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v1", strokeWidth: "1.2", strokeLinecap: "round" })] })) })] })] }));
235
262
  });
236
- function DeliverablesRow({ deliverables, openFile, revealFile }) {
263
+ function DeliverablesRow({ deliverables, openFile, revealFile, openMode }) {
237
264
  const [folderStatus, setFolderStatus] = useState('idle');
238
265
  const onOpenWorkspace = () => {
239
266
  try {
@@ -245,7 +272,7 @@ function DeliverablesRow({ deliverables, openFile, revealFile }) {
245
272
  // ignore
246
273
  }
247
274
  };
248
- return (_jsxs("div", { className: css.deliverablesRoot, "data-reader-deliverables": true, children: [_jsx("span", { className: css.deliverablesLabel, children: "\u4EA7\u7269" }), _jsx("div", { className: css.deliverablesLane, children: _jsxs("div", { className: css.deliverablesRow, children: [deliverables.slice(0, 8).map(path => (_jsx(DeliverableChip, { path: path, openFile: openFile, revealFile: revealFile }, path))), deliverables.length > 8 && (_jsxs("span", { className: css.deliverablesMore, children: ["+ ", deliverables.length - 8, " \u4E2A\u6587\u4EF6"] })), deliverables.length > 1 && (_jsxs("button", { type: "button", className: css.deliverablesShowFolder, "data-status": folderStatus, onClick: onOpenWorkspace, title: "\u5728\u8BBF\u8FBE\u4E2D\u6253\u5F00\u6574\u4E2A\u5DE5\u4F5C\u533A\u76EE\u5F55", children: [folderStatus === 'opened' && (_jsx("svg", { className: css.statusIcon, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", children: _jsx("path", { d: "M3.5 8.5l3 3 6-7", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) })), _jsx("span", { children: folderStatus === 'opened' ? '已打开访达' : '在文件夹中显示' })] }))] }) })] }));
275
+ return (_jsxs("div", { className: css.deliverablesRoot, "data-reader-deliverables": true, children: [_jsx("span", { className: css.deliverablesLabel, children: "\u4EA7\u7269" }), _jsx("div", { className: css.deliverablesLane, children: _jsxs("div", { className: css.deliverablesRow, children: [deliverables.slice(0, 8).map(path => (_jsx(DeliverableChip, { path: path, openFile: openFile, revealFile: revealFile, openMode: openMode }, path))), deliverables.length > 8 && (_jsxs("span", { className: css.deliverablesMore, children: ["+ ", deliverables.length - 8, " \u4E2A\u6587\u4EF6"] })), deliverables.length > 1 && (_jsxs("button", { type: "button", className: css.deliverablesShowFolder, "data-status": folderStatus, onClick: onOpenWorkspace, title: "\u5728\u8BBF\u8FBE\u4E2D\u6253\u5F00\u6574\u4E2A\u5DE5\u4F5C\u533A\u76EE\u5F55", children: [folderStatus === 'opened' && (_jsx("svg", { className: css.statusIcon, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", children: _jsx("path", { d: "M3.5 8.5l3 3 6-7", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) })), _jsx("span", { children: folderStatus === 'opened' ? '已打开访达' : '在文件夹中显示' })] }))] }) })] }));
249
276
  }
250
277
  const TurnGroup = memo(function TurnGroup({ group, motion, autoFold, pinnedKeys, selectedProcessKeys, isAwaitingModel = false, ...props }) {
251
278
  const snapshot = props.useChat(snapshot => snapshot);
@@ -266,6 +293,7 @@ const TurnGroup = memo(function TurnGroup({ group, motion, autoFold, pinnedKeys,
266
293
  const flow = useMemo(() => readerFlow({ ...group, keys: mainKeys }, turn, key => nodes.get(key)), [nodes, group, mainKeys, turn]);
267
294
  const steps = useMemo(() => segmentLiveTurn(flow, key => nodes.get(key)), [flow, nodes]);
268
295
  const liveItems = useMemo(() => presentLiveTurn(steps, boundary, autoFold), [steps, boundary, autoFold]);
296
+ const openMode = deliverableOpenModeOf(useSyncExternalStore(props.openPrefs?.subscribe ?? ((fn) => { void fn; return () => { }; }), () => props.openPrefs?.getSnapshot()?.deliverableOpenMode, () => 'external'));
269
297
  const hasProcess = flow.some(item => item.kind === 'tool' || hasProcessContent(nodes.get(item.nodeKey), boundary));
270
298
  // Only a real, still-active text selection delays folding. Merely clicking,
271
299
  // focusing or scrolling the live card does not create a permanent override.
@@ -273,10 +301,14 @@ const TurnGroup = memo(function TurnGroup({ group, motion, autoFold, pinnedKeys,
273
301
  || liveItems.some(item => item.kind === 'fold'
274
302
  ? item.key === key || item.steps.some(step => step.key === key || ('nodeKey' in step && step.nodeKey === key))
275
303
  : item.key === key || ('nodeKey' in item.step && item.step.nodeKey === key)));
276
- const expanded = holdingSelection || processExpanded(expansionChoice, boundary);
304
+ const expanded = !autoFold || holdingSelection || processExpanded(expansionChoice, boundary);
277
305
  const [foldOpenByKey, setFoldOpenByKey] = useState({});
306
+ useLayoutEffect(() => {
307
+ // Restoring automatic folding also releases live "prior steps" disclosures.
308
+ if (autoFold)
309
+ setFoldOpenByKey(current => Object.keys(current).length ? {} : current);
310
+ }, [autoFold]);
278
311
  const deliverables = useMemo(() => getTurnDeliverables(turn, flow), [turn, flow]);
279
- const fileMentions = useMemo(() => deliverables.length > 0 && props.openFile ? createProducedFileMentions(deliverables, props.openFile) : undefined, [deliverables, props.openFile]);
280
312
  const tailData = useMemo(() => {
281
313
  for (const key of group.keys) {
282
314
  const n = nodes.get(key);
@@ -285,6 +317,21 @@ const TurnGroup = memo(function TurnGroup({ group, motion, autoFold, pinnedKeys,
285
317
  }
286
318
  return undefined;
287
319
  }, [group.keys, nodes]);
320
+ const tailOwner = useMemo(() => turn && tailData ? {
321
+ turn, seq: tailData.closing?.finalNode.seq ?? tailData.seq, openFile: props.openFile,
322
+ } : undefined, [turn, tailData, props.openFile]);
323
+ const fileMentions = useMemo(() => {
324
+ const official = tailOwner ? props.officialFileMentions(tailOwner) : undefined;
325
+ const reader = deliverables.length > 0 ? createProducedFileMentions(deliverables, props.openFile) : undefined;
326
+ return official || reader ? { resolve: (value) => official?.resolve(value) ?? reader?.resolve(value) } : undefined;
327
+ }, [tailOwner, props.officialFileMentions, deliverables, props.openFile]);
328
+ const official = useMemo(() => ({
329
+ renderSlot: props.renderSlot, renderSlotChain: props.renderSlotChain,
330
+ officialImageLoader: props.officialImageLoader, officialFileMentions: props.officialFileMentions,
331
+ officialPreviewFile: props.officialPreviewFile,
332
+ officialHost: props.officialHost, openView: props.openView,
333
+ }), [props.renderSlot, props.renderSlotChain, props.officialImageLoader, props.officialFileMentions, props.officialPreviewFile, props.officialHost, props.openView]);
334
+ const cwd = props.useSessions(state => state.byId[props.sessionId]?.cwd);
288
335
  const runMs = turn?.start && turn?.end ? Math.max(0, turn.end.time - turn.start.time) : undefined;
289
336
  const metrics = useMemo(() => ({
290
337
  usage: tailData?.tokenUsage,
@@ -315,10 +362,18 @@ const TurnGroup = memo(function TurnGroup({ group, motion, autoFold, pinnedKeys,
315
362
  forkSeq,
316
363
  fileMentions,
317
364
  metrics,
365
+ official,
366
+ cwd,
318
367
  };
319
368
  const terminal = terminalLabel(boundary.reason);
320
369
  const hasTurnError = flow.some(item => item.kind === 'node' && nodes.get(item.nodeKey)?.kind === 'turn-error');
321
- const showTerminalNotice = terminal && !hasTurnError && boundary.reason !== 'interrupted' && boundary.reason !== 'aborted';
370
+ // A stopped turn normally stays quiet, but a stopped turn that produced no
371
+ // prose at all reads as if its answer vanished into the process fold. Say so.
372
+ const hasAnswerProse = steps.some(step => step.kind === 'body'
373
+ && step.blocks.some(block => block.kind === 'text' && block.text.trim() !== ''));
374
+ const stoppedWithoutAnswer = (boundary.reason === 'interrupted' || boundary.reason === 'aborted') && !hasAnswerProse;
375
+ const showTerminalNotice = terminal && !hasTurnError
376
+ && (stoppedWithoutAnswer || (boundary.reason !== 'interrupted' && boundary.reason !== 'aborted'));
322
377
  const renderStep = (step, folded) => {
323
378
  const processOpen = folded || expanded;
324
379
  if (step.kind === 'reasoning' || step.kind === 'body')
@@ -329,7 +384,7 @@ const TurnGroup = memo(function TurnGroup({ group, motion, autoFold, pinnedKeys,
329
384
  return _jsx(BlockBoundary, { children: _jsx(MainNode, { ...shared, boundary: boundary, nodeKey: step.nodeKey }) });
330
385
  return _jsxs(Fragment, { children: [_jsx(BlockBoundary, { children: _jsx(ProcessNode, { useChat: useFlowChat, t: props.t, nodeKey: step.nodeKey, open: processOpen, motion: motion, onRead: pinProcess, returnFocusTo: processButton }) }), _jsx(BlockBoundary, { children: _jsx(MainNode, { ...shared, boundary: boundary, nodeKey: step.nodeKey, pinned: pinnedKeys.includes(step.nodeKey), processOpen: processOpen }) })] });
331
386
  };
332
- return _jsxs("section", { className: css.turn, "data-reader-turn": group.turn ?? 'unresolved', "data-reader-turn-state": boundary.status, "data-reader-turn-result": boundary.reason ?? undefined, children: [startsWithUser && _jsx(BlockBoundary, { children: _jsx(MainNode, { ...shared, useChat: props.useChat, boundary: boundary, nodeKey: group.keys[0] }) }), hasProcess && !isAwaitingModel && _jsx(StickyLane, { kind: "status", className: css.turnProcessSticky, children: _jsx(Disclosure, { open: expanded, onChange: setExpanded, controls: flowId, buttonRef: processButton, label: _jsx(GroupStatus, { group: group, sessionId: props.sessionId, useChat: props.useChat, useSessionPendingInteraction: props.useSessionPendingInteraction, motion: motion }) }) }), boundary.status === 'closed' && hasProcess && _jsx(ClosedProcessSummary, { open: expanded, onChange: setExpanded, controls: flowId, steps: steps.filter(step => {
387
+ return _jsxs("section", { className: css.turn, "data-reader-turn": group.turn ?? 'unresolved', "data-reader-turn-state": boundary.status, "data-reader-turn-result": boundary.reason ?? undefined, children: [startsWithUser && _jsx(BlockBoundary, { children: _jsx(MainNode, { ...shared, useChat: props.useChat, boundary: boundary, nodeKey: group.keys[0] }) }), hasProcess && !isAwaitingModel && _jsx(StickyLane, { kind: "status", className: css.turnProcessSticky, children: _jsx(Disclosure, { open: expanded, onChange: setExpanded, controls: flowId, buttonRef: processButton, label: _jsx(GroupStatus, { group: group, sessionId: props.sessionId, useChat: props.useChat, useSessionPendingInteraction: props.useSessionPendingInteraction, motion: motion }) }) }), boundary.status === 'closed' && hasProcess && autoFold && _jsx(ClosedProcessSummary, { open: expanded, onChange: setExpanded, controls: flowId, steps: steps.filter(step => {
333
388
  if (step.kind === 'user')
334
389
  return false;
335
390
  if (step.kind !== 'body')
@@ -338,7 +393,7 @@ const TurnGroup = memo(function TurnGroup({ group, motion, autoFold, pinnedKeys,
338
393
  return !!node && isNode(node, 'assistant-step') && (isEarlierNarration(node.data, boundary)
339
394
  || node.data.blocks.some(block => block.kind === 'tool-call')
340
395
  || (boundary.latestStep > 0 && node.data.step < boundary.latestStep));
341
- }) }), _jsx(ChoreographedFlow, { id: flowId, frame: presentation, motion: motion, enabled: autoFold && boundary.status === 'open' && !holdingSelection, urgent: hasTurnError || interaction !== undefined || !sessionRunning, open: foldOpenByKey, processOpen: expanded, onOpenChange: (key, value) => { pinProcess(); setFoldOpenByKey(current => ({ ...current, [key]: value })); }, renderStep: renderStep }), !hasProcess && boundary.status === 'open' && !isAwaitingModel && _jsx("div", { className: css.disclosure, "data-reader-status-only": true, children: _jsx(GroupStatus, { group: group, sessionId: props.sessionId, useChat: props.useChat, useSessionPendingInteraction: props.useSessionPendingInteraction, motion: motion }) }), showDeliverablesRow(boundary.status, deliverables) && _jsx(DeliverablesRow, { deliverables: deliverables, openFile: props.openFile, revealFile: props.revealFile }), showTerminalNotice && _jsx("div", { className: css.notice, "data-reader-terminal": true, children: terminal })] });
396
+ }) }), _jsx(ChoreographedFlow, { id: flowId, frame: presentation, motion: motion, enabled: autoFold && boundary.status === 'open' && !holdingSelection, urgent: hasTurnError || interaction !== undefined || !sessionRunning, open: foldOpenByKey, processOpen: expanded, onOpenChange: (key, value) => { pinProcess(); setFoldOpenByKey(current => ({ ...current, [key]: value })); }, renderStep: renderStep }), !hasProcess && boundary.status === 'open' && !isAwaitingModel && _jsx("div", { className: css.disclosure, "data-reader-status-only": true, children: _jsx(GroupStatus, { group: group, sessionId: props.sessionId, useChat: props.useChat, useSessionPendingInteraction: props.useSessionPendingInteraction, motion: motion }) }), showDeliverablesRow(boundary.status, deliverables) && _jsx(DeliverablesRow, { deliverables: deliverables, openFile: props.openFile, revealFile: props.revealFile, openMode: openMode }), boundary.status === 'closed' && _jsx(BlockBoundary, { children: _jsx(OfficialTail, { official: official, owner: tailOwner, produced: deliverables }) }), showTerminalNotice && _jsx("div", { className: css.notice, "data-reader-terminal": true, children: terminal })] });
342
397
  });
343
398
  export function Reader(props) {
344
399
  const root = useRef(null);
@@ -353,51 +408,56 @@ export function Reader(props) {
353
408
  const hasMore = props.useSession(snapshot => snapshot.hasMore);
354
409
  const loadingOlder = props.useSession(snapshot => snapshot.loadingOlder);
355
410
  const pendingSubmissions = props.useSession(snapshot => snapshot.pendingSubmissions);
356
- const waitAnchor = waitingAnchor(order, key => nodes.get(key), pendingSubmissions);
411
+ const pendingList = asReadonlyArray(pendingSubmissions);
412
+ const waitAnchor = waitingAnchor(order, key => nodes.get(key), pendingList);
357
413
  const motionPreference = props.useStore(state => state.motion);
358
414
  const motion = useMotionAllowed(motionPreference);
359
- const autoFold = props.useStore(state => state.autoFold ?? true);
415
+ const prefsSnap = useSyncExternalStore(props.openPrefs?.subscribe ?? ((fn) => { void fn; return () => { }; }), () => props.openPrefs?.getSnapshot?.(), () => undefined);
416
+ const storeAutoFold = props.useStore(state => state.autoFold);
417
+ const autoFold = prefsSnap?.autoFold ?? (prefsSnap?.foldIntensity !== undefined ? prefsSnap.foldIntensity !== 0 : undefined) ?? storeAutoFold ?? true;
418
+ const previousAutoFold = useRef(autoFold);
419
+ useLayoutEffect(() => {
420
+ const restored = autoFold && !previousAutoFold.current;
421
+ previousAutoFold.current = autoFold;
422
+ // Watch the effective preference so toolbar and Settings changes behave alike.
423
+ // Reading while folding is off must not pin completed process content forever.
424
+ if (restored)
425
+ props.actions.resetExpanded();
426
+ }, [autoFold, props.actions]);
427
+ const frostedGlass = frostedGlassOf(prefsSnap);
360
428
  const streamMotion = useMemo(() => ({ enabled: motion, activatedAt: activatedAt.current }), [motion]);
361
429
  const groups = useMemo(() => groupNodes(order, key => nodes.get(key)), [order, nodes, timeline]);
362
430
  const isAwaitingModel = useMemo(() => {
363
431
  // 状态机铁律:「深度求索中」与「正在处理/思考」永远互斥。
364
432
  // 1. 仅空闲发送(!running)的本地回显窗口:用户按下回车第 0 毫秒立即反馈。
365
433
  // 忙碌时的 queued/steering 回显不算等待——模型本来就在工作。
366
- if (!running && pendingSubmissions && pendingSubmissions.length > 0)
434
+ if (!running && pendingList.length > 0)
367
435
  return true;
368
436
  const lastKey = order.at(-1);
369
437
  const lastNode = lastKey ? nodes.get(lastKey) : undefined;
370
- const lastIsUser = lastNode?.kind === 'user' || lastNode?.kind === 'steering';
371
- // 2. 最后一个节点是用户发言(含补发消息):检查该用户节点**之后**是否已有模型产出。
372
- // 之前用 slice(1) 是 Bug——补发消息在组中部,前面的旧输出会让等待永远不出现。
373
- if (lastIsUser && lastKey) {
374
- const lastGroup = groups.at(-1);
375
- const turn = lastGroup?.turn === null || lastGroup?.turn === undefined ? undefined : timeline.turns.get(lastGroup.turn);
376
- if (turn?.status === 'closed')
377
- return false;
378
- const keys = lastGroup?.keys ?? [];
379
- const userIdx = keys.lastIndexOf(lastKey);
380
- const hasOutputAfter = keys.slice(userIdx + 1).some(key => {
381
- const node = nodes.get(key);
382
- if (!node || node.visibility === 'hidden')
383
- return false;
384
- if (node.kind === 'assistant-step') {
385
- const data = node.data;
386
- return Array.isArray(data.blocks) && data.blocks.length > 0;
387
- }
388
- return node.kind === 'tool-call' || node.kind === 'tool-return' || node.kind === 'command-input';
389
- });
390
- return !hasOutputAfter;
391
- }
392
- // 3. assistant-step 已创建但一个 block 都没有:模型收到请求、内容未吐出的瞬间。
393
- if (lastNode?.kind === 'assistant-step') {
438
+ if (!lastKey || !lastNode)
439
+ return false;
440
+ // 2. 模型已经产出内容:等待结束,计时器必须立刻消失。
441
+ // 「产出」= assistant-step 已带 block(正文/思维链/工具调用都算)。
442
+ if (lastNode.kind === 'assistant-step') {
394
443
  const data = lastNode.data;
395
- if (data.status === 'running' && (!data.blocks || data.blocks.length === 0)) {
396
- return true;
397
- }
444
+ const blocks = data.blocks ?? [];
445
+ // 空 block 且仍在跑 = 请求已发出、模型还没吐字,正是要计时的那一刻。
446
+ return blocks.length === 0 && data.status === 'running';
398
447
  }
399
- return false;
400
- }, [running, pendingSubmissions, order, nodes, groups, timeline]);
448
+ // 3. 球在模型脚下:用户刚发言,或工具已返回、上下文已注入、命令已执行。
449
+ // 这些时刻模型随时可能卡住,正是要计时的地方;工具执行期间不计时,
450
+ // 因为那时忙的是工具而不是模型。
451
+ // handsBackToModel 内部按 chat 层 kind 判定,并区分「工具已返回」与
452
+ // 「工具仍在跑」——后者不算等待。
453
+ const modelOwesResponse = handsBackToModel(lastNode);
454
+ if (!modelOwesResponse)
455
+ return false;
456
+ // 轮次已结束就没什么可等的。
457
+ const lastGroup = groups.at(-1);
458
+ const turn = lastGroup?.turn === null || lastGroup?.turn === undefined ? undefined : timeline.turns.get(lastGroup.turn);
459
+ return turn?.status !== 'closed';
460
+ }, [running, pendingList, order, nodes, groups, timeline]);
401
461
  const scroll = useReadingScroll(root, motion);
402
462
  const pinnedKeys = usePinnedSelection(root);
403
463
  const selectedProcessKeys = usePinnedSelection(root, '[data-reader-process]');
@@ -505,7 +565,7 @@ export function Reader(props) {
505
565
  }, [props.loadThrough, props.loadOlder, scroll.jump, scroll.release, timelineItems]);
506
566
  const lastKey = order.at(-1);
507
567
  const lastNode = lastKey ? nodes.get(lastKey) : undefined;
508
- const lastSubmissionId = pendingSubmissions?.length ? pendingSubmissions[pendingSubmissions.length - 1].requestId : null;
568
+ const lastSubmissionId = pendingList.length ? pendingList[pendingList.length - 1].requestId : null;
509
569
  const lastOrderKeyRef = useRef(lastKey);
510
570
  const lastSubmissionRef = useRef(lastSubmissionId);
511
571
  useLayoutEffect(() => {
@@ -518,13 +578,16 @@ export function Reader(props) {
518
578
  }
519
579
  }, [lastKey, lastNode?.kind, lastSubmissionId, scroll]);
520
580
  const visibleSubmissions = useMemo(() => {
521
- if (!pendingSubmissions || pendingSubmissions.length === 0)
581
+ if (pendingList.length === 0)
522
582
  return [];
523
- return pendingSubmissions.filter(sub => sub.placement !== 'queued');
524
- }, [pendingSubmissions]);
583
+ return pendingList.filter(sub => sub.placement !== 'queued');
584
+ }, [pendingList]);
525
585
  // ChatView publishes data-chat-flow="" on its column. Skins treat a
526
586
  // scrollport without that hook as inspect-only and hide [data-composer-seat].
527
- return _jsx(StreamMotionContext.Provider, { value: streamMotion, children: _jsxs("div", { ref: root, className: css.root, "data-dsh-better-display": "0.1.1", "data-reader-wait-clock-version": "input-v1", "data-reader-wait-start": waitAnchor.time ?? undefined, "data-motion": motion ? 'on' : 'off', children: [_jsx(TimelineRail, { items: timelineItems, activeTurn: activeTurn, busyTurn: busyTurn, onNavigate: onNavigateTurn }), _jsxs("div", { className: css.column, "data-chat-flow": "", children: [_jsx(StickyLane, { kind: "toolbar", className: css.toolbar, children: _jsx("button", { type: "button", className: css.textButton, "aria-pressed": autoFold, onClick: () => props.actions.setAutoFold(!autoFold), title: "\u65B0\u601D\u8003\u4EA7\u751F\u65F6\uFF0C\u662F\u5426\u81EA\u52A8\u5C06\u6B64\u524D\u6B65\u9AA4\u6536\u62E2\u4E3A\u4E00\u884C\u6C47\u603B\u3002\u5173\u95ED\u540E\u5B8C\u6574\u4FDD\u7559\u539F\u59CB\u8FC7\u7A0B\u4E0E\u6D41\u5F0F\u8F93\u51FA\u3002", children: `自动折叠${autoFold ? '开' : '关'}` }) }), hasMore && _jsx("button", { type: "button", className: css.historyButton, disabled: loadingOlder, onClick: async () => {
587
+ return _jsx(StreamMotionContext.Provider, { value: streamMotion, children: _jsxs("div", { ref: root, className: css.root, "data-reader-build": "0.3.0-preview.1", "data-dsh-better-display": "0.3.0-preview.1", "data-reader-wait-clock-version": "input-v1", "data-reader-wait-start": waitAnchor.time ?? undefined, "data-motion": motion ? 'on' : 'off', "data-reader-glass": frostedGlass || undefined, "data-reader-auto-fold": autoFold ? 'on' : 'off', children: [_jsx(TimelineRail, { items: timelineItems, activeTurn: activeTurn, busyTurn: busyTurn, onNavigate: onNavigateTurn }), _jsxs("div", { className: css.column, "data-chat-flow": "", children: [_jsx(StickyLane, { kind: "toolbar", className: css.toolbar, children: _jsx("button", { type: "button", className: css.textButton, "aria-pressed": autoFold, onClick: () => {
588
+ props.actions.setAutoFold(!autoFold);
589
+ props.openPrefs?.actions?.setAutoFold?.(!autoFold);
590
+ }, title: "\u65B0\u601D\u8003\u4EA7\u751F\u65F6\uFF0C\u662F\u5426\u81EA\u52A8\u5C06\u6B64\u524D\u6B65\u9AA4\u6536\u62E2\u4E3A\u4E00\u884C\u6C47\u603B\u3002\u5173\u95ED\u540E\u5B8C\u6574\u4FDD\u7559\u539F\u59CB\u8FC7\u7A0B\u4E0E\u6D41\u5F0F\u8F93\u51FA\u3002", children: `自动折叠${autoFold ? '开' : '关'}` }) }), hasMore && _jsx("button", { type: "button", className: css.historyButton, disabled: loadingOlder, onClick: async () => {
528
591
  setHistoryError(false);
529
592
  try {
530
593
  await props.loadOlder();
@@ -532,6 +595,9 @@ export function Reader(props) {
532
595
  catch {
533
596
  setHistoryError(true);
534
597
  }
535
- }, children: loadingOlder ? '正在加载更早记录' : '加载更早记录' }), historyError && _jsx("div", { className: css.notice, children: "\u5386\u53F2\u8BB0\u5F55\u52A0\u8F7D\u5931\u8D25\uFF0C\u53EF\u518D\u6B21\u5C1D\u8BD5\uFF1B\u73B0\u6709\u5185\u5BB9\u672A\u6539\u53D8\u3002" }), openError && _jsxs("div", { className: css.error, role: "alert", children: ["\u4F1A\u8BDD\u6682\u65F6\u65E0\u6CD5\u8BFB\u53D6\uFF1A", openError.message] }), loading && groups.length === 0 && _jsx("p", { className: css.empty, role: "status", children: "\u6B63\u5728\u8BFB\u53D6\u4F1A\u8BDD\u2026" }), groups.map(group => _jsx(TurnGroup, { ...props, group: group, motion: motion, autoFold: autoFold, pinnedKeys: pinnedKeys, selectedProcessKeys: selectedProcessKeys, isAwaitingModel: isAwaitingModel && group.key === groups.at(-1)?.key }, group.key)), visibleSubmissions.map(submission => (_jsxs("div", { className: css.userCluster, "data-reader-pending-submission": true, children: [submission.attachments.some(item => item.type === 'image') && (_jsx("div", { className: css.userImages, children: submission.attachments.map((item, idx) => item.type === 'image' ? (_jsx("figure", { className: css.imageFigure, children: _jsx("div", { className: css.imageFrame, style: { aspectRatio: `${item.value.width || 4} / ${item.value.height || 3}` }, children: _jsx("img", { src: item.value.previewUrl, alt: item.value.name ?? '发送的图片', className: css.pendingImage }) }) }, idx)) : null) })), submission.text ? (_jsx("div", { className: css.user, children: _jsx("div", { className: css.blocks, children: submission.text }) })) : null, _jsx(UserMessageActions, { text: submission.text, time: submission.time })] }, submission.requestId))), isAwaitingModel && _jsx(WaitingStatus, { anchor: waitAnchor, label: props.t ? props.t('chat.deepDiving') : '深度求索中...' }), pending !== undefined && _jsxs("div", { className: css.attention, role: "alert", "data-reader-attention": true, children: [_jsx("strong", { children: pending.kind === 'question' ? '需要你回答一个问题' : '需要你的确认' }), _jsx("span", { children: "\u8BF7\u5728\u4E0B\u65B9\u539F\u751F\u64CD\u4F5C\u533A\u5904\u7406\u3002\u6B64\u63D0\u793A\u4E0D\u4F1A\u6536\u8FDB\u6267\u884C\u8FC7\u7A0B\u3002" })] }), scroll.detached && _jsx("div", { className: css.jumpDock, children: _jsx("button", { type: "button", className: css.jump, "aria-label": "\u56DE\u5230\u5E95\u90E8", title: "\u56DE\u5230\u5E95\u90E8", onClick: scroll.jump, children: _jsx("svg", { viewBox: "0 0 14 14", width: "14", height: "14", "aria-hidden": "true", children: _jsx("path", { d: "M3 5.5 7 9.5 11 5.5", fill: "none", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" }) }) }) })] })] }) });
598
+ }, children: loadingOlder ? '正在加载更早记录' : '加载更早记录' }), historyError && _jsx("div", { className: css.notice, children: "\u5386\u53F2\u8BB0\u5F55\u52A0\u8F7D\u5931\u8D25\uFF0C\u53EF\u518D\u6B21\u5C1D\u8BD5\uFF1B\u73B0\u6709\u5185\u5BB9\u672A\u6539\u53D8\u3002" }), openError && _jsxs("div", { className: css.error, role: "alert", children: ["\u4F1A\u8BDD\u6682\u65F6\u65E0\u6CD5\u8BFB\u53D6\uFF1A", openError.message] }), loading && groups.length === 0 && _jsx("p", { className: css.empty, role: "status", children: "\u6B63\u5728\u8BFB\u53D6\u4F1A\u8BDD\u2026" }), groups.map(group => _jsx(TurnGroup, { ...props, group: group, motion: motion, autoFold: autoFold, pinnedKeys: pinnedKeys, selectedProcessKeys: selectedProcessKeys, isAwaitingModel: isAwaitingModel && group.key === groups.at(-1)?.key }, group.key)), visibleSubmissions.map(submission => {
599
+ const images = pendingSubmissionImages(submission);
600
+ return (_jsxs("div", { className: css.userCluster, "data-reader-pending-submission": true, children: [images.length > 0 && (_jsx("div", { className: css.userImages, children: images.map((item, idx) => (_jsx("figure", { className: css.imageFigure, children: _jsx("div", { className: css.imageFrame, style: { aspectRatio: `${item.width || 4} / ${item.height || 3}` }, children: _jsx("img", { src: item.previewUrl, alt: item.name ?? '发送的图片', className: css.pendingImage }) }) }, idx))) })), submission.text ? (_jsx("div", { className: css.user, children: _jsx("div", { className: css.blocks, children: submission.text }) })) : null, _jsx(UserMessageActions, { text: submission.text ?? '', time: submission.time })] }, submission.requestId));
601
+ }), isAwaitingModel && _jsx(WaitingStatus, { anchor: waitAnchor, label: props.t ? props.t('chat.deepDiving') : '深度求索中...' }), pending !== undefined && _jsxs("div", { className: css.attention, role: "alert", "data-reader-attention": true, children: [_jsx("strong", { children: pending.kind === 'question' ? '需要你回答一个问题' : '需要你的确认' }), _jsx("span", { children: "\u8BF7\u5728\u4E0B\u65B9\u539F\u751F\u64CD\u4F5C\u533A\u5904\u7406\u3002\u6B64\u63D0\u793A\u4E0D\u4F1A\u6536\u8FDB\u6267\u884C\u8FC7\u7A0B\u3002" })] }), scroll.detached && _jsx("div", { className: css.jumpDock, children: _jsx("button", { type: "button", className: css.jump, "aria-label": "\u56DE\u5230\u5E95\u90E8", title: "\u56DE\u5230\u5E95\u90E8", onClick: scroll.jump, children: _jsx("svg", { viewBox: "0 0 14 14", width: "14", height: "14", "aria-hidden": "true", children: _jsx("path", { d: "M3 5.5 7 9.5 11 5.5", fill: "none", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" }) }) }) })] })] }) });
536
602
  }
537
603
  //# sourceMappingURL=Reader.js.map