dsh-better-display 0.1.1 → 0.2.1
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/CHANGELOG.md +177 -4
- package/README.en.md +17 -3
- package/README.md +17 -3
- package/lib/client.js +42319 -41533
- package/lib/client.js.map +1 -1
- package/lib/dsh-better-display.js +51 -55
- package/lib/types/client/Blocks.d.ts +4 -1
- package/lib/types/client/Blocks.d.ts.map +1 -1
- package/lib/types/client/Blocks.js +11 -4
- package/lib/types/client/Blocks.js.map +1 -1
- package/lib/types/client/ChoreographedFlow.d.ts.map +1 -1
- package/lib/types/client/ChoreographedFlow.js +68 -20
- package/lib/types/client/ChoreographedFlow.js.map +1 -1
- package/lib/types/client/ClosedProcessSummary.d.ts.map +1 -1
- package/lib/types/client/ClosedProcessSummary.js +3 -2
- package/lib/types/client/ClosedProcessSummary.js.map +1 -1
- package/lib/types/client/DiffPanel.d.ts +13 -0
- package/lib/types/client/DiffPanel.d.ts.map +1 -0
- package/lib/types/client/DiffPanel.js +115 -0
- package/lib/types/client/DiffPanel.js.map +1 -0
- package/lib/types/client/Reader.d.ts.map +1 -1
- package/lib/types/client/Reader.js +88 -54
- package/lib/types/client/Reader.js.map +1 -1
- package/lib/types/client/ReasoningCard.d.ts.map +1 -1
- package/lib/types/client/ReasoningCard.js +29 -8
- package/lib/types/client/ReasoningCard.js.map +1 -1
- package/lib/types/client/SettingsSection.d.ts +35 -0
- package/lib/types/client/SettingsSection.d.ts.map +1 -0
- package/lib/types/client/SettingsSection.js +62 -0
- package/lib/types/client/SettingsSection.js.map +1 -0
- package/lib/types/client/StickyLane.d.ts.map +1 -1
- package/lib/types/client/StickyLane.js +16 -5
- package/lib/types/client/StickyLane.js.map +1 -1
- package/lib/types/client/ToolActivity.d.ts.map +1 -1
- package/lib/types/client/ToolActivity.js +53 -5
- package/lib/types/client/ToolActivity.js.map +1 -1
- package/lib/types/client/WaitClock.d.ts +16 -0
- package/lib/types/client/WaitClock.d.ts.map +1 -0
- package/lib/types/client/WaitClock.js +29 -0
- package/lib/types/client/WaitClock.js.map +1 -0
- package/lib/types/client/WaitingClock.d.ts +15 -0
- package/lib/types/client/WaitingClock.d.ts.map +1 -0
- package/lib/types/client/WaitingClock.js +30 -0
- package/lib/types/client/WaitingClock.js.map +1 -0
- package/lib/types/client/WaitingStatus.d.ts.map +1 -1
- package/lib/types/client/WaitingStatus.js +4 -14
- package/lib/types/client/WaitingStatus.js.map +1 -1
- package/lib/types/client/fold-choreography.d.ts +12 -0
- package/lib/types/client/fold-choreography.d.ts.map +1 -1
- package/lib/types/client/fold-choreography.js +18 -1
- package/lib/types/client/fold-choreography.js.map +1 -1
- package/lib/types/client/fold-intensity.d.ts +9 -0
- package/lib/types/client/fold-intensity.d.ts.map +1 -0
- package/lib/types/client/fold-intensity.js +25 -0
- package/lib/types/client/fold-intensity.js.map +1 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +68 -16
- package/lib/types/client/index.js.map +1 -1
- package/lib/types/client/live-turn.d.ts.map +1 -1
- package/lib/types/client/live-turn.js +46 -0
- package/lib/types/client/live-turn.js.map +1 -1
- package/lib/types/client/motion.d.ts.map +1 -1
- package/lib/types/client/motion.js +89 -12
- package/lib/types/client/motion.js.map +1 -1
- package/lib/types/client/open-file.d.ts +6 -1
- package/lib/types/client/open-file.d.ts.map +1 -0
- package/lib/types/client/open-file.js +71 -0
- package/lib/types/client/open-file.js.map +1 -0
- package/lib/types/client/pending-submission.d.ts +28 -0
- package/lib/types/client/pending-submission.d.ts.map +1 -0
- package/lib/types/client/pending-submission.js +28 -0
- package/lib/types/client/pending-submission.js.map +1 -0
- package/lib/types/client/reasoning-follow.d.ts +5 -1
- package/lib/types/client/reasoning-follow.d.ts.map +1 -1
- package/lib/types/client/reasoning-follow.js +13 -4
- package/lib/types/client/reasoning-follow.js.map +1 -1
- package/lib/types/client/resource-address.d.ts +3 -0
- package/lib/types/client/resource-address.d.ts.map +1 -0
- package/lib/types/client/resource-address.js +33 -0
- package/lib/types/client/resource-address.js.map +1 -0
- package/lib/types/client/settings-copy.d.ts +3 -2
- package/lib/types/client/settings-copy.d.ts.map +1 -0
- package/lib/types/client/settings-copy.js +49 -0
- package/lib/types/client/settings-copy.js.map +1 -0
- package/lib/types/client/settings-slots.d.ts +7 -0
- package/lib/types/client/settings-slots.d.ts.map +1 -0
- package/lib/types/client/settings-slots.js +2 -0
- package/lib/types/client/settings-slots.js.map +1 -0
- package/lib/types/client/settings.d.ts +4 -0
- package/lib/types/client/settings.d.ts.map +1 -0
- package/lib/types/client/settings.js +60 -0
- package/lib/types/client/settings.js.map +1 -0
- package/lib/types/client/skill-status.d.ts +25 -0
- package/lib/types/client/skill-status.d.ts.map +1 -0
- package/lib/types/client/skill-status.js +44 -0
- package/lib/types/client/skill-status.js.map +1 -0
- package/lib/types/client/store.d.ts +14 -0
- package/lib/types/client/store.d.ts.map +1 -1
- package/lib/types/client/store.js +23 -2
- package/lib/types/client/store.js.map +1 -1
- package/lib/types/client/stream-buffer.d.ts +11 -3
- package/lib/types/client/stream-buffer.d.ts.map +1 -1
- package/lib/types/client/stream-buffer.js +39 -2
- package/lib/types/client/stream-buffer.js.map +1 -1
- package/lib/types/client/streaming.d.ts.map +1 -1
- package/lib/types/client/streaming.js +9 -2
- package/lib/types/client/streaming.js.map +1 -1
- package/lib/types/client/tool-activity.d.ts +46 -0
- package/lib/types/client/tool-activity.d.ts.map +1 -1
- package/lib/types/client/tool-activity.js +116 -0
- package/lib/types/client/tool-activity.js.map +1 -1
- package/lib/types/client/types.d.ts +15 -2
- package/lib/types/client/types.d.ts.map +1 -1
- package/lib/types/client/waiting-clock.d.ts +34 -2
- package/lib/types/client/waiting-clock.d.ts.map +1 -1
- package/lib/types/client/waiting-clock.js +62 -6
- package/lib/types/client/waiting-clock.js.map +1 -1
- package/lib/types/client/word-timeline.d.ts +4 -0
- package/lib/types/client/word-timeline.d.ts.map +1 -1
- package/lib/types/client/word-timeline.js +21 -2
- package/lib/types/client/word-timeline.js.map +1 -1
- package/lib/types/dsh-better-display.d.ts.map +1 -1
- package/lib/types/dsh-better-display.js +42 -2
- package/lib/types/dsh-better-display.js.map +1 -1
- package/lib/types/skill-roots.d.ts +22 -0
- package/lib/types/skill-roots.d.ts.map +1 -0
- package/lib/types/skill-roots.js +126 -0
- package/lib/types/skill-roots.js.map +1 -0
- package/lib/types/skill-status.d.ts +36 -0
- package/lib/types/skill-status.d.ts.map +1 -0
- package/lib/types/skill-status.js +51 -0
- package/lib/types/skill-status.js.map +1 -0
- package/package.json +1 -1
- package/src/client/Blocks.tsx +11 -4
- package/src/client/ChoreographedFlow.tsx +68 -18
- package/src/client/ClosedProcessSummary.tsx +6 -2
- package/src/client/DiffPanel.tsx +124 -0
- package/src/client/Reader.module.css +319 -6
- package/src/client/Reader.tsx +78 -42
- package/src/client/ReasoningCard.tsx +27 -6
- package/src/client/SettingsSection.module.css +37 -0
- package/src/client/SettingsSection.tsx +64 -6
- package/src/client/StickyLane.tsx +13 -3
- package/src/client/ToolActivity.tsx +70 -5
- package/src/client/WaitClock.tsx +33 -0
- package/src/client/WaitingStatus.tsx +6 -15
- package/src/client/fold-choreography.ts +18 -1
- package/src/client/fold-intensity.ts +28 -0
- package/src/client/index.tsx +28 -2
- package/src/client/live-turn.ts +43 -0
- package/src/client/motion.tsx +80 -15
- package/src/client/reasoning-follow.ts +20 -4
- package/src/client/settings-copy.ts +21 -0
- package/src/client/store.ts +40 -2
- package/src/client/stream-buffer.ts +38 -2
- package/src/client/streaming.tsx +9 -1
- package/src/client/tool-activity.ts +139 -0
- package/src/client/types.ts +18 -3
- package/src/client/waiting-clock.ts +64 -6
- package/src/client/word-timeline.ts +19 -2
- package/tests/fold-choreography.test.ts +16 -0
- package/tests/fold-intensity.test.ts +94 -0
- package/tests/footer-precedence.test.ts +129 -0
- package/tests/open-file.test.ts +11 -0
- package/tests/stock-install.test.ts +8 -0
- package/tests/stream-buffer.test.ts +8 -3
- package/tests/tool-diff.test.ts +57 -0
- package/tests/waiting-clock.test.ts +78 -19
- 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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Reader.d.ts","sourceRoot":"","sources":["../../../src/client/Reader.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Reader.d.ts","sourceRoot":"","sources":["../../../src/client/Reader.tsx"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAoB,WAAW,EAAE,MAAM,YAAY,CAAC;AA4jBhE,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,+BAyPxC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
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';
|
|
@@ -9,13 +9,16 @@ import { Disclosure, ProcessFragment, RetiringContent, StatusText, useMotionAllo
|
|
|
9
9
|
import { StreamMotionContext } from './streaming.js';
|
|
10
10
|
import { assistantSegments, boundaryOf, forkAnchorSeq, groupNodes, hasProcessContent, hasVisibleBody, isEarlierNarration, processChoiceKey, processExpanded, terminalLabel } from './projection.js';
|
|
11
11
|
import { basename, createProducedFileMentions, dirname, getTurnDeliverables, showDeliverablesRow } from './deliverables.js';
|
|
12
|
+
import { deliverableOpenModeOf } from './open-file.js';
|
|
13
|
+
import { asReadonlyArray, pendingSubmissionImages } from './pending-submission.js';
|
|
12
14
|
import { WaitingStatus } from './WaitingStatus.js';
|
|
13
|
-
import { waitingAnchor } from './waiting-clock.js';
|
|
15
|
+
import { handsBackToModel, waitingAnchor } from './waiting-clock.js';
|
|
14
16
|
import { ContextInjectionRow } from './native/ContextInjectionRow.js';
|
|
15
17
|
import { TimelineRail } from './TimelineRail.js';
|
|
16
18
|
import { landTurn, scrollerOf } from './conversation-scroll.js';
|
|
17
19
|
import { mergeTimelineItems } from './timeline.js';
|
|
18
20
|
import { presentLiveTurn, segmentLiveTurn } from './live-turn.js';
|
|
21
|
+
import { frostedGlassOf } from './fold-intensity.js';
|
|
19
22
|
import { ChoreographedFlow, useFlowChat } from './ChoreographedFlow.js';
|
|
20
23
|
import { ClosedProcessSummary } from './ClosedProcessSummary.js';
|
|
21
24
|
import { StickyLane } from './StickyLane.js';
|
|
@@ -146,12 +149,31 @@ const MainNode = memo(function MainNode({ useChat, nodeKey, boundary, pinned, pr
|
|
|
146
149
|
return null;
|
|
147
150
|
if (node.kind === 'context' || node.kind === 'turn-tail' || node.kind === 'system-prompt' || node.kind === 'turn-process')
|
|
148
151
|
return null;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
152
|
+
// No 'command-input' branch: that string is not a chat node kind in any released
|
|
153
|
+
// host (it appears nowhere in the installed packages), so a slash command arrives
|
|
154
|
+
// as 'command' above and is rendered there. Keeping the branch only made the file
|
|
155
|
+
// look like it handled a case that cannot occur.
|
|
153
156
|
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 })] });
|
|
154
157
|
});
|
|
158
|
+
/**
|
|
159
|
+
* Sub-agents this turn dispatched, read from the host's own Turn-process row.
|
|
160
|
+
*
|
|
161
|
+
* The host already derives this from the dispatch tree (`subagentCount` on
|
|
162
|
+
* `TurnProcessChatData`), so counting calls here would only risk disagreeing with
|
|
163
|
+
* it. The row is otherwise hidden by the reader, which is why this is the one place
|
|
164
|
+
* its counters are read.
|
|
165
|
+
*/
|
|
166
|
+
function subagentCount(snapshot, keys) {
|
|
167
|
+
for (const key of keys) {
|
|
168
|
+
const node = snapshot.nodes.get(key);
|
|
169
|
+
if (!node || node.kind !== 'turn-process')
|
|
170
|
+
continue;
|
|
171
|
+
const value = node.data.subagentCount;
|
|
172
|
+
if (typeof value === 'number' && Number.isFinite(value) && value > 0)
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
return 0;
|
|
176
|
+
}
|
|
155
177
|
function GroupStatus({ group, sessionId, useChat, useSessionPendingInteraction, motion }) {
|
|
156
178
|
const pending = useSessionPendingInteraction(snapshot => snapshot.get(sessionId));
|
|
157
179
|
const text = useChat(snapshot => {
|
|
@@ -168,8 +190,10 @@ function GroupStatus({ group, sessionId, useChat, useSessionPendingInteraction,
|
|
|
168
190
|
return '等待你的操作';
|
|
169
191
|
const current = turn.steps.at(-1)?.data.get('assistant-step');
|
|
170
192
|
const last = current?.blocks.at(-1);
|
|
171
|
-
if (current?.status === 'running' && last?.kind === 'tool-call')
|
|
172
|
-
|
|
193
|
+
if (current?.status === 'running' && last?.kind === 'tool-call') {
|
|
194
|
+
const spawned = subagentCount(snapshot, group.keys);
|
|
195
|
+
return spawned ? `${preparingLabel(last.name)}·${spawned} 个子代理` : preparingLabel(last.name);
|
|
196
|
+
}
|
|
173
197
|
for (let index = group.keys.length - 1; index >= 0; index--) {
|
|
174
198
|
const node = snapshot.nodes.get(group.keys[index]);
|
|
175
199
|
if (!node)
|
|
@@ -186,7 +210,7 @@ function GroupStatus({ group, sessionId, useChat, useSessionPendingInteraction,
|
|
|
186
210
|
const busy = useChat(snapshot => group.turn !== null && snapshot.timeline.turns.get(group.turn)?.status === 'open' && pending === undefined);
|
|
187
211
|
return _jsx(StatusText, { text: text, motion: motion, shimmer: busy });
|
|
188
212
|
}
|
|
189
|
-
const DeliverableChip = memo(function DeliverableChip({ path, openFile, revealFile }) {
|
|
213
|
+
const DeliverableChip = memo(function DeliverableChip({ path, openFile, revealFile, openMode }) {
|
|
190
214
|
const [status, setStatus] = useState('idle');
|
|
191
215
|
const timer = useRef();
|
|
192
216
|
const flash = (next) => {
|
|
@@ -231,9 +255,9 @@ const DeliverableChip = memo(function DeliverableChip({ path, openFile, revealFi
|
|
|
231
255
|
};
|
|
232
256
|
const name = basename(path);
|
|
233
257
|
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":
|
|
258
|
+
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
259
|
});
|
|
236
|
-
function DeliverablesRow({ deliverables, openFile, revealFile }) {
|
|
260
|
+
function DeliverablesRow({ deliverables, openFile, revealFile, openMode }) {
|
|
237
261
|
const [folderStatus, setFolderStatus] = useState('idle');
|
|
238
262
|
const onOpenWorkspace = () => {
|
|
239
263
|
try {
|
|
@@ -245,7 +269,7 @@ function DeliverablesRow({ deliverables, openFile, revealFile }) {
|
|
|
245
269
|
// ignore
|
|
246
270
|
}
|
|
247
271
|
};
|
|
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' ? '已打开访达' : '在文件夹中显示' })] }))] }) })] }));
|
|
272
|
+
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
273
|
}
|
|
250
274
|
const TurnGroup = memo(function TurnGroup({ group, motion, autoFold, pinnedKeys, selectedProcessKeys, isAwaitingModel = false, ...props }) {
|
|
251
275
|
const snapshot = props.useChat(snapshot => snapshot);
|
|
@@ -266,6 +290,7 @@ const TurnGroup = memo(function TurnGroup({ group, motion, autoFold, pinnedKeys,
|
|
|
266
290
|
const flow = useMemo(() => readerFlow({ ...group, keys: mainKeys }, turn, key => nodes.get(key)), [nodes, group, mainKeys, turn]);
|
|
267
291
|
const steps = useMemo(() => segmentLiveTurn(flow, key => nodes.get(key)), [flow, nodes]);
|
|
268
292
|
const liveItems = useMemo(() => presentLiveTurn(steps, boundary, autoFold), [steps, boundary, autoFold]);
|
|
293
|
+
const openMode = deliverableOpenModeOf(useSyncExternalStore(props.openPrefs?.subscribe ?? ((fn) => { void fn; return () => { }; }), () => props.openPrefs?.getSnapshot()?.deliverableOpenMode, () => 'external'));
|
|
269
294
|
const hasProcess = flow.some(item => item.kind === 'tool' || hasProcessContent(nodes.get(item.nodeKey), boundary));
|
|
270
295
|
// Only a real, still-active text selection delays folding. Merely clicking,
|
|
271
296
|
// focusing or scrolling the live card does not create a permanent override.
|
|
@@ -273,7 +298,7 @@ const TurnGroup = memo(function TurnGroup({ group, motion, autoFold, pinnedKeys,
|
|
|
273
298
|
|| liveItems.some(item => item.kind === 'fold'
|
|
274
299
|
? item.key === key || item.steps.some(step => step.key === key || ('nodeKey' in step && step.nodeKey === key))
|
|
275
300
|
: item.key === key || ('nodeKey' in item.step && item.step.nodeKey === key)));
|
|
276
|
-
const expanded = holdingSelection || processExpanded(expansionChoice, boundary);
|
|
301
|
+
const expanded = !autoFold || holdingSelection || processExpanded(expansionChoice, boundary);
|
|
277
302
|
const [foldOpenByKey, setFoldOpenByKey] = useState({});
|
|
278
303
|
const deliverables = useMemo(() => getTurnDeliverables(turn, flow), [turn, flow]);
|
|
279
304
|
const fileMentions = useMemo(() => deliverables.length > 0 && props.openFile ? createProducedFileMentions(deliverables, props.openFile) : undefined, [deliverables, props.openFile]);
|
|
@@ -315,10 +340,17 @@ const TurnGroup = memo(function TurnGroup({ group, motion, autoFold, pinnedKeys,
|
|
|
315
340
|
forkSeq,
|
|
316
341
|
fileMentions,
|
|
317
342
|
metrics,
|
|
343
|
+
getToolView: props.getToolView,
|
|
318
344
|
};
|
|
319
345
|
const terminal = terminalLabel(boundary.reason);
|
|
320
346
|
const hasTurnError = flow.some(item => item.kind === 'node' && nodes.get(item.nodeKey)?.kind === 'turn-error');
|
|
321
|
-
|
|
347
|
+
// A stopped turn normally stays quiet, but a stopped turn that produced no
|
|
348
|
+
// prose at all reads as if its answer vanished into the process fold. Say so.
|
|
349
|
+
const hasAnswerProse = steps.some(step => step.kind === 'body'
|
|
350
|
+
&& step.blocks.some(block => block.kind === 'text' && block.text.trim() !== ''));
|
|
351
|
+
const stoppedWithoutAnswer = (boundary.reason === 'interrupted' || boundary.reason === 'aborted') && !hasAnswerProse;
|
|
352
|
+
const showTerminalNotice = terminal && !hasTurnError
|
|
353
|
+
&& (stoppedWithoutAnswer || (boundary.reason !== 'interrupted' && boundary.reason !== 'aborted'));
|
|
322
354
|
const renderStep = (step, folded) => {
|
|
323
355
|
const processOpen = folded || expanded;
|
|
324
356
|
if (step.kind === 'reasoning' || step.kind === 'body')
|
|
@@ -329,7 +361,7 @@ const TurnGroup = memo(function TurnGroup({ group, motion, autoFold, pinnedKeys,
|
|
|
329
361
|
return _jsx(BlockBoundary, { children: _jsx(MainNode, { ...shared, boundary: boundary, nodeKey: step.nodeKey }) });
|
|
330
362
|
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
363
|
};
|
|
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 => {
|
|
364
|
+
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
365
|
if (step.kind === 'user')
|
|
334
366
|
return false;
|
|
335
367
|
if (step.kind !== 'body')
|
|
@@ -338,7 +370,7 @@ const TurnGroup = memo(function TurnGroup({ group, motion, autoFold, pinnedKeys,
|
|
|
338
370
|
return !!node && isNode(node, 'assistant-step') && (isEarlierNarration(node.data, boundary)
|
|
339
371
|
|| node.data.blocks.some(block => block.kind === 'tool-call')
|
|
340
372
|
|| (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 })] });
|
|
373
|
+
}) }), _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 }), showTerminalNotice && _jsx("div", { className: css.notice, "data-reader-terminal": true, children: terminal })] });
|
|
342
374
|
});
|
|
343
375
|
export function Reader(props) {
|
|
344
376
|
const root = useRef(null);
|
|
@@ -353,51 +385,47 @@ export function Reader(props) {
|
|
|
353
385
|
const hasMore = props.useSession(snapshot => snapshot.hasMore);
|
|
354
386
|
const loadingOlder = props.useSession(snapshot => snapshot.loadingOlder);
|
|
355
387
|
const pendingSubmissions = props.useSession(snapshot => snapshot.pendingSubmissions);
|
|
356
|
-
const
|
|
388
|
+
const pendingList = asReadonlyArray(pendingSubmissions);
|
|
389
|
+
const waitAnchor = waitingAnchor(order, key => nodes.get(key), pendingList);
|
|
357
390
|
const motionPreference = props.useStore(state => state.motion);
|
|
358
391
|
const motion = useMotionAllowed(motionPreference);
|
|
359
|
-
const
|
|
392
|
+
const prefsSnap = useSyncExternalStore(props.openPrefs?.subscribe ?? ((fn) => { void fn; return () => { }; }), () => props.openPrefs?.getSnapshot?.(), () => undefined);
|
|
393
|
+
const storeAutoFold = props.useStore(state => state.autoFold);
|
|
394
|
+
const autoFold = prefsSnap?.autoFold ?? (prefsSnap?.foldIntensity !== undefined ? prefsSnap.foldIntensity !== 0 : undefined) ?? storeAutoFold ?? true;
|
|
395
|
+
const frostedGlass = frostedGlassOf(prefsSnap);
|
|
360
396
|
const streamMotion = useMemo(() => ({ enabled: motion, activatedAt: activatedAt.current }), [motion]);
|
|
361
397
|
const groups = useMemo(() => groupNodes(order, key => nodes.get(key)), [order, nodes, timeline]);
|
|
362
398
|
const isAwaitingModel = useMemo(() => {
|
|
363
399
|
// 状态机铁律:「深度求索中」与「正在处理/思考」永远互斥。
|
|
364
400
|
// 1. 仅空闲发送(!running)的本地回显窗口:用户按下回车第 0 毫秒立即反馈。
|
|
365
401
|
// 忙碌时的 queued/steering 回显不算等待——模型本来就在工作。
|
|
366
|
-
if (!running &&
|
|
402
|
+
if (!running && pendingList.length > 0)
|
|
367
403
|
return true;
|
|
368
404
|
const lastKey = order.at(-1);
|
|
369
405
|
const lastNode = lastKey ? nodes.get(lastKey) : undefined;
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
//
|
|
373
|
-
|
|
374
|
-
|
|
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') {
|
|
406
|
+
if (!lastKey || !lastNode)
|
|
407
|
+
return false;
|
|
408
|
+
// 2. 模型已经产出内容:等待结束,计时器必须立刻消失。
|
|
409
|
+
// 「产出」= assistant-step 已带 block(正文/思维链/工具调用都算)。
|
|
410
|
+
if (lastNode.kind === 'assistant-step') {
|
|
394
411
|
const data = lastNode.data;
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
412
|
+
const blocks = data.blocks ?? [];
|
|
413
|
+
// 空 block 且仍在跑 = 请求已发出、模型还没吐字,正是要计时的那一刻。
|
|
414
|
+
return blocks.length === 0 && data.status === 'running';
|
|
398
415
|
}
|
|
399
|
-
|
|
400
|
-
|
|
416
|
+
// 3. 球在模型脚下:用户刚发言,或工具已返回、上下文已注入、命令已执行。
|
|
417
|
+
// 这些时刻模型随时可能卡住,正是要计时的地方;工具执行期间不计时,
|
|
418
|
+
// 因为那时忙的是工具而不是模型。
|
|
419
|
+
// handsBackToModel 内部按 chat 层 kind 判定,并区分「工具已返回」与
|
|
420
|
+
// 「工具仍在跑」——后者不算等待。
|
|
421
|
+
const modelOwesResponse = handsBackToModel(lastNode);
|
|
422
|
+
if (!modelOwesResponse)
|
|
423
|
+
return false;
|
|
424
|
+
// 轮次已结束就没什么可等的。
|
|
425
|
+
const lastGroup = groups.at(-1);
|
|
426
|
+
const turn = lastGroup?.turn === null || lastGroup?.turn === undefined ? undefined : timeline.turns.get(lastGroup.turn);
|
|
427
|
+
return turn?.status !== 'closed';
|
|
428
|
+
}, [running, pendingList, order, nodes, groups, timeline]);
|
|
401
429
|
const scroll = useReadingScroll(root, motion);
|
|
402
430
|
const pinnedKeys = usePinnedSelection(root);
|
|
403
431
|
const selectedProcessKeys = usePinnedSelection(root, '[data-reader-process]');
|
|
@@ -505,7 +533,7 @@ export function Reader(props) {
|
|
|
505
533
|
}, [props.loadThrough, props.loadOlder, scroll.jump, scroll.release, timelineItems]);
|
|
506
534
|
const lastKey = order.at(-1);
|
|
507
535
|
const lastNode = lastKey ? nodes.get(lastKey) : undefined;
|
|
508
|
-
const lastSubmissionId =
|
|
536
|
+
const lastSubmissionId = pendingList.length ? pendingList[pendingList.length - 1].requestId : null;
|
|
509
537
|
const lastOrderKeyRef = useRef(lastKey);
|
|
510
538
|
const lastSubmissionRef = useRef(lastSubmissionId);
|
|
511
539
|
useLayoutEffect(() => {
|
|
@@ -518,13 +546,16 @@ export function Reader(props) {
|
|
|
518
546
|
}
|
|
519
547
|
}, [lastKey, lastNode?.kind, lastSubmissionId, scroll]);
|
|
520
548
|
const visibleSubmissions = useMemo(() => {
|
|
521
|
-
if (
|
|
549
|
+
if (pendingList.length === 0)
|
|
522
550
|
return [];
|
|
523
|
-
return
|
|
524
|
-
}, [
|
|
551
|
+
return pendingList.filter(sub => sub.placement !== 'queued');
|
|
552
|
+
}, [pendingList]);
|
|
525
553
|
// ChatView publishes data-chat-flow="" on its column. Skins treat a
|
|
526
554
|
// 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.
|
|
555
|
+
return _jsx(StreamMotionContext.Provider, { value: streamMotion, children: _jsxs("div", { ref: root, className: css.root, "data-dsh-better-display": "0.2.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: () => {
|
|
556
|
+
props.actions.setAutoFold(!autoFold);
|
|
557
|
+
props.openPrefs?.actions?.setAutoFold?.(!autoFold);
|
|
558
|
+
}, 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
559
|
setHistoryError(false);
|
|
529
560
|
try {
|
|
530
561
|
await props.loadOlder();
|
|
@@ -532,6 +563,9 @@ export function Reader(props) {
|
|
|
532
563
|
catch {
|
|
533
564
|
setHistoryError(true);
|
|
534
565
|
}
|
|
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 =>
|
|
566
|
+
}, 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 => {
|
|
567
|
+
const images = pendingSubmissionImages(submission);
|
|
568
|
+
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));
|
|
569
|
+
}), 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
570
|
}
|
|
537
571
|
//# sourceMappingURL=Reader.js.map
|