Files
letta-code/src/cli/components/StreamingOutputDisplay.tsx
2026-03-18 10:17:35 -07:00

97 lines
3.1 KiB
TypeScript

import { Box } from "ink";
import { memo, useEffect, useState } from "react";
import type { StreamingState } from "../helpers/accumulator";
import { useTerminalWidth } from "../hooks/useTerminalWidth";
import { Text } from "./Text";
interface StreamingOutputDisplayProps {
streaming: StreamingState;
/** Show "(esc to interrupt)" hint - used by bash mode (LET-7199) */
showInterruptHint?: boolean;
}
/**
* Display component for streaming bash output during execution.
* Shows a rolling window of the last 5 lines with elapsed time.
*/
export const StreamingOutputDisplay = memo(
({ streaming, showInterruptHint }: StreamingOutputDisplayProps) => {
const columns = useTerminalWidth();
// Force re-render every second for elapsed timer
const [, forceUpdate] = useState(0);
useEffect(() => {
const interval = setInterval(() => forceUpdate((n) => n + 1), 1000);
return () => clearInterval(interval);
}, []);
const elapsed = Math.floor((Date.now() - streaming.startTime) / 1000);
const { tailLines, totalLineCount } = streaming;
const hiddenCount = Math.max(0, totalLineCount - tailLines.length);
const contentWidth = Math.max(10, columns - 5);
const clipToWidth = (text: string): string => {
if (text.length <= contentWidth) {
return text;
}
if (contentWidth <= 1) {
return "…";
}
return `${text.slice(0, contentWidth - 1)}`;
};
const firstLine = tailLines[0];
const interruptHint = showInterruptHint ? " (esc to interrupt)" : "";
if (!firstLine) {
return (
<Box>
<Text
dimColor
>{` ⎿ Running... (${elapsed}s)${interruptHint}`}</Text>
</Box>
);
}
return (
<Box flexDirection="column">
{/* L-bracket on first line - matches ToolCallMessageRich format " ⎿ " */}
<Box>
<Text dimColor>{" ⎿ "}</Text>
<Text
dimColor={!firstLine.isStderr}
color={firstLine.isStderr ? "red" : undefined}
>
{clipToWidth(firstLine.text)}
</Text>
</Box>
{/* Remaining lines with indent (5 spaces to align with content after bracket) */}
{tailLines.slice(1).map((line, i) => (
<Text
// biome-ignore lint/suspicious/noArrayIndexKey: Lines are positional output, stable order within render
key={i}
dimColor={!line.isStderr}
color={line.isStderr ? "red" : undefined}
>
{" "}
{clipToWidth(line.text)}
</Text>
))}
{/* Hidden count + elapsed time */}
{hiddenCount > 0 && (
<Text dimColor>
{" "} +{hiddenCount} more lines ({elapsed}s){interruptHint}
</Text>
)}
{/* Always show elapsed while running, even if output is short */}
{hiddenCount === 0 && (
<Text dimColor>
{" "}({elapsed}s){interruptHint}
</Text>
)}
</Box>
);
},
);
StreamingOutputDisplay.displayName = "StreamingOutputDisplay";