@bubblebrain-ai/bubble 0.0.31 → 0.0.32
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.
|
@@ -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);
|