@deepseek-harness-tui/dsh-tui 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/bin/dsh-tui.js +24 -3
- package/cordis.patch.yml +24 -11
- package/cordis.yml +9 -1
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +2 -1
- package/lib/types/components/GoalTodoPanel.d.ts.map +1 -1
- package/lib/types/components/GoalTodoPanel.js +6 -1
- package/lib/types/components/HistorySearchDialog.d.ts.map +1 -1
- package/lib/types/components/HistorySearchDialog.js +5 -1
- package/lib/types/components/MessageList.d.ts +3 -1
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +9 -5
- package/lib/types/components/PluginSceneBoundary.d.ts +31 -0
- package/lib/types/components/PluginSceneBoundary.d.ts.map +1 -0
- package/lib/types/components/PluginSceneBoundary.js +29 -0
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +52 -74
- package/lib/types/components/SearchBox.d.ts +4 -0
- package/lib/types/components/SearchBox.d.ts.map +1 -1
- package/lib/types/components/SearchBox.js +101 -2
- package/lib/types/components/SkillsPicker.d.ts +18 -0
- package/lib/types/components/SkillsPicker.d.ts.map +1 -0
- package/lib/types/components/SkillsPicker.js +50 -0
- package/lib/types/components/Spinner/SpinnerAnimationRow.d.ts +4 -1
- package/lib/types/components/Spinner/SpinnerAnimationRow.d.ts.map +1 -1
- package/lib/types/components/Spinner/SpinnerAnimationRow.js +10 -5
- package/lib/types/components/SplitDiffView.d.ts +28 -0
- package/lib/types/components/SplitDiffView.d.ts.map +1 -0
- package/lib/types/components/SplitDiffView.js +417 -0
- package/lib/types/components/WorkingSpinner.d.ts +3 -1
- package/lib/types/components/WorkingSpinner.d.ts.map +1 -1
- package/lib/types/components/WorkingSpinner.js +2 -2
- package/lib/types/components/design-system/ThemedText.d.ts +7 -1
- package/lib/types/components/design-system/ThemedText.d.ts.map +1 -1
- package/lib/types/components/design-system/ThemedText.js +2 -2
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts +3 -1
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantToolUseMessage.js +17 -7
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
- package/lib/types/components/questions/AskUserQuestionPanel.js +52 -8
- package/lib/types/components/questions/PlanReviewPanel.d.ts.map +1 -1
- package/lib/types/components/questions/PlanReviewPanel.js +12 -3
- package/lib/types/dsh-adapter/channel.d.ts +78 -0
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +165 -14
- package/lib/types/dsh-adapter/contract.d.ts +1 -1
- package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
- package/lib/types/dsh-adapter/contract.js +1 -0
- package/lib/types/dsh-adapter/index.d.ts +4 -0
- package/lib/types/dsh-adapter/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.js +1 -0
- package/lib/types/dsh-adapter/packaged-presets.d.ts.map +1 -1
- package/lib/types/dsh-adapter/packaged-presets.js +5 -2
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +66 -3
- package/lib/types/dsh-adapter/promptDebug.d.ts +10 -0
- package/lib/types/dsh-adapter/promptDebug.d.ts.map +1 -0
- package/lib/types/dsh-adapter/promptDebug.js +148 -0
- package/lib/types/dsh-adapter/providerWizard.d.ts.map +1 -1
- package/lib/types/dsh-adapter/providerWizard.js +3 -1
- package/lib/types/dsh-adapter/scenes.d.ts +75 -0
- package/lib/types/dsh-adapter/scenes.d.ts.map +1 -0
- package/lib/types/dsh-adapter/scenes.js +78 -0
- package/lib/types/dsh-adapter/settings-sections.d.ts +116 -0
- package/lib/types/dsh-adapter/settings-sections.d.ts.map +1 -0
- package/lib/types/dsh-adapter/settings-sections.js +63 -0
- package/lib/types/dsh-adapter/settingsEditor.d.ts +128 -0
- package/lib/types/dsh-adapter/settingsEditor.d.ts.map +1 -0
- package/lib/types/dsh-adapter/settingsEditor.js +244 -0
- package/lib/types/i18n.d.ts +169 -18
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +45 -6
- package/lib/types/ink/components/Text.d.ts +9 -1
- package/lib/types/ink/components/Text.d.ts.map +1 -1
- package/lib/types/ink/components/Text.js +7 -6
- package/lib/types/ink/ink.d.ts.map +1 -1
- package/lib/types/ink/ink.js +4 -2
- package/lib/types/ink/log-update.d.ts.map +1 -1
- package/lib/types/ink/log-update.js +76 -9
- package/lib/types/ink/parse-keypress.d.ts.map +1 -1
- package/lib/types/ink/parse-keypress.js +4 -1
- package/lib/types/ink/terminal.d.ts +40 -4
- package/lib/types/ink/terminal.d.ts.map +1 -1
- package/lib/types/ink/terminal.js +57 -5
- package/lib/types/ink/termio/tokenize.d.ts +6 -0
- package/lib/types/ink/termio/tokenize.d.ts.map +1 -1
- package/lib/types/ink/termio/tokenize.js +18 -4
- package/lib/types/jsx-runtime.d.ts +2 -0
- package/lib/types/jsx-runtime.d.ts.map +1 -0
- package/lib/types/jsx-runtime.js +13 -0
- package/lib/types/modelRoute.d.ts +10 -2
- package/lib/types/modelRoute.d.ts.map +1 -1
- package/lib/types/modelRoute.js +33 -4
- package/lib/types/scenes.d.ts +3 -0
- package/lib/types/scenes.d.ts.map +1 -0
- package/lib/types/scenes.js +4 -0
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +182 -26
- package/lib/types/screens/SessionBrowser.d.ts.map +1 -1
- package/lib/types/screens/SessionBrowser.js +30 -15
- package/lib/types/screens/Settings.d.ts +20 -0
- package/lib/types/screens/Settings.d.ts.map +1 -0
- package/lib/types/screens/Settings.js +333 -0
- package/lib/types/screens/StatusLine.js +1 -1
- package/lib/types/sessions/view.d.ts.map +1 -1
- package/lib/types/sessions/view.js +13 -1
- package/lib/types/settings-sections.d.ts +3 -0
- package/lib/types/settings-sections.d.ts.map +1 -0
- package/lib/types/settings-sections.js +4 -0
- package/lib/types/theme.d.ts +12 -0
- package/lib/types/theme.d.ts.map +1 -1
- package/lib/types/theme.js +36 -0
- package/lib/types/update.d.ts +21 -7
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +66 -16
- package/lib/types/utils/paths.d.ts.map +1 -1
- package/lib/types/utils/paths.js +4 -1
- package/package.json +49 -9
|
@@ -26,7 +26,7 @@ const THINKING_GLOW_PERIOD_S = 2;
|
|
|
26
26
|
* animation clock (frame, glimmer, token counter animation, elapsed time,
|
|
27
27
|
* stalled intensity, thinking shimmer).
|
|
28
28
|
*/
|
|
29
|
-
export function SpinnerAnimationRow({ mode, reducedMotion, hasActiveTools, responseLengthRef, message, messageColor, shimmerColor, loadingStartTimeRef, totalPausedMsRef, pauseStartTimeRef, spinnerSuffix, verbose, columns, thinkingStatus, }) {
|
|
29
|
+
export function SpinnerAnimationRow({ mode, reducedMotion, hasActiveTools, responseLengthRef, uploadTokensRef, message, messageColor, shimmerColor, loadingStartTimeRef, totalPausedMsRef, pauseStartTimeRef, spinnerSuffix, verbose, columns, thinkingStatus, }) {
|
|
30
30
|
const [viewportRef, time] = useAnimationFrame(reducedMotion ? null : 50);
|
|
31
31
|
// === Elapsed time (wall-clock, derived from refs each frame) ===
|
|
32
32
|
const now = Date.now();
|
|
@@ -81,8 +81,13 @@ export function SpinnerAnimationRow({ mode, reducedMotion, hasActiveTools, respo
|
|
|
81
81
|
const timerText = formatDuration(elapsedTimeMs);
|
|
82
82
|
const timerWidth = stringWidth(timerText);
|
|
83
83
|
const tokenCount = formatNumber(leaderTokens);
|
|
84
|
-
const
|
|
85
|
-
|
|
84
|
+
const uploadTokens = uploadTokensRef.current;
|
|
85
|
+
// Real upload tokens (last request's input + cache) ride beside the
|
|
86
|
+
// animated download estimate; both labeled once to keep the row short.
|
|
87
|
+
const tokensLabel = uploadTokens > 0
|
|
88
|
+
? `↑ ${formatNumber(uploadTokens)} · ↓ ${tokenCount} tokens`
|
|
89
|
+
: `↓ ${tokenCount} tokens`;
|
|
90
|
+
const tokensWidth = stringWidth(tokensLabel);
|
|
86
91
|
// === Thinking text (may shrink to fit) ===
|
|
87
92
|
let thinkingText = thinkingStatus === 'thinking'
|
|
88
93
|
? 'thinking'
|
|
@@ -108,7 +113,7 @@ export function SpinnerAnimationRow({ mode, reducedMotion, hasActiveTools, respo
|
|
|
108
113
|
const showTimer = wantsTimerAndTokens && availableSpace > usedAfterThinking + timerWidth;
|
|
109
114
|
const usedAfterTimer = usedAfterThinking + (showTimer ? timerWidth + sep : 0);
|
|
110
115
|
const showTokens = wantsTimerAndTokens &&
|
|
111
|
-
leaderTokens > 0 &&
|
|
116
|
+
(leaderTokens > 0 || uploadTokens > 0) &&
|
|
112
117
|
availableSpace > usedAfterTimer + tokensWidth;
|
|
113
118
|
const thinkingOnly = showThinking &&
|
|
114
119
|
thinkingStatus === 'thinking' &&
|
|
@@ -137,7 +142,7 @@ export function SpinnerAnimationRow({ mode, reducedMotion, hasActiveTools, respo
|
|
|
137
142
|
: []),
|
|
138
143
|
...(showTokens
|
|
139
144
|
? [
|
|
140
|
-
_jsxs(Box, { flexDirection: "row", children: [_jsx(SpinnerModeGlyph, { mode: mode }),
|
|
145
|
+
_jsxs(Box, { flexDirection: "row", children: [_jsx(SpinnerModeGlyph, { mode: mode }), _jsx(Text, { dimColor: true, children: tokensLabel })] }, "tokens"),
|
|
141
146
|
]
|
|
142
147
|
: []),
|
|
143
148
|
...(showThinking && thinkingText
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ToolFileDiff } from '../dsh-adapter/channel.js';
|
|
3
|
+
import { type CliHighlight } from '../cc/cliHighlight.js';
|
|
4
|
+
/** chalk style for one raw theme value. Accepts every form the theme
|
|
5
|
+
* loader documents: #rgb, #rrggbb, #rrggbbaa (alpha stripped), rgb() with
|
|
6
|
+
* or without spaces, ansi256(n), ansi:name (issue #250, P2-5). */
|
|
7
|
+
export declare function chalkFromToken(token: string): (text: string) => string;
|
|
8
|
+
/** Parse a cli-highlight ANSI string into colored runs, preserving open
|
|
9
|
+
* spans across embedded newlines (multi-line strings/comments). Handles
|
|
10
|
+
* truecolor 38;2, 256-color 38;5 (tmux / FORCE_COLOR=2, issue #250 P1-1),
|
|
11
|
+
* and single-code 16-color SGR. */
|
|
12
|
+
export declare function parseAnsiRuns(text: string): {
|
|
13
|
+
text: string;
|
|
14
|
+
color?: string;
|
|
15
|
+
}[];
|
|
16
|
+
export declare function highlightLines(text: string, language: string | undefined, hl: CliHighlight | null, chTheme: Record<string, (text: string) => string> | undefined, themeSig: string): readonly {
|
|
17
|
+
text: string;
|
|
18
|
+
color?: string;
|
|
19
|
+
}[][] | undefined;
|
|
20
|
+
export declare function SplitDiffView({ diffs, width, maxRows, verbose, }: {
|
|
21
|
+
readonly diffs: readonly ToolFileDiff[];
|
|
22
|
+
/** Content width available to the whole two-pane block (divider included). */
|
|
23
|
+
readonly width: number;
|
|
24
|
+
/** Row budget when not verbose; overflow folds into one hint row. */
|
|
25
|
+
readonly maxRows: number;
|
|
26
|
+
readonly verbose: boolean;
|
|
27
|
+
}): React.ReactNode;
|
|
28
|
+
//# sourceMappingURL=SplitDiffView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SplitDiffView.d.ts","sourceRoot":"","sources":["../../../src/components/SplitDiffView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAKzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAE7D,OAAO,EAA0B,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAwFjF;;mEAEmE;AACnE,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAmBtE;AAUD;;;oCAGoC;AACpC,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,CAuB9E;AAuBD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,EAAE,EAAE,YAAY,GAAG,IAAI,EACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,SAAS,EAC7D,QAAQ,EAAE,MAAM,GACf,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,GAAG,SAAS,CAe3D;AA6ND,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,KAAK,EACL,OAAO,EACP,OAAO,GACR,EAAE;IACD,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAA;IACvC,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAC1B,GAAG,KAAK,CAAC,SAAS,CAiGlB"}
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Box, Text } from '../ui.js';
|
|
4
|
+
import * as JsDiff from 'diff';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { extname } from 'node:path';
|
|
7
|
+
import { getCliHighlightPromise } from '../cc/cliHighlight.js';
|
|
8
|
+
import { getTheme } from '../theme.js';
|
|
9
|
+
import { useTheme } from './design-system/ThemeProvider.js';
|
|
10
|
+
/** Replace tabs so width math and alignment hold (pi convention). */
|
|
11
|
+
const expandTabs = (text) => text.replaceAll('\t', ' ');
|
|
12
|
+
// --- syntax highlighting ----------------------------------------------------
|
|
13
|
+
/** highlight.js token classes → theme syntax tokens. `default` catches
|
|
14
|
+
* every unmapped class so cli-highlight's own yellow never leaks through
|
|
15
|
+
* (issue #250, P2-6). */
|
|
16
|
+
const SYNTAX_CLASS_TO_TOKEN = {
|
|
17
|
+
keyword: 'syntaxKeyword',
|
|
18
|
+
built_in: 'syntaxKeyword',
|
|
19
|
+
literal: 'syntaxKeyword',
|
|
20
|
+
string: 'syntaxString',
|
|
21
|
+
subst: 'syntaxString',
|
|
22
|
+
quote: 'syntaxString',
|
|
23
|
+
comment: 'syntaxComment',
|
|
24
|
+
number: 'syntaxNumber',
|
|
25
|
+
title: 'syntaxFunction',
|
|
26
|
+
'title.function_': 'syntaxFunction',
|
|
27
|
+
function: 'syntaxFunction',
|
|
28
|
+
'title.class_': 'syntaxType',
|
|
29
|
+
type: 'syntaxType',
|
|
30
|
+
class: 'syntaxType',
|
|
31
|
+
tag: 'syntaxType',
|
|
32
|
+
name: 'syntaxType',
|
|
33
|
+
attr: 'syntaxVariable',
|
|
34
|
+
attribute: 'syntaxVariable',
|
|
35
|
+
variable: 'syntaxVariable',
|
|
36
|
+
'template-variable': 'syntaxVariable',
|
|
37
|
+
params: 'syntaxVariable',
|
|
38
|
+
operator: 'syntaxOperator',
|
|
39
|
+
punctuation: 'syntaxPunctuation',
|
|
40
|
+
meta: 'syntaxPunctuation',
|
|
41
|
+
symbol: 'syntaxConstant',
|
|
42
|
+
regexp: 'syntaxConstant',
|
|
43
|
+
default: 'syntaxVariable',
|
|
44
|
+
};
|
|
45
|
+
/** chalk style for one raw theme value. Accepts every form the theme
|
|
46
|
+
* loader documents: #rgb, #rrggbb, #rrggbbaa (alpha stripped), rgb() with
|
|
47
|
+
* or without spaces, ansi256(n), ansi:name (issue #250, P2-5). */
|
|
48
|
+
export function chalkFromToken(token) {
|
|
49
|
+
let match = /^#([0-9a-fA-F]{3})$/.exec(token);
|
|
50
|
+
if (match !== null) {
|
|
51
|
+
const [r, g, b] = match[1].split('').map(c => parseInt(c + c, 16));
|
|
52
|
+
return chalk.rgb(r, g, b);
|
|
53
|
+
}
|
|
54
|
+
match = /^#([0-9a-fA-F]{6})(?:[0-9a-fA-F]{2})?$/.exec(token);
|
|
55
|
+
if (match !== null)
|
|
56
|
+
return chalk.hex(`#${match[1]}`);
|
|
57
|
+
match = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/.exec(token);
|
|
58
|
+
if (match !== null)
|
|
59
|
+
return chalk.rgb(Number(match[1]), Number(match[2]), Number(match[3]));
|
|
60
|
+
match = /^ansi256\((\d+)\)$/.exec(token);
|
|
61
|
+
if (match !== null)
|
|
62
|
+
return chalk.ansi256(Number(match[1]));
|
|
63
|
+
match = /^ansi:(\w+)$/.exec(token);
|
|
64
|
+
if (match !== null) {
|
|
65
|
+
const name = match[1] === 'blackBright' ? 'gray' : match[1];
|
|
66
|
+
const style = chalk[name];
|
|
67
|
+
if (style !== undefined)
|
|
68
|
+
return style;
|
|
69
|
+
}
|
|
70
|
+
return (text) => text;
|
|
71
|
+
}
|
|
72
|
+
/** ANSI 16-color SGR codes → rgb() strings (the dark-ansi palette path). */
|
|
73
|
+
const ANSI16 = {
|
|
74
|
+
30: 'rgb(0,0,0)', 31: 'rgb(128,0,0)', 32: 'rgb(0,128,0)', 33: 'rgb(128,128,0)',
|
|
75
|
+
34: 'rgb(0,0,128)', 35: 'rgb(128,0,128)', 36: 'rgb(0,128,128)', 37: 'rgb(192,192,192)',
|
|
76
|
+
90: 'rgb(128,128,128)', 91: 'rgb(255,0,0)', 92: 'rgb(0,255,0)', 93: 'rgb(255,255,0)',
|
|
77
|
+
94: 'rgb(0,0,255)', 95: 'rgb(255,0,255)', 96: 'rgb(0,255,255)', 97: 'rgb(255,255,255)',
|
|
78
|
+
};
|
|
79
|
+
/** Parse a cli-highlight ANSI string into colored runs, preserving open
|
|
80
|
+
* spans across embedded newlines (multi-line strings/comments). Handles
|
|
81
|
+
* truecolor 38;2, 256-color 38;5 (tmux / FORCE_COLOR=2, issue #250 P1-1),
|
|
82
|
+
* and single-code 16-color SGR. */
|
|
83
|
+
export function parseAnsiRuns(text) {
|
|
84
|
+
const runs = [];
|
|
85
|
+
let color;
|
|
86
|
+
let rest = text;
|
|
87
|
+
// eslint-disable-next-line no-control-regex -- parsing SGR is the point
|
|
88
|
+
const sgr = /\x1b\[([0-9;]+)m/;
|
|
89
|
+
while (rest.length > 0) {
|
|
90
|
+
const match = sgr.exec(rest);
|
|
91
|
+
const chunk = match === null ? rest : rest.slice(0, match.index);
|
|
92
|
+
if (chunk !== '')
|
|
93
|
+
runs.push(color === undefined ? { text: chunk } : { text: chunk, color });
|
|
94
|
+
if (match === null)
|
|
95
|
+
break;
|
|
96
|
+
const codes = match[1].split(';').map(Number);
|
|
97
|
+
if (codes.includes(0) || codes.includes(39))
|
|
98
|
+
color = undefined;
|
|
99
|
+
else if (codes[0] === 38 && codes[1] === 2 && codes.length >= 5) {
|
|
100
|
+
color = `rgb(${codes[2]},${codes[3]},${codes[4]})`;
|
|
101
|
+
}
|
|
102
|
+
else if (codes[0] === 38 && codes[1] === 5 && codes.length >= 3) {
|
|
103
|
+
color = `ansi256(${codes[2]})`;
|
|
104
|
+
}
|
|
105
|
+
else if (codes.length === 1 && ANSI16[codes[0]] !== undefined) {
|
|
106
|
+
color = ANSI16[codes[0]];
|
|
107
|
+
}
|
|
108
|
+
rest = rest.slice(match.index + match[0].length);
|
|
109
|
+
}
|
|
110
|
+
return runs;
|
|
111
|
+
}
|
|
112
|
+
/** Split flat runs into per-line run arrays at embedded newlines. */
|
|
113
|
+
function splitRunsToLines(runs) {
|
|
114
|
+
const lines = [[]];
|
|
115
|
+
for (const run of runs) {
|
|
116
|
+
const parts = run.text.split('\n');
|
|
117
|
+
for (let i = 0; i < parts.length; i++) {
|
|
118
|
+
if (i > 0)
|
|
119
|
+
lines.push([]);
|
|
120
|
+
if (parts[i] !== '') {
|
|
121
|
+
lines[lines.length - 1].push(run.color === undefined ? { text: parts[i] } : { text: parts[i], color: run.color });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return lines;
|
|
126
|
+
}
|
|
127
|
+
/** Whole-hunk syntax runs per line, cached per (theme signature + language
|
|
128
|
+
* + text): one entry survives any amount of card re-rendering, and the
|
|
129
|
+
* palette change invalidates it (issue #250, P1-2 + P2-8). */
|
|
130
|
+
const syntaxCache = new Map();
|
|
131
|
+
const SYNTAX_CACHE_MAX = 40;
|
|
132
|
+
export function highlightLines(text, language, hl, chTheme, themeSig) {
|
|
133
|
+
if (hl === null || chTheme === undefined || language === undefined)
|
|
134
|
+
return undefined;
|
|
135
|
+
if (text === '' || !hl.supportsLanguage(language))
|
|
136
|
+
return undefined;
|
|
137
|
+
const key = `${themeSig}${language}${text}`;
|
|
138
|
+
const cached = syntaxCache.get(key);
|
|
139
|
+
if (cached !== undefined)
|
|
140
|
+
return cached;
|
|
141
|
+
let lines;
|
|
142
|
+
try {
|
|
143
|
+
lines = splitRunsToLines(parseAnsiRuns(hl.highlight(text, { language, theme: chTheme })));
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
if (syntaxCache.size >= SYNTAX_CACHE_MAX)
|
|
149
|
+
syntaxCache.clear();
|
|
150
|
+
syntaxCache.set(key, lines);
|
|
151
|
+
return lines;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Merge syntax runs with word-diff change flags over the same string: both
|
|
155
|
+
* partition it, so an offset sweep yields runs carrying a syntax color AND
|
|
156
|
+
* a changed flag. The render layer lets `changed` override the color.
|
|
157
|
+
*/
|
|
158
|
+
function mergeRuns(syntax, words) {
|
|
159
|
+
const out = [];
|
|
160
|
+
let si = 0;
|
|
161
|
+
let wi = 0;
|
|
162
|
+
let sOff = 0;
|
|
163
|
+
let wOff = 0;
|
|
164
|
+
while (si < syntax.length && wi < words.length) {
|
|
165
|
+
const s = syntax[si];
|
|
166
|
+
const w = words[wi];
|
|
167
|
+
const sLen = s.text.length - sOff;
|
|
168
|
+
const wLen = w.text.length - wOff;
|
|
169
|
+
const take = Math.min(sLen, wLen);
|
|
170
|
+
if (take > 0) {
|
|
171
|
+
out.push({
|
|
172
|
+
text: w.text.slice(wOff, wOff + take),
|
|
173
|
+
changed: w.changed,
|
|
174
|
+
color: w.changed ? undefined : s.color,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
sOff += take;
|
|
178
|
+
wOff += take;
|
|
179
|
+
if (sOff >= s.text.length) {
|
|
180
|
+
si++;
|
|
181
|
+
sOff = 0;
|
|
182
|
+
}
|
|
183
|
+
if (wOff >= w.text.length) {
|
|
184
|
+
wi++;
|
|
185
|
+
wOff = 0;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
// --- word-level diff --------------------------------------------------------
|
|
191
|
+
/** Strip the shared leading whitespace before word-diffing: otherwise the
|
|
192
|
+
* whole indent reads as one changed blob (pi's renderIntraLineDiff trick). */
|
|
193
|
+
function wordSegments(oldLine, newLine) {
|
|
194
|
+
const oldIndent = /^\s*/.exec(oldLine)?.[0] ?? '';
|
|
195
|
+
const newIndent = /^\s*/.exec(newLine)?.[0] ?? '';
|
|
196
|
+
const sharedIndent = oldIndent === newIndent ? oldIndent : '';
|
|
197
|
+
const parts = JsDiff.diffWords(oldLine.slice(sharedIndent.length), newLine.slice(sharedIndent.length));
|
|
198
|
+
const oldSegments = sharedIndent === '' ? [] : [{ text: sharedIndent, changed: false }];
|
|
199
|
+
const newSegments = sharedIndent === '' ? [] : [{ text: sharedIndent, changed: false }];
|
|
200
|
+
for (const part of parts) {
|
|
201
|
+
if (part.added)
|
|
202
|
+
newSegments.push({ text: part.value, changed: true });
|
|
203
|
+
else if (part.removed)
|
|
204
|
+
oldSegments.push({ text: part.value, changed: true });
|
|
205
|
+
else {
|
|
206
|
+
oldSegments.push({ text: part.value, changed: false });
|
|
207
|
+
newSegments.push({ text: part.value, changed: false });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return { old: oldSegments, new: newSegments };
|
|
211
|
+
}
|
|
212
|
+
const plainSegments = (line) => [{ text: line, changed: false }];
|
|
213
|
+
// --- hunk alignment ---------------------------------------------------------
|
|
214
|
+
/**
|
|
215
|
+
* Pair the lines of an unequal removed+added block via a case-insensitive
|
|
216
|
+
* LCS: lines equal modulo case align as change pairs, everything before the
|
|
217
|
+
* first match stays block order. An inserted line above an edited one
|
|
218
|
+
* (`foo,bar` → `insert,FOO,bar`) therefore yields `insert` add-only, then
|
|
219
|
+
* `foo ↔ FOO`, instead of the index-zip misalignment (issue #250, P2-4).
|
|
220
|
+
*/
|
|
221
|
+
function lcsPairs(oldLines, newLines) {
|
|
222
|
+
const m = oldLines.length;
|
|
223
|
+
const n = newLines.length;
|
|
224
|
+
// dp[i][j] = LCS length of oldLines[i:] and newLines[j:].
|
|
225
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
226
|
+
const eq = (a, b) => a.toLowerCase() === b.toLowerCase();
|
|
227
|
+
for (let i = m - 1; i >= 0; i--) {
|
|
228
|
+
for (let j = n - 1; j >= 0; j--) {
|
|
229
|
+
dp[i][j] = eq(oldLines[i], newLines[j])
|
|
230
|
+
? dp[i + 1][j + 1] + 1
|
|
231
|
+
: Math.max(dp[i + 1][j], dp[i][j + 1]);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const pairs = [];
|
|
235
|
+
let i = 0;
|
|
236
|
+
let j = 0;
|
|
237
|
+
while (i < m && j < n) {
|
|
238
|
+
if (eq(oldLines[i], newLines[j])) {
|
|
239
|
+
pairs.push([i, j]);
|
|
240
|
+
i++;
|
|
241
|
+
j++;
|
|
242
|
+
}
|
|
243
|
+
else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
244
|
+
i++;
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
j++;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return pairs;
|
|
251
|
+
}
|
|
252
|
+
/** Align one file's hunks into rows (plain text + word flags; styling is a
|
|
253
|
+
* render-time concern over the visible slice). */
|
|
254
|
+
function alignFileDiff(fileIndex, oldText, newText) {
|
|
255
|
+
const rows = [];
|
|
256
|
+
const parts = JsDiff.diffLines(expandTabs(oldText ?? ''), expandTabs(newText));
|
|
257
|
+
let oldIndex = 0;
|
|
258
|
+
let newIndex = 0;
|
|
259
|
+
for (let i = 0; i < parts.length; i++) {
|
|
260
|
+
const part = parts[i];
|
|
261
|
+
const lines = part.value.replace(/\n$/, '').split('\n');
|
|
262
|
+
if (!part.added && !part.removed) {
|
|
263
|
+
for (const line of lines) {
|
|
264
|
+
rows.push({
|
|
265
|
+
kind: 'context',
|
|
266
|
+
fileIndex,
|
|
267
|
+
oldIndex: oldIndex++,
|
|
268
|
+
newIndex: newIndex++,
|
|
269
|
+
oldWords: plainSegments(line),
|
|
270
|
+
newWords: plainSegments(line),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (part.removed) {
|
|
276
|
+
const addedPart = parts[i + 1]?.added === true ? parts[i + 1] : undefined;
|
|
277
|
+
const addedLines = addedPart === undefined ? [] : addedPart.value.replace(/\n$/, '').split('\n');
|
|
278
|
+
// Same-length replacement blocks zip index-for-index (the common
|
|
279
|
+
// edit case); unequal blocks pair through the ci-LCS so an inserted
|
|
280
|
+
// line cannot shift the alignment (issue #250, P2-4).
|
|
281
|
+
const pairs = lines.length === addedLines.length
|
|
282
|
+
? lines.map((_, index) => [index, index])
|
|
283
|
+
: lcsPairs(lines, addedLines);
|
|
284
|
+
// Block order: walk both lists; unpaired entries emit in place, pairs
|
|
285
|
+
// emit at the later of the two positions.
|
|
286
|
+
const events = [];
|
|
287
|
+
let oi = 0;
|
|
288
|
+
let ai = 0;
|
|
289
|
+
for (const [o, a] of pairs) {
|
|
290
|
+
while (oi < o)
|
|
291
|
+
events.push({ kind: 'del', o: oi++ });
|
|
292
|
+
while (ai < a)
|
|
293
|
+
events.push({ kind: 'add', a: ai++ });
|
|
294
|
+
events.push({ kind: 'change', o: oi++, a: ai++ });
|
|
295
|
+
}
|
|
296
|
+
while (oi < lines.length)
|
|
297
|
+
events.push({ kind: 'del', o: oi++ });
|
|
298
|
+
while (ai < addedLines.length)
|
|
299
|
+
events.push({ kind: 'add', a: ai++ });
|
|
300
|
+
for (const event of events) {
|
|
301
|
+
if (event.kind === 'change') {
|
|
302
|
+
const segments = wordSegments(lines[event.o], addedLines[event.a]);
|
|
303
|
+
rows.push({
|
|
304
|
+
kind: 'change',
|
|
305
|
+
fileIndex,
|
|
306
|
+
oldIndex: oldIndex++,
|
|
307
|
+
newIndex: newIndex++,
|
|
308
|
+
oldWords: segments.old,
|
|
309
|
+
newWords: segments.new,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
else if (event.kind === 'del') {
|
|
313
|
+
rows.push({ kind: 'del', fileIndex, oldIndex: oldIndex++, oldWords: plainSegments(lines[event.o]) });
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
rows.push({ kind: 'add', fileIndex, newIndex: newIndex++, newWords: plainSegments(addedLines[event.a]) });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (addedPart !== undefined)
|
|
320
|
+
i++;
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
for (const line of lines) {
|
|
324
|
+
rows.push({ kind: 'add', fileIndex, newIndex: newIndex++, newWords: plainSegments(line) });
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return rows;
|
|
328
|
+
}
|
|
329
|
+
// --- rendering --------------------------------------------------------------
|
|
330
|
+
function PaneLine({ side, kind, width, tone, padLeft = false, }) {
|
|
331
|
+
const backgroundColor = kind === 'context'
|
|
332
|
+
? 'toolCardBackground'
|
|
333
|
+
: tone === 'old'
|
|
334
|
+
? 'diffRemovedDimmed'
|
|
335
|
+
: 'diffAddedDimmed';
|
|
336
|
+
const wordColor = tone === 'old' ? 'diffRemovedWord' : 'diffAddedWord';
|
|
337
|
+
// Status gutter instead of line numbers: ToolFileDiff has no file
|
|
338
|
+
// offsets, and an invented number misleads (issue #250, P2-3).
|
|
339
|
+
const marker = kind === 'context' ? ' ' : tone === 'old' ? '−' : '+';
|
|
340
|
+
const prefix = padLeft ? ` ${marker}` : marker;
|
|
341
|
+
return (_jsxs(Box, { width: width, flexShrink: 0, backgroundColor: backgroundColor, children: [_jsx(Text, { dimColor: true, backgroundColor: backgroundColor, children: `${prefix} ` }), side === undefined ? (_jsx(Text, { backgroundColor: backgroundColor, children: " " })) : (_jsx(Text, { wrap: "truncate", backgroundColor: backgroundColor, children: side.segments.map((segment, index) => segment.changed ? (_jsx(Text, { color: wordColor, bold: true, backgroundColor: backgroundColor, children: segment.text }, index)) : (_jsx(Text, { color: segment.color, backgroundColor: backgroundColor, children: segment.text }, index))) }))] }));
|
|
342
|
+
}
|
|
343
|
+
export function SplitDiffView({ diffs, width, maxRows, verbose, }) {
|
|
344
|
+
const [hl, setHl] = React.useState(null);
|
|
345
|
+
React.useEffect(() => {
|
|
346
|
+
let mounted = true;
|
|
347
|
+
void getCliHighlightPromise().then(loaded => {
|
|
348
|
+
if (mounted)
|
|
349
|
+
setHl(loaded);
|
|
350
|
+
});
|
|
351
|
+
return () => { mounted = false; };
|
|
352
|
+
}, []);
|
|
353
|
+
const [themeName] = useTheme();
|
|
354
|
+
// Resolved-palette signature: covers dark→light, light→dark, AND the
|
|
355
|
+
// `auto` base flip where the name never changes (issue #250, P1-2).
|
|
356
|
+
const themeSig = React.useMemo(() => {
|
|
357
|
+
const theme = getTheme(themeName);
|
|
358
|
+
return Object.values(SYNTAX_CLASS_TO_TOKEN)
|
|
359
|
+
.map(key => theme[key] ?? '')
|
|
360
|
+
.join('|');
|
|
361
|
+
}, [themeName]);
|
|
362
|
+
const chTheme = React.useMemo(() => {
|
|
363
|
+
const theme = getTheme(themeName);
|
|
364
|
+
const out = {};
|
|
365
|
+
for (const [tokenClass, tokenKey] of Object.entries(SYNTAX_CLASS_TO_TOKEN)) {
|
|
366
|
+
const value = theme[tokenKey];
|
|
367
|
+
if (value !== undefined)
|
|
368
|
+
out[tokenClass] = chalkFromToken(value);
|
|
369
|
+
}
|
|
370
|
+
return out;
|
|
371
|
+
// themeSig changes exactly when any syntax token's resolved value does.
|
|
372
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
373
|
+
}, [themeSig]);
|
|
374
|
+
// Alignment is text-only and capped BEFORE styling: the highlighter and
|
|
375
|
+
// word-diff work lands only on the visible slice (issue #250, P2-8).
|
|
376
|
+
const rows = [];
|
|
377
|
+
let prevPath;
|
|
378
|
+
diffs.forEach((diff, fileIndex) => {
|
|
379
|
+
if (diffs.length > 1) {
|
|
380
|
+
if (diff.path !== prevPath)
|
|
381
|
+
rows.push({ separator: diff.path });
|
|
382
|
+
else
|
|
383
|
+
rows.push({ separator: '⋯' });
|
|
384
|
+
}
|
|
385
|
+
prevPath = diff.path;
|
|
386
|
+
rows.push(...alignFileDiff(fileIndex, diff.oldText, diff.newText));
|
|
387
|
+
});
|
|
388
|
+
const totalRows = rows.length;
|
|
389
|
+
const capped = verbose || totalRows <= maxRows || totalRows - maxRows === 1;
|
|
390
|
+
const visible = capped ? rows : rows.slice(0, maxRows);
|
|
391
|
+
const hidden = totalRows - visible.length;
|
|
392
|
+
const paneWidth = Math.max(20, Math.floor((width - 1) / 2));
|
|
393
|
+
// Whole-hunk highlight per file (multi-line lexer state preserved);
|
|
394
|
+
// only the visible rows merge syntax runs with word flags below.
|
|
395
|
+
const fileSyntax = diffs.map(diff => {
|
|
396
|
+
const language = extname(diff.path).replace(/^\./, '') || undefined;
|
|
397
|
+
return {
|
|
398
|
+
old: highlightLines(expandTabs(diff.oldText ?? ''), language, hl, chTheme, themeSig),
|
|
399
|
+
next: highlightLines(expandTabs(diff.newText), language, hl, chTheme, themeSig),
|
|
400
|
+
};
|
|
401
|
+
});
|
|
402
|
+
return (_jsxs(Box, { flexDirection: "column", width: paneWidth * 2 + 1, backgroundColor: "toolCardBackgroundDim", children: [visible.map((row, index) => {
|
|
403
|
+
if ('separator' in row) {
|
|
404
|
+
return (_jsx(Box, { width: paneWidth * 2 + 1, children: _jsx(Text, { dimColor: true, wrap: "truncate", children: row.separator === '⋯' ? '⋯' : ` ${row.separator}` }) }, index));
|
|
405
|
+
}
|
|
406
|
+
const syntax = fileSyntax[row.fileIndex];
|
|
407
|
+
const oldRuns = row.oldIndex !== undefined ? syntax?.old?.[row.oldIndex] : undefined;
|
|
408
|
+
const newRuns = row.newIndex !== undefined ? syntax?.next?.[row.newIndex] : undefined;
|
|
409
|
+
const oldSide = row.oldWords === undefined
|
|
410
|
+
? undefined
|
|
411
|
+
: { segments: oldRuns !== undefined ? mergeRuns(oldRuns, row.oldWords) : row.oldWords };
|
|
412
|
+
const newSide = row.newWords === undefined
|
|
413
|
+
? undefined
|
|
414
|
+
: { segments: newRuns !== undefined ? mergeRuns(newRuns, row.newWords) : row.newWords };
|
|
415
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(PaneLine, { side: oldSide, kind: row.kind === 'add' ? 'context' : row.kind, tone: "old", width: paneWidth }), _jsx(Box, { width: 1, flexShrink: 0, backgroundColor: "toolCardBackgroundDim", children: _jsx(Text, { dimColor: true, children: "\u2502" }) }), _jsx(PaneLine, { side: newSide, kind: row.kind === 'del' ? 'context' : row.kind, tone: "new", width: paneWidth, padLeft: true })] }, index));
|
|
416
|
+
}), hidden > 0 && (_jsx(Text, { dimColor: true, children: `… +${hidden} lines (ctrl+o to expand)` }))] }));
|
|
417
|
+
}
|
|
@@ -8,10 +8,12 @@ import type { SpinnerMode } from './Spinner/spinnerMode.js';
|
|
|
8
8
|
*
|
|
9
9
|
* Random verb is picked once per turn (per mount of the spinner).
|
|
10
10
|
*/
|
|
11
|
-
export declare function WorkingSpinner({ mode, hasActiveTools, responseLengthRef, loadingStartTimeRef, totalPausedMsRef, pauseStartTimeRef, thinkingStatus, }: {
|
|
11
|
+
export declare function WorkingSpinner({ mode, hasActiveTools, responseLengthRef, uploadTokensRef, loadingStartTimeRef, totalPausedMsRef, pauseStartTimeRef, thinkingStatus, }: {
|
|
12
12
|
mode: SpinnerMode;
|
|
13
13
|
hasActiveTools: boolean;
|
|
14
14
|
responseLengthRef: React.RefObject<number>;
|
|
15
|
+
/** Most recent request's real upload tokens; 0 until the first usage event. */
|
|
16
|
+
uploadTokensRef: React.RefObject<number>;
|
|
15
17
|
loadingStartTimeRef: React.RefObject<number>;
|
|
16
18
|
totalPausedMsRef: React.RefObject<number>;
|
|
17
19
|
pauseStartTimeRef: React.RefObject<number | null>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkingSpinner.d.ts","sourceRoot":"","sources":["../../../src/components/WorkingSpinner.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAA;AAG1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAK3D;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,cAAc,EACd,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,GACf,EAAE;IACD,IAAI,EAAE,WAAW,CAAA;IACjB,cAAc,EAAE,OAAO,CAAA;IACvB,iBAAiB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IAC1C,mBAAmB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IAC5C,gBAAgB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IACzC,iBAAiB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACjD,cAAc,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAAA;CAC3C,GAAG,KAAK,CAAC,SAAS,
|
|
1
|
+
{"version":3,"file":"WorkingSpinner.d.ts","sourceRoot":"","sources":["../../../src/components/WorkingSpinner.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAA;AAG1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAK3D;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,GACf,EAAE;IACD,IAAI,EAAE,WAAW,CAAA;IACjB,cAAc,EAAE,OAAO,CAAA;IACvB,iBAAiB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IAC1C,+EAA+E;IAC/E,eAAe,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IACxC,mBAAmB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IAC5C,gBAAgB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IACzC,iBAAiB,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACjD,cAAc,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAAA;CAC3C,GAAG,KAAK,CAAC,SAAS,CA4BlB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,OAAO,GAClB,UAAU,GAAG,MAAM,GAAG,IAAI,CA6C5B"}
|
|
@@ -13,12 +13,12 @@ import { sample } from 'lodash-es';
|
|
|
13
13
|
*
|
|
14
14
|
* Random verb is picked once per turn (per mount of the spinner).
|
|
15
15
|
*/
|
|
16
|
-
export function WorkingSpinner({ mode, hasActiveTools, responseLengthRef, loadingStartTimeRef, totalPausedMsRef, pauseStartTimeRef, thinkingStatus, }) {
|
|
16
|
+
export function WorkingSpinner({ mode, hasActiveTools, responseLengthRef, uploadTokensRef, loadingStartTimeRef, totalPausedMsRef, pauseStartTimeRef, thinkingStatus, }) {
|
|
17
17
|
const { columns } = useTerminalSize();
|
|
18
18
|
// Pick a random verb once per spinner mount (per turn).
|
|
19
19
|
const [randomVerb] = useState(() => sample(SPINNER_VERBS) ?? 'Working');
|
|
20
20
|
const message = `${randomVerb}…`;
|
|
21
|
-
return (_jsx(Box, { flexDirection: "column", width: "100%", alignItems: "flex-start", children: _jsx(SpinnerAnimationRow, { mode: mode, reducedMotion: false, hasActiveTools: hasActiveTools, responseLengthRef: responseLengthRef, message: message, messageColor: "claude", shimmerColor: "claudeShimmer", loadingStartTimeRef: loadingStartTimeRef, totalPausedMsRef: totalPausedMsRef, pauseStartTimeRef: pauseStartTimeRef, spinnerSuffix: null, verbose: true, columns: columns, thinkingStatus: thinkingStatus }) }));
|
|
21
|
+
return (_jsx(Box, { flexDirection: "column", width: "100%", alignItems: "flex-start", children: _jsx(SpinnerAnimationRow, { mode: mode, reducedMotion: false, hasActiveTools: hasActiveTools, responseLengthRef: responseLengthRef, uploadTokensRef: uploadTokensRef, message: message, messageColor: "claude", shimmerColor: "claudeShimmer", loadingStartTimeRef: loadingStartTimeRef, totalPausedMsRef: totalPausedMsRef, pauseStartTimeRef: pauseStartTimeRef, spinnerSuffix: null, verbose: true, columns: columns, thinkingStatus: thinkingStatus }) }));
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
24
|
* Tracks thinking status: 'thinking' while the model is streaming reasoning,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { Color, Styles } from '../../ink/styles.js';
|
|
3
|
+
import type { DOMElement } from '../../ink/dom.js';
|
|
3
4
|
import { type Theme } from '../../theme.js';
|
|
4
5
|
/**
|
|
5
6
|
* Colors uncolored ThemedText in the subtree. Precedence: explicit `color` >
|
|
@@ -45,6 +46,11 @@ export type Props = {
|
|
|
45
46
|
* This property tells Ink to wrap or truncate text if its width is larger than container.
|
|
46
47
|
*/
|
|
47
48
|
readonly wrap?: Styles['textWrap'];
|
|
49
|
+
/**
|
|
50
|
+
* Ref to the underlying ink-text DOMElement (e.g. useDeclaredCursor
|
|
51
|
+
* parking the native cursor on an inline caret cell).
|
|
52
|
+
*/
|
|
53
|
+
readonly ref?: React.Ref<DOMElement>;
|
|
48
54
|
readonly children?: React.ReactNode;
|
|
49
55
|
};
|
|
50
56
|
/**
|
|
@@ -52,5 +58,5 @@ export type Props = {
|
|
|
52
58
|
* (in the Claude Code visual language). This is what lets every ported CC
|
|
53
59
|
* component use `color="subtle"`-style theme keys unchanged.
|
|
54
60
|
*/
|
|
55
|
-
export default function ThemedText({ color, backgroundColor, dimColor, bold, italic, underline, strikethrough, inverse, wrap, children, }: Props): React.ReactNode;
|
|
61
|
+
export default function ThemedText({ color, backgroundColor, dimColor, bold, italic, underline, strikethrough, inverse, wrap, ref, children, }: Props): React.ReactNode;
|
|
56
62
|
//# sourceMappingURL=ThemedText.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThemedText.d.ts","sourceRoot":"","sources":["../../../../src/components/design-system/ThemedText.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EAAY,KAAK,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAGrD;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,wCAEtB,CAAA;AAqBZ,MAAM,MAAM,KAAK,GAAG;IAClB;;OAEG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,GAAG,KAAK,CAAA;IAEpC;;OAEG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,KAAK,GAAG,KAAK,CAAA;IAE9C;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAE3B;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;IAEvB;;OAEG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IAEzB;;OAEG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;IAE5B;;OAEG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAA;IAEhC;;OAEG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAE1B;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,CAAA;IAElC,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACpC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,EACjC,KAAK,EACL,eAAe,EACf,QAAgB,EAChB,IAAY,EACZ,MAAc,EACd,SAAiB,EACjB,aAAqB,EACrB,OAAe,EACf,IAAa,EACb,QAAQ,GACT,EAAE,KAAK,GAAG,KAAK,CAAC,SAAS,
|
|
1
|
+
{"version":3,"file":"ThemedText.d.ts","sourceRoot":"","sources":["../../../../src/components/design-system/ThemedText.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,EAAY,KAAK,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAGrD;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,wCAEtB,CAAA;AAqBZ,MAAM,MAAM,KAAK,GAAG;IAClB;;OAEG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,GAAG,KAAK,CAAA;IAEpC;;OAEG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,KAAK,GAAG,KAAK,CAAA;IAE9C;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAE3B;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;IAEvB;;OAEG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IAEzB;;OAEG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;IAE5B;;OAEG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAA;IAEhC;;OAEG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAE1B;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,CAAA;IAElC;;;OAGG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;IAEpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACpC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,EACjC,KAAK,EACL,eAAe,EACf,QAAgB,EAChB,IAAY,EACZ,MAAc,EACd,SAAiB,EACjB,aAAqB,EACrB,OAAe,EACf,IAAa,EACb,GAAG,EACH,QAAQ,GACT,EAAE,KAAK,GAAG,KAAK,CAAC,SAAS,CA6BzB"}
|
|
@@ -28,7 +28,7 @@ function resolveColor(color, theme) {
|
|
|
28
28
|
* (in the Claude Code visual language). This is what lets every ported CC
|
|
29
29
|
* component use `color="subtle"`-style theme keys unchanged.
|
|
30
30
|
*/
|
|
31
|
-
export default function ThemedText({ color, backgroundColor, dimColor = false, bold = false, italic = false, underline = false, strikethrough = false, inverse = false, wrap = 'wrap', children, }) {
|
|
31
|
+
export default function ThemedText({ color, backgroundColor, dimColor = false, bold = false, italic = false, underline = false, strikethrough = false, inverse = false, wrap = 'wrap', ref, children, }) {
|
|
32
32
|
const [themeName] = useTheme();
|
|
33
33
|
const theme = getTheme(themeName);
|
|
34
34
|
const hoverColor = React.useContext(TextHoverColorContext);
|
|
@@ -39,5 +39,5 @@ export default function ThemedText({ color, backgroundColor, dimColor = false, b
|
|
|
39
39
|
? theme.inactive
|
|
40
40
|
: resolveColor(color, theme);
|
|
41
41
|
const resolvedBackgroundColor = resolveColor(backgroundColor, theme);
|
|
42
|
-
return (_jsx(Text, { color: resolvedColor, backgroundColor: resolvedBackgroundColor, bold: bold, italic: italic, underline: underline, strikethrough: strikethrough, inverse: inverse, wrap: wrap, children: children }));
|
|
42
|
+
return (_jsx(Text, { ref: ref, color: resolvedColor, backgroundColor: resolvedBackgroundColor, bold: bold, italic: italic, underline: underline, strikethrough: strikethrough, inverse: inverse, wrap: wrap, children: children }));
|
|
43
43
|
}
|
|
@@ -19,6 +19,8 @@ type Props = {
|
|
|
19
19
|
* is worth less than mentioning it once.
|
|
20
20
|
*/
|
|
21
21
|
footnote?: string;
|
|
22
|
+
/** Diff presentation preference; `auto` picks by terminal width. */
|
|
23
|
+
diffLayout?: 'auto' | 'split' | 'unified';
|
|
22
24
|
};
|
|
23
25
|
/**
|
|
24
26
|
* Tool-call card: `● Edit /path` header with a blinking status dot, then the
|
|
@@ -26,6 +28,6 @@ type Props = {
|
|
|
26
28
|
* output, read content — instead of the raw result dump (mirroring Claude Code's `AssistantToolUseMessage.tsx` + the dsh-tools presentation views the
|
|
27
29
|
* channel captures per call).
|
|
28
30
|
*/
|
|
29
|
-
export declare function AssistantToolUseMessage({ tool, addMargin, verbose, isSelected, isExpanded, footnote, }: Props): React.ReactNode;
|
|
31
|
+
export declare function AssistantToolUseMessage({ tool, addMargin, verbose, isSelected, isExpanded, footnote, diffLayout, }: Props): React.ReactNode;
|
|
30
32
|
export {};
|
|
31
33
|
//# sourceMappingURL=AssistantToolUseMessage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssistantToolUseMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/messages/AssistantToolUseMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB,OAAO,KAAK,EAA8C,OAAO,EAAE,MAAM,8BAA8B,CAAA;
|
|
1
|
+
{"version":3,"file":"AssistantToolUseMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/messages/AssistantToolUseMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB,OAAO,KAAK,EAA8C,OAAO,EAAE,MAAM,8BAA8B,CAAA;AAKvG,KAAK,KAAK,GAAG;IACX,IAAI,EAAE,OAAO,CAAA;IACb,4DAA4D;IAC5D,SAAS,EAAE,OAAO,CAAA;IAClB,iEAAiE;IACjE,OAAO,EAAE,OAAO,CAAA;IAChB,wCAAwC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,sEAAsE;IACtE,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;CAC1C,CAAA;AAyMD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,IAAI,EACJ,SAAS,EACT,OAAO,EACP,UAAkB,EAClB,UAAkB,EAClB,QAAQ,EACR,UAAmB,GACpB,EAAE,KAAK,GAAG,KAAK,CAAC,SAAS,CAoIzB"}
|