@bubblebrain-ai/bubble 0.0.33 → 0.0.35
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/dist/agent/abort-errors.d.ts +2 -2
- package/dist/agent/budget-ledger.d.ts +4 -45
- package/dist/agent/budget-ledger.js +5 -75
- package/dist/agent/child-runner.d.ts +1 -3
- package/dist/agent/child-runner.js +6 -37
- package/dist/agent/profiles.d.ts +0 -2
- package/dist/agent/profiles.js +0 -3
- package/dist/agent/subagent-control.d.ts +5 -12
- package/dist/agent/subagent-control.js +0 -2
- package/dist/agent/subagent-store.d.ts +1 -1
- package/dist/agent/subagent-store.js +1 -3
- package/dist/agent/workflow/control.d.ts +7 -0
- package/dist/agent/workflow/control.js +22 -1
- package/dist/agent/workflow/runtime.d.ts +15 -2
- package/dist/agent/workflow/runtime.js +112 -30
- package/dist/agent.d.ts +7 -43
- package/dist/agent.js +50 -176
- package/dist/config.d.ts +1 -3
- package/dist/config.js +6 -5
- package/dist/context/overflow.js +2 -0
- package/dist/main.js +9 -1
- package/dist/model-catalog.d.ts +1 -1
- package/dist/model-catalog.js +25 -18
- package/dist/orchestrator/default-hooks.js +7 -1
- package/dist/prompt/delegation.d.ts +1 -1
- package/dist/prompt/delegation.js +21 -7
- package/dist/prompt/task-reminders.d.ts +1 -0
- package/dist/prompt/task-reminders.js +24 -1
- package/dist/provider-ai-sdk.d.ts +56 -0
- package/dist/provider-ai-sdk.js +518 -0
- package/dist/provider-registry.js +49 -3
- package/dist/provider-transform.d.ts +1 -1
- package/dist/provider-transform.js +8 -13
- package/dist/provider.js +4 -0
- package/dist/slash-commands/commands.js +3 -7
- package/dist/slash-commands/types.d.ts +1 -1
- package/dist/tools/agent-lifecycle.d.ts +1 -9
- package/dist/tools/agent-lifecycle.js +35 -263
- package/dist/tui-ink/app.js +51 -13
- package/dist/tui-ink/approval/approval-dialog.js +1 -1
- package/dist/tui-ink/approval/diff-view.js +4 -4
- package/dist/tui-ink/approval/select.js +2 -2
- package/dist/tui-ink/feedback-dialog.js +2 -2
- package/dist/tui-ink/feishu-setup-picker.js +3 -3
- package/dist/tui-ink/input-box.d.ts +1 -6
- package/dist/tui-ink/input-box.js +14 -35
- package/dist/tui-ink/markdown.js +11 -9
- package/dist/tui-ink/message-list.d.ts +5 -1
- package/dist/tui-ink/message-list.js +15 -2
- package/dist/tui-ink/model-picker.d.ts +3 -1
- package/dist/tui-ink/model-picker.js +25 -14
- package/dist/tui-ink/plan-confirm.js +1 -1
- package/dist/tui-ink/subagent-view.d.ts +3 -1
- package/dist/tui-ink/theme.d.ts +19 -2
- package/dist/tui-ink/theme.js +25 -8
- package/dist/tui-ink/welcome.d.ts +3 -2
- package/dist/tui-ink/welcome.js +10 -6
- package/dist/types.d.ts +16 -30
- package/dist/variant/variant-resolver.d.ts +13 -0
- package/dist/variant/variant-resolver.js +26 -0
- package/package.json +9 -3
|
@@ -234,7 +234,7 @@ function renderBody(stage, theme) {
|
|
|
234
234
|
case "done":
|
|
235
235
|
return (_jsx(Box, { flexDirection: "column", children: stage.summary.split("\n").map((line, i) => (_jsx(Text, { children: line }, i))) }));
|
|
236
236
|
case "error":
|
|
237
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color:
|
|
237
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: theme.error, children: ["\u274C ", stage.message] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.muted, children: "\u6309 Enter \u5173\u95ED\u3002\u53EF\u4EE5\u7A0D\u540E\u518D /feishu setup \u91CD\u8BD5\u3002" }) })] }));
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
function BindingForm({ stage, theme }) {
|
|
@@ -258,8 +258,8 @@ function BindingForm({ stage, theme }) {
|
|
|
258
258
|
const isActive = stage.field === field;
|
|
259
259
|
const isDone = !isActive && value && fieldOrderIndex(stage.field) > fieldOrderIndex(field);
|
|
260
260
|
const marker = isActive ? "› " : isDone ? "✓ " : " ";
|
|
261
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: isActive ? 1 : 0, children: [_jsxs(Box, { children: [_jsxs(Text, { color: isActive ? theme.accent : isDone ?
|
|
262
|
-
}), stage.error && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color:
|
|
261
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: isActive ? 1 : 0, children: [_jsxs(Box, { children: [_jsxs(Text, { color: isActive ? theme.accent : isDone ? theme.success : theme.muted, children: [marker, meta.label, ":"] }), _jsx(Box, { marginLeft: 1, children: _jsxs(Text, { children: [value, isActive ? "▌" : ""] }) })] }), isActive && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: theme.muted, children: meta.hint }) }))] }, field));
|
|
262
|
+
}), stage.error && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.error, children: stage.error }) }))] }));
|
|
263
263
|
}
|
|
264
264
|
function fieldOrderIndex(field) {
|
|
265
265
|
return field === "chatId" ? 0 : field === "cwd" ? 1 : 2;
|
|
@@ -53,8 +53,6 @@ export declare function isCtrlLetterInput(input: string, key: {
|
|
|
53
53
|
export declare function isCtrlCInput(input: string, key: {
|
|
54
54
|
ctrl?: boolean;
|
|
55
55
|
}): boolean;
|
|
56
|
-
export declare function shouldUseLineComposerFrame(_background: string): boolean;
|
|
57
|
-
export declare function composerSurfaceBackground(lineFrame: boolean, background: string, inputBg: string): string;
|
|
58
56
|
export declare function shouldUseHardwareComposerCursor(env?: Record<string, string | undefined>): boolean;
|
|
59
57
|
export declare function composerVerticalArrowDirection(key: {
|
|
60
58
|
upArrow?: boolean;
|
|
@@ -63,12 +61,9 @@ export declare function composerVerticalArrowDirection(key: {
|
|
|
63
61
|
}): "up" | "down" | undefined;
|
|
64
62
|
export declare function resolveSoftwareCursorCellStyle(input: {
|
|
65
63
|
visible: boolean;
|
|
66
|
-
cursorBackground: string;
|
|
67
|
-
cursorForeground: string;
|
|
68
64
|
textColor: string;
|
|
69
|
-
rowBackground?: string;
|
|
70
65
|
}): {
|
|
71
|
-
|
|
66
|
+
inverse: boolean;
|
|
72
67
|
color: string;
|
|
73
68
|
};
|
|
74
69
|
/**
|
|
@@ -14,7 +14,6 @@ import { submitPayloadFingerprint } from "./submit-dedupe.js";
|
|
|
14
14
|
export { createPastedContentMarker, expandPastedContentMarkers, shouldCollapsePastedContent, } from "../tui/paste-placeholder.js";
|
|
15
15
|
import { createPastedContentMarker, expandPastedContentMarkers, shouldCollapsePastedContent, } from "../tui/paste-placeholder.js";
|
|
16
16
|
import { imageDisplayLabel, stripInlineImageLabels } from "../tui/image-display.js";
|
|
17
|
-
const MIN_VISIBLE_LINES = 3;
|
|
18
17
|
const MAX_VISIBLE_LINES = 6;
|
|
19
18
|
const PADDING_X = 1;
|
|
20
19
|
const PROMPT = " > ";
|
|
@@ -47,12 +46,6 @@ export function isCtrlLetterInput(input, key, letter) {
|
|
|
47
46
|
export function isCtrlCInput(input, key) {
|
|
48
47
|
return isCtrlLetterInput(input, key, "c");
|
|
49
48
|
}
|
|
50
|
-
export function shouldUseLineComposerFrame(_background) {
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
export function composerSurfaceBackground(lineFrame, background, inputBg) {
|
|
54
|
-
return lineFrame ? background : inputBg;
|
|
55
|
-
}
|
|
56
49
|
export function shouldUseHardwareComposerCursor(env = process.env) {
|
|
57
50
|
return env.BUBBLE_HARDWARE_CURSOR === "1";
|
|
58
51
|
}
|
|
@@ -64,16 +57,10 @@ export function composerVerticalArrowDirection(key) {
|
|
|
64
57
|
return undefined;
|
|
65
58
|
}
|
|
66
59
|
export function resolveSoftwareCursorCellStyle(input) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
return {
|
|
74
|
-
backgroundColor: input.rowBackground,
|
|
75
|
-
color: input.textColor,
|
|
76
|
-
};
|
|
60
|
+
// Inverse video swaps the cell's real foreground/background at the terminal
|
|
61
|
+
// level, so the cursor block stays visible on any terminal palette without
|
|
62
|
+
// guessing a contrasting pair.
|
|
63
|
+
return { inverse: input.visible, color: input.textColor };
|
|
77
64
|
}
|
|
78
65
|
/**
|
|
79
66
|
* Split a composer line around the cursor so the cell under it can render as
|
|
@@ -995,8 +982,7 @@ export function InputBox({ onSubmit, onQueue, onPasteNotice, disabled, onArrowDo
|
|
|
995
982
|
const classifyVerticalArrow = (direction) => {
|
|
996
983
|
performVerticalArrowRef.current(direction);
|
|
997
984
|
};
|
|
998
|
-
const
|
|
999
|
-
const minVisibleLines = lineFrame ? 1 : MIN_VISIBLE_LINES;
|
|
985
|
+
const minVisibleLines = 1;
|
|
1000
986
|
const totalLines = Math.max(visualLines.length, 1);
|
|
1001
987
|
const visibleLines = Math.min(Math.max(totalLines, minVisibleLines), MAX_VISIBLE_LINES);
|
|
1002
988
|
let scrollOffset = 0;
|
|
@@ -1130,25 +1116,18 @@ export function InputBox({ onSubmit, onQueue, onPasteNotice, disabled, onArrowDo
|
|
|
1130
1116
|
});
|
|
1131
1117
|
// Reference cursorTick so the effect re-runs on the forced render pass.
|
|
1132
1118
|
void cursorTick;
|
|
1133
|
-
const inputBg = disabled ? theme.inputBgDisabled : theme.inputBg;
|
|
1134
|
-
const composerBg = composerSurfaceBackground(lineFrame, theme.background, inputBg);
|
|
1135
|
-
const rowBg = lineFrame ? undefined : inputBg;
|
|
1136
|
-
const cursorFg = lineFrame ? theme.background : inputBg;
|
|
1137
1119
|
const cursorCellStyle = resolveSoftwareCursorCellStyle({
|
|
1138
1120
|
visible: softwareCursorVisible,
|
|
1139
|
-
cursorBackground: theme.inputText,
|
|
1140
|
-
cursorForeground: cursorFg,
|
|
1141
1121
|
textColor: theme.inputText,
|
|
1142
|
-
rowBackground: rowBg,
|
|
1143
1122
|
});
|
|
1144
1123
|
const moreBelow = totalLines - scrollOffset - visibleLines;
|
|
1145
1124
|
const filledLine = (value) => {
|
|
1146
1125
|
const visibleWidth = visualWidth(value);
|
|
1147
1126
|
return value + " ".repeat(Math.max(0, contentWidth - visibleWidth));
|
|
1148
1127
|
};
|
|
1149
|
-
return (_jsxs(Box, { flexDirection: "column", width: width, backgroundColor: theme.background, children: [
|
|
1128
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, backgroundColor: theme.background, children: [_jsx(Box, { paddingX: PADDING_X, children: _jsx(Text, { color: theme.border, children: "─".repeat(contentWidth) }) }), _jsxs(Box, { flexDirection: "column", paddingX: PADDING_X, width: width, backgroundColor: theme.background, children: [hasMoreAbove && (_jsx(Text, { color: theme.muted, dimColor: true, children: filledLine(` ↑ ${scrollOffset} more`) })), displayedLines.map((row) => {
|
|
1150
1129
|
if (row.kind === "pad") {
|
|
1151
|
-
return (_jsx(Text, {
|
|
1130
|
+
return (_jsx(Text, { children: " ".repeat(contentWidth) }, row.key));
|
|
1152
1131
|
}
|
|
1153
1132
|
const { text: line, visualIdx } = row;
|
|
1154
1133
|
const visualLine = visualLines[visualIdx];
|
|
@@ -1167,17 +1146,17 @@ export function InputBox({ onSubmit, onQueue, onPasteNotice, disabled, onArrowDo
|
|
|
1167
1146
|
});
|
|
1168
1147
|
const renderedLine = renderedSegments.map((segment) => segment.text).join("");
|
|
1169
1148
|
const fill = " ".repeat(Math.max(0, lineWidth - visualWidth(renderedLine)));
|
|
1170
|
-
return (_jsxs(Box, { height: 1, overflow: "hidden", backgroundColor:
|
|
1149
|
+
return (_jsxs(Box, { height: 1, overflow: "hidden", backgroundColor: theme.background, ref: isCursorLine
|
|
1171
1150
|
? (el) => {
|
|
1172
1151
|
cursorLineRef.current = el;
|
|
1173
1152
|
}
|
|
1174
|
-
: undefined, children: [_jsx(Text, {
|
|
1153
|
+
: undefined, children: [_jsx(Text, { color: isFirst ? theme.accent : theme.inputText, children: prompt }), renderedSegments.map((segment, index) => {
|
|
1175
1154
|
if (segment.kind === "cursor") {
|
|
1176
|
-
return (_jsx(Text, {
|
|
1155
|
+
return (_jsx(Text, { inverse: cursorCellStyle.inverse, color: cursorCellStyle.color, children: segment.text }, index));
|
|
1177
1156
|
}
|
|
1178
|
-
return (_jsx(Text, {
|
|
1179
|
-
}), _jsx(Text, {
|
|
1180
|
-
}), hasMoreBelow && (_jsx(Text, {
|
|
1157
|
+
return (_jsx(Text, { color: segment.kind === "command" ? theme.accent : theme.inputText, bold: segment.kind === "command", children: segment.text }, index));
|
|
1158
|
+
}), _jsx(Text, { children: fill })] }, visualIdx));
|
|
1159
|
+
}), hasMoreBelow && (_jsx(Text, { color: theme.muted, dimColor: true, children: filledLine(` ↓ ${moreBelow} more`) }))] }), _jsx(Box, { paddingX: PADDING_X, children: _jsx(Text, { color: theme.border, children: "─".repeat(contentWidth) }) }), showSuggestions && mode === "slash" && (_jsxs(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 4, children: [slashSuggestions
|
|
1181
1160
|
.slice(suggestionOffset, suggestionOffset + MAX_VISIBLE_SUGGESTIONS)
|
|
1182
1161
|
.map((cmd, visibleIndex) => {
|
|
1183
1162
|
const i = suggestionOffset + visibleIndex;
|
|
@@ -1190,6 +1169,6 @@ export function InputBox({ onSubmit, onQueue, onPasteNotice, disabled, onArrowDo
|
|
|
1190
1169
|
const i = suggestionOffset + visibleIndex;
|
|
1191
1170
|
const maxWidth = Math.max(10, Math.min(80, contentWidth - 2));
|
|
1192
1171
|
const label = s.path.length > maxWidth ? "…" + s.path.slice(-(maxWidth - 1)) : s.path;
|
|
1193
|
-
return (_jsx(Box, { height: 1, children: i === selectedIndex ? (_jsx(Text, {
|
|
1172
|
+
return (_jsx(Box, { height: 1, children: i === selectedIndex ? (_jsx(Text, { color: theme.accent, bold: true, children: `› ${label}` })) : (_jsx(Text, { children: ` ${label}` })) }, s.path));
|
|
1194
1173
|
}), fileSuggestions.length > MAX_VISIBLE_SUGGESTIONS && (_jsx(Text, { color: theme.muted, children: `Showing ${suggestionOffset + 1}-${Math.min(suggestionOffset + MAX_VISIBLE_SUGGESTIONS, fileSuggestions.length)} of ${fileSuggestions.length}` }))] }))] }));
|
|
1195
1174
|
}
|
package/dist/tui-ink/markdown.js
CHANGED
|
@@ -320,16 +320,17 @@ function appendInlineSegment(segments, text, style) {
|
|
|
320
320
|
segments.push(next);
|
|
321
321
|
}
|
|
322
322
|
}
|
|
323
|
-
function renderSegmentNodes(segments, keyPrefix) {
|
|
324
|
-
return segments.map((segment, index) => (_jsx(Text, { bold: segment.bold, italic: segment.italic, color: segment.code ?
|
|
323
|
+
function renderSegmentNodes(segments, keyPrefix, codeColor) {
|
|
324
|
+
return segments.map((segment, index) => (_jsx(Text, { bold: segment.bold, italic: segment.italic, color: segment.code ? codeColor : undefined, children: segment.text }, `${keyPrefix}-${index}`)));
|
|
325
325
|
}
|
|
326
|
-
function renderInlineSegments(text, keyPrefix, style = {}) {
|
|
327
|
-
return renderSegmentNodes(parseMarkdownInlineSegments(text, style), keyPrefix);
|
|
326
|
+
function renderInlineSegments(text, keyPrefix, codeColor, style = {}) {
|
|
327
|
+
return renderSegmentNodes(parseMarkdownInlineSegments(text, style), keyPrefix, codeColor);
|
|
328
328
|
}
|
|
329
329
|
function inlinePlainText(text) {
|
|
330
330
|
return parseMarkdownInlineSegments(text).map((segment) => segment.text).join("");
|
|
331
331
|
}
|
|
332
332
|
function InlineText({ text, maxWidth }) {
|
|
333
|
+
const theme = useTheme();
|
|
333
334
|
const { columns } = useTerminalSize();
|
|
334
335
|
// Pre-wrap CJK-aware so Ink's space-only wrap-ansi can't chop "、"-joined runs
|
|
335
336
|
// mid-token. A caller-supplied maxWidth is exact (it already nets out the
|
|
@@ -338,10 +339,10 @@ function InlineText({ text, maxWidth }) {
|
|
|
338
339
|
// wrapping a few cells early is invisible, a mid-token chop is not.
|
|
339
340
|
const effectiveWidth = maxWidth ?? (columns ? Math.max(20, columns - 10) : undefined);
|
|
340
341
|
if (effectiveWidth === undefined) {
|
|
341
|
-
return _jsx(Text, { children: renderInlineSegments(text, "inline") });
|
|
342
|
+
return _jsx(Text, { children: renderInlineSegments(text, "inline", theme.inlineCode) });
|
|
342
343
|
}
|
|
343
344
|
const wrapped = wrapInlineSegments(parseMarkdownInlineSegments(text), effectiveWidth);
|
|
344
|
-
return (_jsx(_Fragment, { children: wrapped.map((segments, li) => (_jsx(Text, { children: segments.length ? renderSegmentNodes(segments, `inline-${li}
|
|
345
|
+
return (_jsx(_Fragment, { children: wrapped.map((segments, li) => (_jsx(Text, { children: segments.length ? renderSegmentNodes(segments, `inline-${li}`, theme.inlineCode) : " " }, li))) }));
|
|
345
346
|
}
|
|
346
347
|
function CodeBlock({ lang, lines }) {
|
|
347
348
|
const theme = useTheme();
|
|
@@ -378,6 +379,7 @@ function CodeBlock({ lang, lines }) {
|
|
|
378
379
|
return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [lang && _jsx(Text, { color: theme.muted, children: lang }), _jsx(Box, { flexDirection: "column", children: highlighted.map((line, i) => (_jsx(Text, { children: line || " " }, i))) })] }));
|
|
379
380
|
}
|
|
380
381
|
function TableBlock({ headers, rows, maxWidth, }) {
|
|
382
|
+
const theme = useTheme();
|
|
381
383
|
const { columns: termWidth } = useTerminalSize();
|
|
382
384
|
const colCount = headers.length;
|
|
383
385
|
// Reserve a buffer so the table fits even when wrapped inside an indented
|
|
@@ -425,7 +427,7 @@ function TableBlock({ headers, rows, maxWidth, }) {
|
|
|
425
427
|
return lines;
|
|
426
428
|
});
|
|
427
429
|
const height = Math.max(1, ...wrapped.map((lines) => lines.length));
|
|
428
|
-
return (_jsx(Box, { flexDirection: "column", children: Array.from({ length: height }, (_, line) => (_jsxs(Text, { children: [`${g.v} `, wrapped.map((cellLines, i) => (_jsxs(React.Fragment, { children: [renderCellLine(cellLines[line] ?? [], widths[i] ?? 4, `${keyPrefix}-cell-${i}-${line}
|
|
430
|
+
return (_jsx(Box, { flexDirection: "column", children: Array.from({ length: height }, (_, line) => (_jsxs(Text, { children: [`${g.v} `, wrapped.map((cellLines, i) => (_jsxs(React.Fragment, { children: [renderCellLine(cellLines[line] ?? [], widths[i] ?? 4, `${keyPrefix}-cell-${i}-${line}`, theme.inlineCode), i < colCount - 1 ? ` ${g.v} ` : ` ${g.v}`] }, i)))] }, `${keyPrefix}-line-${line}`))) }, keyPrefix));
|
|
429
431
|
};
|
|
430
432
|
return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: top }), renderRow(headers, "header", true), _jsx(Text, { children: mid }), rows.map((row, ri) => renderRow(row, `row-${ri}`)), _jsx(Text, { children: bot })] }));
|
|
431
433
|
}
|
|
@@ -491,10 +493,10 @@ export function allocateColumnWidths(natural, available) {
|
|
|
491
493
|
}
|
|
492
494
|
return widths;
|
|
493
495
|
}
|
|
494
|
-
function renderCellLine(segments, width, keyPrefix) {
|
|
496
|
+
function renderCellLine(segments, width, keyPrefix, codeColor) {
|
|
495
497
|
const padding = " ".repeat(Math.max(0, width - inlineSegmentsWidth(segments)));
|
|
496
498
|
return [
|
|
497
|
-
...segments
|
|
499
|
+
...renderSegmentNodes(segments, keyPrefix, codeColor),
|
|
498
500
|
_jsx(Text, { children: padding }, `${keyPrefix}-pad`),
|
|
499
501
|
];
|
|
500
502
|
}
|
|
@@ -31,7 +31,11 @@ interface MessageListProps {
|
|
|
31
31
|
* a freshly-cleared screen instead of appending duplicates.
|
|
32
32
|
*/
|
|
33
33
|
staticGeneration?: number;
|
|
34
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Horizontal padding applied inside each committed/streaming row. Wrap
|
|
36
|
+
* budgets net this out via TRANSCRIPT_ROW_PADDING (= both sides of the
|
|
37
|
+
* default of 1) — keep the two in sync if this ever changes.
|
|
38
|
+
*/
|
|
35
39
|
paddingX?: number;
|
|
36
40
|
/**
|
|
37
41
|
* Maximum height (rows) for the live/dynamic region. The in-progress turn is
|
|
@@ -12,6 +12,16 @@ import { latestSubagentNote, sortSubagents, subagentDescriptor, subagentLabel, s
|
|
|
12
12
|
import { sanitizeInternalReminderBlocks } from "../agent/internal-reminder-sanitizer.js";
|
|
13
13
|
import { splitImageDisplayContent } from "../tui/image-display.js";
|
|
14
14
|
const EXECUTE_COMMAND_BLOCK_MAX_LINES = 4;
|
|
15
|
+
/**
|
|
16
|
+
* Every transcript row — each committed <Static> item and the DynamicClamp
|
|
17
|
+
* live region — wraps its content in paddingX={1}: one column of inset on
|
|
18
|
+
* EACH side. Any wrap budget derived from terminalColumns inside a row must
|
|
19
|
+
* net out these two columns. Forgetting them hands the pre-wrapper a budget
|
|
20
|
+
* two columns wider than the real room, and a dense CJK line that packs flush
|
|
21
|
+
* against that budget renders past the terminal edge — the terminal then
|
|
22
|
+
* hard-wraps the last glyph onto a stray row at column 0.
|
|
23
|
+
*/
|
|
24
|
+
const TRANSCRIPT_ROW_PADDING = 2;
|
|
15
25
|
export function MessageList({ messages, streamingContent, streamingReasoning, streamingTools, streamingParts, terminalColumns, showThinking = false, verboseTrace, pendingApproval, nowTick, welcomeBanner, staticGeneration = 0, paddingX = 1, maxStreamRows, }) {
|
|
16
26
|
const theme = useTheme();
|
|
17
27
|
const hasStreaming = !!(streamingContent ||
|
|
@@ -195,7 +205,9 @@ function TimelineText({ content, compactTop, terminalColumns, streaming = false,
|
|
|
195
205
|
// cell right and would overflow. Reserve that extra cell up front so the
|
|
196
206
|
// pre-wrap never packs a line the terminal then hard-wraps.
|
|
197
207
|
const gutter = ambiguousIsWide() ? 6 : 5;
|
|
198
|
-
const available = terminalColumns
|
|
208
|
+
const available = terminalColumns
|
|
209
|
+
? Math.max(20, terminalColumns - gutter - TRANSCRIPT_ROW_PADDING)
|
|
210
|
+
: undefined;
|
|
199
211
|
const trimmed = visible.trim();
|
|
200
212
|
return (_jsxs(Box, { marginLeft: 2, marginTop: compactTop ? 0 : 1, children: [_jsx(Text, { color: theme.agent, children: "\u25CF " }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: streaming ? (_jsx(StreamingMarkdown, { content: trimmed, maxWidth: available })) : (_jsx(MarkdownContent, { content: trimmed, maxWidth: available })) })] }));
|
|
201
213
|
}
|
|
@@ -347,7 +359,8 @@ function UserMessageBlock({ content, terminalColumns, inputStatus, separateFromP
|
|
|
347
359
|
}
|
|
348
360
|
function PendingInputMessagesBlock({ messages, terminalColumns, title, hint, bulletColor, }) {
|
|
349
361
|
const theme = useTheme();
|
|
350
|
-
|
|
362
|
+
// Inset per row: marginLeft (2) + "↳ " (2) + the transcript row padding.
|
|
363
|
+
const contentWidth = Math.max(20, terminalColumns - 4 - TRANSCRIPT_ROW_PADDING);
|
|
351
364
|
return (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: bulletColor, children: "\u2022 " }), _jsxs(Text, { bold: true, color: theme.inputText, children: [title, " "] }), _jsxs(Text, { color: theme.dim, children: ["(", hint, ")"] })] }), messages.flatMap((message, messageIndex) => {
|
|
352
365
|
const { bodyLines, referenceLines } = splitImageDisplayContent(message.content || " ");
|
|
353
366
|
const wrappedBody = bodyLines.flatMap((line) => wrapByVisualWidth(line || " ", contentWidth));
|
|
@@ -65,8 +65,10 @@ export interface ProviderPickerProps {
|
|
|
65
65
|
onSelect: (providerId: string) => void;
|
|
66
66
|
onCancel: () => void;
|
|
67
67
|
title?: string;
|
|
68
|
+
/** Fires whenever the highlighted row changes (and once on mount) — lets callers live-preview the selection. */
|
|
69
|
+
onHighlight?: (providerId: string) => void;
|
|
68
70
|
}
|
|
69
|
-
export declare function ProviderPicker({ providers, current, onSelect, onCancel, title }: ProviderPickerProps): import("react/jsx-runtime").JSX.Element;
|
|
71
|
+
export declare function ProviderPicker({ providers, current, onSelect, onCancel, title, onHighlight }: ProviderPickerProps): import("react/jsx-runtime").JSX.Element;
|
|
70
72
|
export interface KeyPickerProps {
|
|
71
73
|
providerName: string;
|
|
72
74
|
onSubmit: (key: string) => void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect, useMemo } from "react";
|
|
3
3
|
import { Box, Text, useInput, usePaste, useStdout } from "ink";
|
|
4
4
|
import { isKeyReleaseEvent } from "./key-events.js";
|
|
@@ -7,7 +7,7 @@ import { encodeModel, decodeModel, displayModel, isUserVisibleProvider } from ".
|
|
|
7
7
|
import { listBuiltinModels } from "../model-catalog.js";
|
|
8
8
|
import { padVisual, truncateVisual } from "../text-display.js";
|
|
9
9
|
import { hasTerminalMouseSequence } from "./terminal-mouse.js";
|
|
10
|
-
import { getAvailableThinkingLevels, normalizeThinkingLevel } from "../provider-transform.js";
|
|
10
|
+
import { getAvailableThinkingLevels, isThinkingOnlyLevels, isThinkingToggleModel, normalizeThinkingLevel } from "../provider-transform.js";
|
|
11
11
|
export { padVisual, truncateVisual } from "../text-display.js";
|
|
12
12
|
export function resolvePickerKeyAction(input, key) {
|
|
13
13
|
if (key.escape)
|
|
@@ -78,25 +78,24 @@ export function padPickerRows(rows, bodyRows, width) {
|
|
|
78
78
|
}
|
|
79
79
|
return padded;
|
|
80
80
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// other 2-level models (e.g. GLM toggles) keep their effort labels.
|
|
85
|
-
function isMiniMaxToggleModel(modelId) {
|
|
86
|
-
return modelId.toLowerCase().includes("minimax");
|
|
81
|
+
function isThinkingToggleOption(option) {
|
|
82
|
+
const decoded = decodeModel(option.id);
|
|
83
|
+
return isThinkingToggleModel(decoded.providerId || option.providerBadge, decoded.modelId);
|
|
87
84
|
}
|
|
88
85
|
export function formatReasoningLevelsLabel(levels, asToggle = false) {
|
|
89
|
-
|
|
86
|
+
if (isThinkingOnlyLevels(levels))
|
|
87
|
+
return "on";
|
|
90
88
|
if (asToggle)
|
|
91
89
|
return "thinking on/off";
|
|
92
|
-
|
|
90
|
+
const normalized = levels.length > 0 ? levels : ["off"];
|
|
91
|
+
return normalized.join("/");
|
|
93
92
|
}
|
|
94
93
|
export function formatModelPickerRow(option, options) {
|
|
95
94
|
const width = Math.max(24, options.width);
|
|
96
95
|
const marker = options.selected ? "> " : " ";
|
|
97
96
|
const label = option.label.replace(/\s+/g, " ").trim();
|
|
98
97
|
const provider = option.providerBadge.replace(/\s+/g, " ").trim();
|
|
99
|
-
const effort = formatReasoningLevelsLabel(option.reasoningLevels,
|
|
98
|
+
const effort = formatReasoningLevelsLabel(option.reasoningLevels, isThinkingToggleOption(option));
|
|
100
99
|
const current = options.current ? " ●" : "";
|
|
101
100
|
const providerWidth = Math.max(6, Math.min(16, Math.floor(width * 0.18)));
|
|
102
101
|
const effortWidth = Math.max(12, Math.min(30, Math.floor(width * 0.32)));
|
|
@@ -345,7 +344,14 @@ export function ModelPicker({ registry, current, currentThinkingLevel, recent, o
|
|
|
345
344
|
row,
|
|
346
345
|
selected: rawModelRows[index]?.selected ?? false,
|
|
347
346
|
}));
|
|
348
|
-
|
|
347
|
+
const highlightedOption = options[clampPickerIndex(selectedIndex, options.length)];
|
|
348
|
+
const enterAction = highlightedOption && shouldOpenEffortPicker(highlightedOption)
|
|
349
|
+
? (isThinkingToggleOption(highlightedOption) ? "choose mode" : "choose effort")
|
|
350
|
+
: "select";
|
|
351
|
+
const effortTitle = phase.kind === "effort" && isThinkingToggleOption(phase.model)
|
|
352
|
+
? "Select Thinking Mode"
|
|
353
|
+
: "Select Reasoning Effort";
|
|
354
|
+
return (_jsxs(Box, { flexDirection: "column", marginY: 1, paddingX: 1, borderStyle: "round", borderColor: theme.borderActive, children: [_jsx(Text, { bold: true, color: theme.accent, children: phase.kind === "effort" ? effortTitle : "Select Model" }), phase.kind === "effort" ? (_jsx(EffortPickerView, { model: phase.model, selectedIndex: phase.selectedIndex, bodyRows: bodyRows, rowWidth: rowWidth })) : (_jsxs(_Fragment, { children: [_jsx(SearchField, { query: query, placeholder: "Type to search models...", width: rowWidth }), _jsxs(Text, { color: theme.muted, children: ["\u2191/\u2193 navigate \u00B7 Enter ", enterAction, " \u00B7 Esc cancel \u00B7 Backspace clear"] })] })), phase.kind === "model" && _jsx(Box, { flexDirection: "column", height: bodyRows, overflow: "hidden", marginTop: 1, children: modelRows.map(({ key, row, selected }) => (_jsx(Box, { height: 1, overflow: "hidden", children: _jsx(Text, { color: selected ? theme.accent : (key === "no-results" ? theme.muted : undefined), bold: selected, children: row }) }, key))) })] }));
|
|
349
355
|
}
|
|
350
356
|
function EffortPickerView({ model, selectedIndex, bodyRows, rowWidth, }) {
|
|
351
357
|
const theme = useTheme();
|
|
@@ -355,7 +361,7 @@ function EffortPickerView({ model, selectedIndex, bodyRows, rowWidth, }) {
|
|
|
355
361
|
row: formatEffortPickerRow(level, {
|
|
356
362
|
selected: index === safeSelectedIndex,
|
|
357
363
|
width: rowWidth,
|
|
358
|
-
asToggle:
|
|
364
|
+
asToggle: isThinkingToggleOption(model),
|
|
359
365
|
}),
|
|
360
366
|
selected: index === safeSelectedIndex,
|
|
361
367
|
}));
|
|
@@ -448,7 +454,7 @@ function reasoningLevelsForModel(model) {
|
|
|
448
454
|
const { providerId, modelId } = decodeModel(model);
|
|
449
455
|
return getAvailableThinkingLevels(providerId || "openai", modelId);
|
|
450
456
|
}
|
|
451
|
-
export function ProviderPicker({ providers, current, onSelect, onCancel, title }) {
|
|
457
|
+
export function ProviderPicker({ providers, current, onSelect, onCancel, title, onHighlight }) {
|
|
452
458
|
const theme = useTheme();
|
|
453
459
|
const { stdout } = useStdout();
|
|
454
460
|
const termHeight = stdout?.rows || 24;
|
|
@@ -457,6 +463,11 @@ export function ProviderPicker({ providers, current, onSelect, onCancel, title }
|
|
|
457
463
|
const idx = providers.findIndex((p) => p.id === current);
|
|
458
464
|
return idx >= 0 ? idx : 0;
|
|
459
465
|
});
|
|
466
|
+
useEffect(() => {
|
|
467
|
+
const p = providers[selectedIndex];
|
|
468
|
+
if (p)
|
|
469
|
+
onHighlight?.(p.id);
|
|
470
|
+
}, [selectedIndex]);
|
|
460
471
|
useInput((input, key) => {
|
|
461
472
|
if (isKeyReleaseEvent(key))
|
|
462
473
|
return;
|
|
@@ -110,6 +110,6 @@ export function PlanConfirm({ initialPlan, onApprove, onReject }) {
|
|
|
110
110
|
return (_jsx(Text, { children: line || " " }, index));
|
|
111
111
|
}
|
|
112
112
|
const safe = line || " ";
|
|
113
|
-
return (_jsxs(Box, { children: [_jsx(Text, { children: safe.slice(0, cursorCol) }), _jsx(Text, {
|
|
113
|
+
return (_jsxs(Box, { children: [_jsx(Text, { children: safe.slice(0, cursorCol) }), _jsx(Text, { inverse: true, children: safe[cursorCol] || " " }), _jsx(Text, { children: safe.slice(cursorCol + 1) })] }, index));
|
|
114
114
|
}) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.muted, children: [_jsx(Text, { color: theme.accent, bold: true, children: "\u2303S" }), " save & approve \u00A0\u00A0", _jsx(Text, { color: theme.accent, bold: true, children: "esc" }), " cancel edit"] }) })] }));
|
|
115
115
|
}
|
|
@@ -26,7 +26,9 @@ export declare function subagentDescriptor(subagent: SubagentDisplay, includeThi
|
|
|
26
26
|
export declare function subagentStatusColor(status: string | undefined, theme: Theme): string;
|
|
27
27
|
export declare function subagentSummary(subagents: SubagentDisplay[]): string;
|
|
28
28
|
export declare function sortSubagents(subagents: SubagentDisplay[]): SubagentDisplay[];
|
|
29
|
-
/** A spawn_agent (one member)
|
|
29
|
+
/** A spawn_agent (one member) or a run_workflow (a group of members). The
|
|
30
|
+
* "team"/"batch" kinds survive only to render transcripts recorded before
|
|
31
|
+
* those tools were removed (2026-07-06). */
|
|
30
32
|
export interface SubagentGroup {
|
|
31
33
|
id: string;
|
|
32
34
|
kind: "single" | "team" | "batch" | "workflow";
|
package/dist/tui-ink/theme.d.ts
CHANGED
|
@@ -15,7 +15,13 @@ export interface Theme {
|
|
|
15
15
|
error: string;
|
|
16
16
|
warning: string;
|
|
17
17
|
success: string;
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Painted canvas color. `undefined` means "do not paint" — the terminal's
|
|
20
|
+
* own background shows through, so the app never fights the user's terminal
|
|
21
|
+
* palette (e.g. forcing pure black inside a soft-dark terminal). Users can
|
|
22
|
+
* still force a painted canvas via `theme.overrides.background` in config.
|
|
23
|
+
*/
|
|
24
|
+
background: string | undefined;
|
|
19
25
|
accent: string;
|
|
20
26
|
/** Welcome banner border. */
|
|
21
27
|
bannerBorder: string;
|
|
@@ -40,7 +46,8 @@ export interface Theme {
|
|
|
40
46
|
toolResult: string;
|
|
41
47
|
toolError: string;
|
|
42
48
|
toolPending: string;
|
|
43
|
-
code
|
|
49
|
+
/** Markdown inline `code` spans. */
|
|
50
|
+
inlineCode: string;
|
|
44
51
|
traceAction: string;
|
|
45
52
|
traceCount: string;
|
|
46
53
|
traceDetail: string;
|
|
@@ -50,8 +57,10 @@ export interface Theme {
|
|
|
50
57
|
userMessageBg: string;
|
|
51
58
|
userMessageText: string;
|
|
52
59
|
userRail: string;
|
|
60
|
+
/** Painted band backgrounds for diff lines inside user-message cards. */
|
|
53
61
|
diffAdd: string;
|
|
54
62
|
diffRemove: string;
|
|
63
|
+
/** Foregrounds for +/- diff lines rendered without a painted background. */
|
|
55
64
|
diffAddFg: string;
|
|
56
65
|
diffRemoveFg: string;
|
|
57
66
|
}
|
|
@@ -62,6 +71,14 @@ export declare const darkTheme: Theme;
|
|
|
62
71
|
* terminal background.
|
|
63
72
|
*/
|
|
64
73
|
export declare const lightTheme: Theme;
|
|
74
|
+
/**
|
|
75
|
+
* Decide whether the canvas needs painting. Auto mode always inherits the
|
|
76
|
+
* terminal's own background, and so does a forced theme that matches the
|
|
77
|
+
* detected terminal. A forced theme that mismatches (e.g. `/theme light`
|
|
78
|
+
* inside a dark terminal) paints its canvas, because its foregrounds are
|
|
79
|
+
* tuned for the opposite background and would otherwise be unreadable.
|
|
80
|
+
*/
|
|
81
|
+
export declare function canvasBackgroundFor(mode: ThemeMode, resolved: ResolvedTheme, terminalTheme: ResolvedTheme): string | undefined;
|
|
65
82
|
export declare const ThemeProvider: import("react").Provider<Theme>;
|
|
66
83
|
export declare function useTheme(): Theme;
|
|
67
84
|
/** Build the active palette given a resolved mode and optional overrides. */
|
package/dist/tui-ink/theme.js
CHANGED
|
@@ -14,7 +14,7 @@ export const darkTheme = {
|
|
|
14
14
|
error: "red",
|
|
15
15
|
warning: "yellow",
|
|
16
16
|
success: "green",
|
|
17
|
-
background:
|
|
17
|
+
background: undefined,
|
|
18
18
|
accent: "cyan",
|
|
19
19
|
bannerBorder: "#38bdf8",
|
|
20
20
|
bannerGradientFrom: "#67e8f9",
|
|
@@ -37,7 +37,7 @@ export const darkTheme = {
|
|
|
37
37
|
toolResult: "gray",
|
|
38
38
|
toolError: "red",
|
|
39
39
|
toolPending: "yellow",
|
|
40
|
-
|
|
40
|
+
inlineCode: "#a78bfa",
|
|
41
41
|
traceAction: "#E89A6B",
|
|
42
42
|
traceCount: "#c9c1bd",
|
|
43
43
|
traceDetail: "gray",
|
|
@@ -49,8 +49,8 @@ export const darkTheme = {
|
|
|
49
49
|
userRail: "#8A7FC6",
|
|
50
50
|
diffAdd: "#1a3d1a",
|
|
51
51
|
diffRemove: "#3d1a1a",
|
|
52
|
-
diffAddFg: "
|
|
53
|
-
diffRemoveFg: "
|
|
52
|
+
diffAddFg: "green",
|
|
53
|
+
diffRemoveFg: "red",
|
|
54
54
|
};
|
|
55
55
|
/**
|
|
56
56
|
* Light palette tuned for paper-neutral surfaces, blue focus/user rails, warm
|
|
@@ -63,7 +63,7 @@ export const lightTheme = {
|
|
|
63
63
|
error: "#B62633",
|
|
64
64
|
warning: "#8B4A00",
|
|
65
65
|
success: "#2F7D4A",
|
|
66
|
-
background:
|
|
66
|
+
background: undefined,
|
|
67
67
|
accent: "#8B4A00",
|
|
68
68
|
bannerBorder: "#356FD2",
|
|
69
69
|
bannerGradientFrom: "#0E7490",
|
|
@@ -86,7 +86,7 @@ export const lightTheme = {
|
|
|
86
86
|
toolResult: "#171717",
|
|
87
87
|
toolError: "#B62633",
|
|
88
88
|
toolPending: "#8B4A00",
|
|
89
|
-
|
|
89
|
+
inlineCode: "#6D28D9",
|
|
90
90
|
traceAction: "#8B4A00",
|
|
91
91
|
traceCount: "#6F7377",
|
|
92
92
|
traceDetail: "#8B9094",
|
|
@@ -98,9 +98,26 @@ export const lightTheme = {
|
|
|
98
98
|
userRail: "#356FD2",
|
|
99
99
|
diffAdd: "#D7E8D8",
|
|
100
100
|
diffRemove: "#F7DADC",
|
|
101
|
-
diffAddFg: "#
|
|
102
|
-
diffRemoveFg: "#
|
|
101
|
+
diffAddFg: "#2F7D4A",
|
|
102
|
+
diffRemoveFg: "#B62633",
|
|
103
103
|
};
|
|
104
|
+
/** Canvas colors painted only when a forced theme mismatches the terminal. */
|
|
105
|
+
const paintedCanvas = {
|
|
106
|
+
dark: "#0A0A0A",
|
|
107
|
+
light: "#FCFCFA",
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Decide whether the canvas needs painting. Auto mode always inherits the
|
|
111
|
+
* terminal's own background, and so does a forced theme that matches the
|
|
112
|
+
* detected terminal. A forced theme that mismatches (e.g. `/theme light`
|
|
113
|
+
* inside a dark terminal) paints its canvas, because its foregrounds are
|
|
114
|
+
* tuned for the opposite background and would otherwise be unreadable.
|
|
115
|
+
*/
|
|
116
|
+
export function canvasBackgroundFor(mode, resolved, terminalTheme) {
|
|
117
|
+
if (mode === "auto" || resolved === terminalTheme)
|
|
118
|
+
return undefined;
|
|
119
|
+
return paintedCanvas[resolved];
|
|
120
|
+
}
|
|
104
121
|
const ThemeContext = createContext(darkTheme);
|
|
105
122
|
export const ThemeProvider = ThemeContext.Provider;
|
|
106
123
|
export function useTheme() {
|
|
@@ -9,6 +9,7 @@ interface WelcomeBannerProps {
|
|
|
9
9
|
/** Session identifier (session file basename). */
|
|
10
10
|
sessionLabel?: string;
|
|
11
11
|
providerId?: string;
|
|
12
|
+
modelId?: string;
|
|
12
13
|
modelLabel?: string;
|
|
13
14
|
/** Active thinking level, rendered as part of the model unit (e.g. "xhigh"). */
|
|
14
15
|
thinkingLabel?: string;
|
|
@@ -19,6 +20,6 @@ interface WelcomeVisibilityInput {
|
|
|
19
20
|
}
|
|
20
21
|
export declare function shouldShowWelcomeBanner({ startedWithVisibleHistory, }: WelcomeVisibilityInput): boolean;
|
|
21
22
|
export declare function lerpColor(from: string, to: string, t: number): string;
|
|
22
|
-
export declare function WelcomeBanner({ terminalColumns, tips, updateNotice, cwd, sessionLabel, providerId, modelLabel, thinkingLabel, }: WelcomeBannerProps): import("react/jsx-runtime").JSX.Element;
|
|
23
|
-
export declare function formatModelLine({ providerId, modelLabel, thinkingLabel, tips, }: Pick<WelcomeBannerProps, "providerId" | "modelLabel" | "thinkingLabel" | "tips">): string;
|
|
23
|
+
export declare function WelcomeBanner({ terminalColumns, tips, updateNotice, cwd, sessionLabel, providerId, modelId, modelLabel, thinkingLabel, }: WelcomeBannerProps): import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
export declare function formatModelLine({ providerId, modelId, modelLabel, thinkingLabel, tips, }: Pick<WelcomeBannerProps, "providerId" | "modelId" | "modelLabel" | "thinkingLabel" | "tips">): string;
|
|
24
25
|
export {};
|
package/dist/tui-ink/welcome.js
CHANGED
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { useTheme } from "./theme.js";
|
|
5
|
+
import { isThinkingOnlyModel, isThinkingToggleModel } from "../provider-transform.js";
|
|
5
6
|
const require = createRequire(import.meta.url);
|
|
6
7
|
const PACKAGE_VERSION = readPackageVersion();
|
|
7
8
|
const COMPACT_LOGO = [
|
|
@@ -28,11 +29,12 @@ function GradientText({ text, from, to }) {
|
|
|
28
29
|
const chars = [...text];
|
|
29
30
|
return (_jsx(_Fragment, { children: chars.map((ch, i) => (_jsx(Text, { bold: true, color: lerpColor(from, to, chars.length <= 1 ? 0 : i / (chars.length - 1)), children: ch }, `ch-${i}`))) }));
|
|
30
31
|
}
|
|
31
|
-
export function WelcomeBanner({ terminalColumns, tips, updateNotice, cwd, sessionLabel, providerId, modelLabel, thinkingLabel, }) {
|
|
32
|
+
export function WelcomeBanner({ terminalColumns, tips, updateNotice, cwd, sessionLabel, providerId, modelId, modelLabel, thinkingLabel, }) {
|
|
32
33
|
const theme = useTheme();
|
|
33
34
|
const effectiveWidth = Math.max(24, Math.min(terminalColumns - 2, 96));
|
|
34
35
|
const modelLine = formatModelLine({
|
|
35
36
|
providerId,
|
|
37
|
+
modelId,
|
|
36
38
|
modelLabel,
|
|
37
39
|
thinkingLabel,
|
|
38
40
|
tips,
|
|
@@ -48,14 +50,16 @@ export function WelcomeBanner({ terminalColumns, tips, updateNotice, cwd, sessio
|
|
|
48
50
|
const labelWidth = Math.max(...infoRows.map((row) => row.label.length)) + 1;
|
|
49
51
|
return (_jsxs(Box, { width: effectiveWidth, flexDirection: "column", marginBottom: 1, borderStyle: "round", borderColor: theme.bannerBorder, paddingX: 2, children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { flexDirection: "column", marginRight: 2, flexShrink: 0, children: COMPACT_LOGO.map((line, rowIndex) => (_jsx(Text, { bold: true, color: lerpColor(theme.bannerGradientFrom, theme.bannerGradientTo, COMPACT_LOGO.length <= 1 ? 0 : rowIndex / (COMPACT_LOGO.length - 1)), children: line }, `logo-row-${rowIndex}`))) }), _jsxs(Box, { flexDirection: "column", marginTop: 1, flexGrow: 1, flexShrink: 1, children: [_jsx(Box, { children: _jsx(GradientText, { text: "Welcome to Bubble!", from: theme.bannerGradientFrom, to: theme.bannerGradientTo }) }), _jsx(Text, { color: theme.muted, wrap: "wrap", children: "I am a cat and you can send /help for help information." })] })] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: infoRows.map((row) => (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { flexShrink: 0, children: _jsx(Text, { color: theme.dim, children: row.label.padEnd(labelWidth) }) }), _jsx(Box, { flexGrow: 1, flexShrink: 1, children: _jsx(Text, { color: row.color, wrap: "wrap", children: row.value }) })] }, row.label))) }), updateNotice && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.accent, children: updateNotice }) }))] }));
|
|
50
52
|
}
|
|
51
|
-
export function formatModelLine({ providerId, modelLabel, thinkingLabel, tips, }) {
|
|
53
|
+
export function formatModelLine({ providerId, modelId, modelLabel, thinkingLabel, tips, }) {
|
|
52
54
|
const parts = [];
|
|
53
|
-
//
|
|
54
|
-
// "thinking mode" rather than "<level> effort"
|
|
55
|
-
//
|
|
55
|
+
// Binary thinking toggles use an internal placeholder level, so label them
|
|
56
|
+
// "thinking mode" rather than "<level> effort". MiniMax provider ids are
|
|
57
|
+
// redundant with the model name, so omit them.
|
|
56
58
|
const isMiniMax = (providerId || "").toLowerCase().includes("minimax");
|
|
59
|
+
const isThinkingToggle = isThinkingToggleModel(providerId, modelId);
|
|
60
|
+
const isThinkingOnly = isThinkingOnlyModel(providerId, modelId);
|
|
57
61
|
if (modelLabel) {
|
|
58
|
-
if (thinkingLabel &&
|
|
62
|
+
if (thinkingLabel && (isThinkingToggle || isThinkingOnly))
|
|
59
63
|
parts.push(modelLabel, "thinking mode");
|
|
60
64
|
else if (thinkingLabel)
|
|
61
65
|
parts.push(`${modelLabel} with ${thinkingLabel} effort`);
|