@bubblebrain-ai/bubble 0.0.31 → 0.0.33
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/tui-ink/app.js +7 -1
- package/dist/tui-ink/markdown.d.ts +7 -0
- package/dist/tui-ink/markdown.js +80 -20
- package/dist/tui-ink/message-list.js +5 -1
- package/dist/tui-ink/theme.d.ts +5 -0
- package/dist/tui-ink/theme.js +6 -0
- package/dist/tui-ink/welcome.d.ts +4 -1
- package/dist/tui-ink/welcome.js +22 -3
- package/package.json +1 -1
package/dist/tui-ink/app.js
CHANGED
|
@@ -72,6 +72,12 @@ function friendlyCwd(cwd) {
|
|
|
72
72
|
return "~" + cwd.slice(home.length);
|
|
73
73
|
return cwd;
|
|
74
74
|
}
|
|
75
|
+
function sessionBasename(sessionFile) {
|
|
76
|
+
if (!sessionFile)
|
|
77
|
+
return undefined;
|
|
78
|
+
const base = sessionFile.split("/").pop() ?? sessionFile;
|
|
79
|
+
return base.replace(/\.jsonl$/, "");
|
|
80
|
+
}
|
|
75
81
|
function truncate(value, max) {
|
|
76
82
|
if (value.length <= max)
|
|
77
83
|
return value;
|
|
@@ -1686,7 +1692,7 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1686
1692
|
const showThinkingLabel = Boolean(thinkingLevel)
|
|
1687
1693
|
&& thinkingLevel !== "off"
|
|
1688
1694
|
&& (isMiniMaxProvider || getAvailableThinkingLevels(agent.providerId, agent.apiModel).length > 2);
|
|
1689
|
-
const welcomeBannerNode = showWelcome ? (_jsx(WelcomeBanner, { terminalColumns: terminalColumns, tips: buildTips(agent, safeRegistry), updateNotice: currentUpdateNotice, cwd: friendlyCwd(args.cwd), providerId: agent.providerId || safeRegistry.getDefault()?.id, modelLabel: agent.model ? displayModel(agent.model) : undefined, thinkingLabel: showThinkingLabel ? thinkingLevel : undefined })) : null;
|
|
1695
|
+
const welcomeBannerNode = showWelcome ? (_jsx(WelcomeBanner, { terminalColumns: terminalColumns, tips: buildTips(agent, safeRegistry), updateNotice: currentUpdateNotice, cwd: friendlyCwd(args.cwd), sessionLabel: sessionBasename(currentSessionFile()), providerId: agent.providerId || safeRegistry.getDefault()?.id, modelLabel: agent.model ? displayModel(agent.model) : undefined, thinkingLabel: showThinkingLabel ? thinkingLevel : undefined })) : null;
|
|
1690
1696
|
const commandPaletteItems = useMemo(() => buildCommandPaletteItems(safeSkillRegistry), [safeSkillRegistry]);
|
|
1691
1697
|
const mcpReconnectItems = useMemo(() => buildMcpReconnectItems(mcpManager), [mcpManager]);
|
|
1692
1698
|
// No fixed-height frame: settled rows flow into the terminal's native
|
|
@@ -44,6 +44,13 @@ export declare function parseMarkdownBlocks(text: string): MarkdownBlock[];
|
|
|
44
44
|
*/
|
|
45
45
|
export declare function findLastBlockStart(text: string): number;
|
|
46
46
|
export declare function parseMarkdownInlineSegments(text: string, style?: InlineStyle): MarkdownInlineSegment[];
|
|
47
|
+
/**
|
|
48
|
+
* Distribute `available` inner cells across columns. Columns whose natural
|
|
49
|
+
* width already fits their fair share keep it untouched (numbers and dates
|
|
50
|
+
* never wrap); only the wider columns split the remainder proportionally and
|
|
51
|
+
* wrap their content.
|
|
52
|
+
*/
|
|
53
|
+
export declare function allocateColumnWidths(natural: number[], available: number): number[];
|
|
47
54
|
/**
|
|
48
55
|
* CJK-aware line wrap over styled inline segments.
|
|
49
56
|
*
|
package/dist/tui-ink/markdown.js
CHANGED
|
@@ -406,32 +406,92 @@ function TableBlock({ headers, rows, maxWidth, }) {
|
|
|
406
406
|
let widths = [...maxWidths];
|
|
407
407
|
if (totalWidth > budget) {
|
|
408
408
|
const available = Math.max(budget - separatorsWidth, colCount * 4);
|
|
409
|
-
|
|
410
|
-
widths = maxWidths.map((w) => Math.max(4, Math.floor(w * ratio)));
|
|
411
|
-
// The 4-cell floor can push the sum back above `available`; shave the
|
|
412
|
-
// overshoot off the widest columns so the row never exceeds the budget
|
|
413
|
-
// and gets hard-wrapped by the terminal.
|
|
414
|
-
let excess = widths.reduce((a, b) => a + b, 0) - available;
|
|
415
|
-
while (excess > 0) {
|
|
416
|
-
let widest = -1;
|
|
417
|
-
for (let i = 0; i < widths.length; i++) {
|
|
418
|
-
if (widths[i] > 4 && (widest === -1 || widths[i] > widths[widest]))
|
|
419
|
-
widest = i;
|
|
420
|
-
}
|
|
421
|
-
if (widest === -1)
|
|
422
|
-
break;
|
|
423
|
-
widths[widest] -= 1;
|
|
424
|
-
excess -= 1;
|
|
425
|
-
}
|
|
409
|
+
widths = allocateColumnWidths(maxWidths, available);
|
|
426
410
|
}
|
|
427
411
|
const top = g.tl + widths.map((w) => g.h.repeat(w + 2)).join(g.tm) + g.tr;
|
|
428
412
|
const mid = g.ml + widths.map((w) => g.h.repeat(w + 2)).join(g.mm) + g.mr;
|
|
429
413
|
const bot = g.bl + widths.map((w) => g.h.repeat(w + 2)).join(g.bm) + g.br;
|
|
430
|
-
|
|
414
|
+
// A cell wider than its column wraps onto continuation lines (CJK-aware, so
|
|
415
|
+
// 、-joined user lists break cleanly); the row grows to its tallest cell.
|
|
416
|
+
const renderRow = (cells, keyPrefix, isHeader = false) => {
|
|
417
|
+
const wrapped = cells.map((c, i) => {
|
|
418
|
+
const width = widths[i] ?? 4;
|
|
419
|
+
let lines = wrapInlineSegments(parseMarkdownInlineSegments(c, { bold: isHeader }), width);
|
|
420
|
+
if (lines.length > MAX_TABLE_CELL_LINES) {
|
|
421
|
+
lines = lines.slice(0, MAX_TABLE_CELL_LINES);
|
|
422
|
+
const last = lines[MAX_TABLE_CELL_LINES - 1];
|
|
423
|
+
lines[MAX_TABLE_CELL_LINES - 1] = truncateInlineSegments([...last, { text: " …" }], width);
|
|
424
|
+
}
|
|
425
|
+
return lines;
|
|
426
|
+
});
|
|
427
|
+
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}`), i < colCount - 1 ? ` ${g.v} ` : ` ${g.v}`] }, i)))] }, `${keyPrefix}-line-${line}`))) }, keyPrefix));
|
|
429
|
+
};
|
|
431
430
|
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 })] }));
|
|
432
431
|
}
|
|
433
|
-
|
|
434
|
-
|
|
432
|
+
/** Cap a pathological cell (huge blob of text) at this many wrapped lines. */
|
|
433
|
+
const MAX_TABLE_CELL_LINES = 8;
|
|
434
|
+
/**
|
|
435
|
+
* Distribute `available` inner cells across columns. Columns whose natural
|
|
436
|
+
* width already fits their fair share keep it untouched (numbers and dates
|
|
437
|
+
* never wrap); only the wider columns split the remainder proportionally and
|
|
438
|
+
* wrap their content.
|
|
439
|
+
*/
|
|
440
|
+
export function allocateColumnWidths(natural, available) {
|
|
441
|
+
const count = natural.length;
|
|
442
|
+
const widths = new Array(count).fill(0);
|
|
443
|
+
const fixed = new Array(count).fill(false);
|
|
444
|
+
let remaining = available;
|
|
445
|
+
let unfixed = count;
|
|
446
|
+
// Each fixed column frees slack that can fit further columns — iterate to a
|
|
447
|
+
// fixpoint. Terminates: natural sum exceeds `available` (only caller path),
|
|
448
|
+
// so at least one column always stays unfixed.
|
|
449
|
+
let changed = true;
|
|
450
|
+
while (changed && unfixed > 0) {
|
|
451
|
+
changed = false;
|
|
452
|
+
const fair = remaining / unfixed;
|
|
453
|
+
for (let i = 0; i < count; i++) {
|
|
454
|
+
if (fixed[i] || natural[i] > fair)
|
|
455
|
+
continue;
|
|
456
|
+
fixed[i] = true;
|
|
457
|
+
widths[i] = natural[i];
|
|
458
|
+
remaining -= natural[i];
|
|
459
|
+
unfixed -= 1;
|
|
460
|
+
changed = true;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
if (unfixed > 0) {
|
|
464
|
+
const wideTotal = natural.reduce((sum, w, i) => sum + (fixed[i] ? 0 : w), 0);
|
|
465
|
+
let assigned = 0;
|
|
466
|
+
let lastWide = -1;
|
|
467
|
+
for (let i = 0; i < count; i++) {
|
|
468
|
+
if (fixed[i])
|
|
469
|
+
continue;
|
|
470
|
+
widths[i] = Math.max(4, Math.floor((remaining * natural[i]) / wideTotal));
|
|
471
|
+
assigned += widths[i];
|
|
472
|
+
lastWide = i;
|
|
473
|
+
}
|
|
474
|
+
// Flooring leftovers go to the last wide column instead of being wasted.
|
|
475
|
+
if (lastWide >= 0 && assigned < remaining)
|
|
476
|
+
widths[lastWide] += remaining - assigned;
|
|
477
|
+
}
|
|
478
|
+
// On a very narrow budget the 4-cell floor can overshoot; shave the widest
|
|
479
|
+
// columns so the row never exceeds `available` and hard-wraps.
|
|
480
|
+
let excess = widths.reduce((a, b) => a + b, 0) - available;
|
|
481
|
+
while (excess > 0) {
|
|
482
|
+
let widest = -1;
|
|
483
|
+
for (let i = 0; i < count; i++) {
|
|
484
|
+
if (widths[i] > 4 && (widest === -1 || widths[i] > widths[widest]))
|
|
485
|
+
widest = i;
|
|
486
|
+
}
|
|
487
|
+
if (widest === -1)
|
|
488
|
+
break;
|
|
489
|
+
widths[widest] -= 1;
|
|
490
|
+
excess -= 1;
|
|
491
|
+
}
|
|
492
|
+
return widths;
|
|
493
|
+
}
|
|
494
|
+
function renderCellLine(segments, width, keyPrefix) {
|
|
435
495
|
const padding = " ".repeat(Math.max(0, width - inlineSegmentsWidth(segments)));
|
|
436
496
|
return [
|
|
437
497
|
...segments.map((segment, index) => (_jsx(Text, { bold: segment.bold, italic: segment.italic, color: segment.code ? "#a78bfa" : undefined, children: segment.text }, `${keyPrefix}-${index}`))),
|
|
@@ -139,7 +139,11 @@ const MessageItem = React.memo(function MessageItem({ message, terminalColumns,
|
|
|
139
139
|
message.taskElapsedMs !== undefined;
|
|
140
140
|
if (!hasVisibleAssistantContent)
|
|
141
141
|
return null;
|
|
142
|
-
return (_jsxs(Box, { marginTop: 1, marginBottom: 1, flexDirection: "column", children: [visibleReasoning && (showThinking || verboseTrace) && _jsx(ReasoningTraceBlock, { reasoning: visibleReasoning }), message.parts && message.parts.length > 0 ? (_jsx(MessageParts, { parts: message.parts, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: undefined, showExpandHint: showExpandHint, nowTick: nowTick })) : (_jsxs(_Fragment, { children: [message.toolCalls && (_jsx(ToolsPart, { toolCalls: message.toolCalls, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: undefined, showExpandHint: showExpandHint, nowTick: nowTick })), visibleContent.trim() &&
|
|
142
|
+
return (_jsxs(Box, { marginTop: 1, marginBottom: 1, flexDirection: "column", children: [visibleReasoning && (showThinking || verboseTrace) && _jsx(ReasoningTraceBlock, { reasoning: visibleReasoning }), message.parts && message.parts.length > 0 ? (_jsx(MessageParts, { parts: message.parts, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: undefined, showExpandHint: showExpandHint, nowTick: nowTick })) : (_jsxs(_Fragment, { children: [message.toolCalls && (_jsx(ToolsPart, { toolCalls: message.toolCalls, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: undefined, showExpandHint: showExpandHint, nowTick: nowTick })), visibleContent.trim() && (
|
|
143
|
+
// Thread the real terminal width down (minus the row's paddingX
|
|
144
|
+
// inset); without it, width-aware blocks like tables fall back to
|
|
145
|
+
// a stale/default useTerminalSize and mis-budget their columns.
|
|
146
|
+
_jsx(MarkdownContent, { content: visibleContent, maxWidth: Math.max(20, terminalColumns - 4) }))] })), verboseTrace && message.toolCalls && message.toolCalls.length > 0 && (_jsx(TurnDigest, { toolCalls: message.toolCalls })), message.taskElapsedMs !== undefined && (_jsx(TaskDurationLine, { elapsedMs: message.taskElapsedMs }))] }));
|
|
143
147
|
});
|
|
144
148
|
function StreamingMessage({ content, reasoning, tools, parts, terminalColumns, showThinking, verboseTrace, pendingApproval, nowTick, }) {
|
|
145
149
|
const deferredContent = React.useDeferredValue(content);
|
package/dist/tui-ink/theme.d.ts
CHANGED
|
@@ -17,6 +17,11 @@ export interface Theme {
|
|
|
17
17
|
success: string;
|
|
18
18
|
background: string;
|
|
19
19
|
accent: string;
|
|
20
|
+
/** Welcome banner border. */
|
|
21
|
+
bannerBorder: string;
|
|
22
|
+
/** Welcome banner logo/title gradient endpoints (top→bottom, left→right). */
|
|
23
|
+
bannerGradientFrom: string;
|
|
24
|
+
bannerGradientTo: string;
|
|
20
25
|
border: string;
|
|
21
26
|
borderActive: string;
|
|
22
27
|
backgroundPanel: string;
|
package/dist/tui-ink/theme.js
CHANGED
|
@@ -16,6 +16,9 @@ export const darkTheme = {
|
|
|
16
16
|
success: "green",
|
|
17
17
|
background: "#0A0A0A",
|
|
18
18
|
accent: "cyan",
|
|
19
|
+
bannerBorder: "#38bdf8",
|
|
20
|
+
bannerGradientFrom: "#67e8f9",
|
|
21
|
+
bannerGradientTo: "#a78bfa",
|
|
19
22
|
border: "gray",
|
|
20
23
|
borderActive: "cyan",
|
|
21
24
|
backgroundPanel: "#141414",
|
|
@@ -62,6 +65,9 @@ export const lightTheme = {
|
|
|
62
65
|
success: "#2F7D4A",
|
|
63
66
|
background: "#FCFCFA",
|
|
64
67
|
accent: "#8B4A00",
|
|
68
|
+
bannerBorder: "#356FD2",
|
|
69
|
+
bannerGradientFrom: "#0E7490",
|
|
70
|
+
bannerGradientTo: "#6D28D9",
|
|
65
71
|
border: "#B9BDB8",
|
|
66
72
|
borderActive: "#356FD2",
|
|
67
73
|
backgroundPanel: "#F6F6F3",
|
|
@@ -6,6 +6,8 @@ interface WelcomeBannerProps {
|
|
|
6
6
|
updateNotice?: string;
|
|
7
7
|
/** Friendly working directory (~ collapsed). */
|
|
8
8
|
cwd?: string;
|
|
9
|
+
/** Session identifier (session file basename). */
|
|
10
|
+
sessionLabel?: string;
|
|
9
11
|
providerId?: string;
|
|
10
12
|
modelLabel?: string;
|
|
11
13
|
/** Active thinking level, rendered as part of the model unit (e.g. "xhigh"). */
|
|
@@ -16,6 +18,7 @@ interface WelcomeVisibilityInput {
|
|
|
16
18
|
startedWithVisibleHistory: boolean;
|
|
17
19
|
}
|
|
18
20
|
export declare function shouldShowWelcomeBanner({ startedWithVisibleHistory, }: WelcomeVisibilityInput): boolean;
|
|
19
|
-
export declare function
|
|
21
|
+
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;
|
|
20
23
|
export declare function formatModelLine({ providerId, modelLabel, thinkingLabel, tips, }: Pick<WelcomeBannerProps, "providerId" | "modelLabel" | "thinkingLabel" | "tips">): string;
|
|
21
24
|
export {};
|
package/dist/tui-ink/welcome.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { useTheme } from "./theme.js";
|
|
@@ -18,7 +18,17 @@ export function shouldShowWelcomeBanner({ startedWithVisibleHistory, }) {
|
|
|
18
18
|
return false;
|
|
19
19
|
return true;
|
|
20
20
|
}
|
|
21
|
-
export function
|
|
21
|
+
export function lerpColor(from, to, t) {
|
|
22
|
+
const pa = [1, 3, 5].map((i) => parseInt(from.slice(i, i + 2), 16));
|
|
23
|
+
const pb = [1, 3, 5].map((i) => parseInt(to.slice(i, i + 2), 16));
|
|
24
|
+
const out = pa.map((v, i) => Math.round(v + ((pb[i] ?? v) - v) * t));
|
|
25
|
+
return `#${out.map((v) => v.toString(16).padStart(2, "0")).join("")}`;
|
|
26
|
+
}
|
|
27
|
+
function GradientText({ text, from, to }) {
|
|
28
|
+
const chars = [...text];
|
|
29
|
+
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
|
+
export function WelcomeBanner({ terminalColumns, tips, updateNotice, cwd, sessionLabel, providerId, modelLabel, thinkingLabel, }) {
|
|
22
32
|
const theme = useTheme();
|
|
23
33
|
const effectiveWidth = Math.max(24, Math.min(terminalColumns - 2, 96));
|
|
24
34
|
const modelLine = formatModelLine({
|
|
@@ -27,7 +37,16 @@ export function WelcomeBanner({ terminalColumns, tips, updateNotice, cwd, provid
|
|
|
27
37
|
thinkingLabel,
|
|
28
38
|
tips,
|
|
29
39
|
});
|
|
30
|
-
|
|
40
|
+
const infoRows = [];
|
|
41
|
+
if (cwd)
|
|
42
|
+
infoRows.push({ label: "Directory:", value: cwd, color: theme.inputText });
|
|
43
|
+
if (sessionLabel)
|
|
44
|
+
infoRows.push({ label: "Session:", value: sessionLabel, color: theme.muted });
|
|
45
|
+
if (modelLine)
|
|
46
|
+
infoRows.push({ label: "Model:", value: modelLine, color: theme.traceCommand });
|
|
47
|
+
infoRows.push({ label: "Version:", value: PACKAGE_VERSION, color: theme.muted });
|
|
48
|
+
const labelWidth = Math.max(...infoRows.map((row) => row.label.length)) + 1;
|
|
49
|
+
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 }) }))] }));
|
|
31
50
|
}
|
|
32
51
|
export function formatModelLine({ providerId, modelLabel, thinkingLabel, tips, }) {
|
|
33
52
|
const parts = [];
|