feat(tui): tab expand tool calls cleanly (#8136)
Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
Generated
+2
-2
@@ -11300,9 +11300,9 @@ checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
version = "1.13.1"
|
version = "1.13.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "da36089a805484bcccfffe0739803392c8298778a2d2f09febf76fac5ad9025b"
|
checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
|
|||||||
@@ -9,6 +9,5 @@ unsound = "none"
|
|||||||
# Ignore proc-macro-error unmaintained warning
|
# Ignore proc-macro-error unmaintained warning
|
||||||
# See: https://github.com/block/goose/issues/7008
|
# See: https://github.com/block/goose/issues/7008
|
||||||
ignore = [
|
ignore = [
|
||||||
"RUSTSEC-2024-0370", # proc-macro-error is unmaintained
|
|
||||||
"RUSTSEC-2023-0071", # rsa: Marvin Attack timing sidechannel (no safe upgrade available, via jsonwebtoken)
|
"RUSTSEC-2023-0071", # rsa: Marvin Attack timing sidechannel (no safe upgrade available, via jsonwebtoken)
|
||||||
]
|
]
|
||||||
|
|||||||
+39
-14
@@ -171,7 +171,6 @@ export function buildToolCallCardLines(
|
|||||||
const bodyRows: Array<{ text: string; color?: string; italic?: boolean }> = [];
|
const bodyRows: Array<{ text: string; color?: string; italic?: boolean }> = [];
|
||||||
|
|
||||||
const runningText = info.status === "in_progress" ? " running…" : "";
|
const runningText = info.status === "in_progress" ? " running…" : "";
|
||||||
const tabHint = hasTruncated && !expanded ? "tab ↔" : "";
|
|
||||||
bodyRows.push({ text: "__HEADER__" });
|
bodyRows.push({ text: "__HEADER__" });
|
||||||
|
|
||||||
if (hasLocations) {
|
if (hasLocations) {
|
||||||
@@ -220,7 +219,6 @@ export function buildToolCallCardLines(
|
|||||||
<Text color={TEXT_SECONDARY} bold>{info.title}</Text>
|
<Text color={TEXT_SECONDARY} bold>{info.title}</Text>
|
||||||
{runningText ? <Text color={TEXT_DIM} italic>{runningText}</Text> : null}
|
{runningText ? <Text color={TEXT_DIM} italic>{runningText}</Text> : null}
|
||||||
</Box>
|
</Box>
|
||||||
{tabHint ? <Text color={TEXT_DIM} italic>{tabHint}</Text> : null}
|
|
||||||
</Box>
|
</Box>
|
||||||
<Text color={borderColor} dimColor={dimBorder}> │</Text>
|
<Text color={borderColor} dimColor={dimBorder}> │</Text>
|
||||||
</Box>,
|
</Box>,
|
||||||
@@ -252,28 +250,55 @@ export function ToolCallCompact({
|
|||||||
info,
|
info,
|
||||||
indent,
|
indent,
|
||||||
width,
|
width,
|
||||||
|
keyPrefix,
|
||||||
|
showTabHint,
|
||||||
}: {
|
}: {
|
||||||
info: ToolCallInfo;
|
info: ToolCallInfo;
|
||||||
indent: number;
|
indent: number;
|
||||||
width: number;
|
width: number;
|
||||||
}) {
|
keyPrefix: string;
|
||||||
|
showTabHint: boolean;
|
||||||
|
}): React.ReactNode[] {
|
||||||
const statusInfo = STATUS_INDICATORS[info.status] ?? STATUS_INDICATORS.pending!;
|
const statusInfo = STATUS_INDICATORS[info.status] ?? STATUS_INDICATORS.pending!;
|
||||||
const kindIcon = KIND_ICONS[info.kind ?? "other"] ?? "⚙";
|
const kindIcon = KIND_ICONS[info.kind ?? "other"] ?? "⚙";
|
||||||
const summary = summarizeContent(info);
|
const summary = summarizeContent(info);
|
||||||
const maxSummaryWidth = width - indent - 12 - info.title.length;
|
const borderColor = info.status === "failed" ? CRANBERRY : CEDAR;
|
||||||
|
const dimBorder = info.status !== "failed";
|
||||||
|
|
||||||
|
const cardWidth = Math.min(width - indent - 2, 72);
|
||||||
|
const innerWidth = cardWidth - 2;
|
||||||
|
|
||||||
|
const tabHintText = "tab ↔";
|
||||||
|
const maxSummaryWidth = innerWidth - info.title.length - 8 - (showTabHint ? tabHintText.length + 2 : 0);
|
||||||
const trimmedSummary =
|
const trimmedSummary =
|
||||||
summary.length > maxSummaryWidth && maxSummaryWidth > 3
|
summary.length > maxSummaryWidth && maxSummaryWidth > 3
|
||||||
? summary.slice(0, maxSummaryWidth - 1) + "…"
|
? summary.slice(0, maxSummaryWidth - 1) + "…"
|
||||||
: summary;
|
: summary;
|
||||||
|
|
||||||
return (
|
const topBorder = "╭" + "─".repeat(innerWidth) + "╮";
|
||||||
<Box marginLeft={indent} height={1}>
|
const botBorder = "╰" + "─".repeat(innerWidth) + "╯";
|
||||||
<Text color={statusInfo.color}>{statusInfo.icon} </Text>
|
|
||||||
<Text>{kindIcon} </Text>
|
return [
|
||||||
<Text color={TEXT_SECONDARY}>{info.title}</Text>
|
<Box key={`${keyPrefix}-top`} marginLeft={indent} height={1}>
|
||||||
{trimmedSummary ? (
|
<Text color={borderColor} dimColor={dimBorder}>{topBorder}</Text>
|
||||||
<Text color={TEXT_DIM}> — {trimmedSummary}</Text>
|
</Box>,
|
||||||
) : null}
|
<Box key={`${keyPrefix}-content`} marginLeft={indent} width={cardWidth} height={1}>
|
||||||
</Box>
|
<Text color={borderColor} dimColor={dimBorder}>│ </Text>
|
||||||
);
|
<Box flexGrow={1} justifyContent="space-between">
|
||||||
|
<Box>
|
||||||
|
<Text color={statusInfo.color}>{statusInfo.icon} </Text>
|
||||||
|
<Text>{kindIcon} </Text>
|
||||||
|
<Text color={TEXT_SECONDARY} bold>{info.title}</Text>
|
||||||
|
{trimmedSummary ? (
|
||||||
|
<Text color={TEXT_DIM}> — {trimmedSummary}</Text>
|
||||||
|
) : null}
|
||||||
|
</Box>
|
||||||
|
{showTabHint && <Text color={TEXT_DIM} italic>{tabHintText}</Text>}
|
||||||
|
</Box>
|
||||||
|
<Text color={borderColor} dimColor={dimBorder}> │</Text>
|
||||||
|
</Box>,
|
||||||
|
<Box key={`${keyPrefix}-bot`} marginLeft={indent} height={1}>
|
||||||
|
<Text color={borderColor} dimColor={dimBorder}>{botBorder}</Text>
|
||||||
|
</Box>,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-23
@@ -319,7 +319,7 @@ function buildTurnBodyLines({
|
|||||||
spinIdx,
|
spinIdx,
|
||||||
pendingPermission,
|
pendingPermission,
|
||||||
permissionIdx,
|
permissionIdx,
|
||||||
expandedToolCall,
|
toolCallsExpanded,
|
||||||
}: {
|
}: {
|
||||||
turn: Turn;
|
turn: Turn;
|
||||||
width: number;
|
width: number;
|
||||||
@@ -328,32 +328,32 @@ function buildTurnBodyLines({
|
|||||||
spinIdx: number;
|
spinIdx: number;
|
||||||
pendingPermission: PendingPermission | null;
|
pendingPermission: PendingPermission | null;
|
||||||
permissionIdx: number;
|
permissionIdx: number;
|
||||||
expandedToolCall: string | null;
|
toolCallsExpanded: boolean;
|
||||||
}): React.ReactNode[] {
|
}): React.ReactNode[] {
|
||||||
const toolCallIds = turn.toolCallOrder;
|
const toolCallIds = turn.toolCallOrder;
|
||||||
const toolCalls = turn.toolCalls;
|
const toolCalls = turn.toolCalls;
|
||||||
const featuredId = findFeaturedToolCallId(toolCallIds, toolCalls);
|
|
||||||
|
|
||||||
const lines: React.ReactNode[] = [];
|
const lines: React.ReactNode[] = [];
|
||||||
|
|
||||||
lines.push(<Box key="gap-top" height={1} />);
|
lines.push(<Box key="gap-top" height={1} />);
|
||||||
|
|
||||||
for (const tcId of toolCallIds) {
|
for (let i = 0; i < toolCallIds.length; i++) {
|
||||||
|
const tcId = toolCallIds[i]!;
|
||||||
const tc = toolCalls.get(tcId);
|
const tc = toolCalls.get(tcId);
|
||||||
if (!tc) continue;
|
if (!tc) continue;
|
||||||
|
|
||||||
if (tcId === featuredId || expandedToolCall === tcId) {
|
if (toolCallsExpanded) {
|
||||||
const cardLines = buildToolCallCardLines(tc, CONTENT_INDENT, width, expandedToolCall === tcId, `tc-${tcId}`);
|
const cardLines = buildToolCallCardLines(tc, CONTENT_INDENT, width, true, `tc-${tcId}`);
|
||||||
lines.push(...cardLines);
|
lines.push(...cardLines);
|
||||||
} else {
|
} else {
|
||||||
lines.push(
|
const compactLines = ToolCallCompact({
|
||||||
<ToolCallCompact
|
info: tc,
|
||||||
key={`tc-${tcId}`}
|
indent: CONTENT_INDENT,
|
||||||
info={tc}
|
width,
|
||||||
indent={CONTENT_INDENT}
|
keyPrefix: `tc-${tcId}`,
|
||||||
width={width}
|
showTabHint: i === 0,
|
||||||
/>,
|
});
|
||||||
);
|
lines.push(...compactLines);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -566,7 +566,7 @@ function App({
|
|||||||
const [queuedMessages, setQueuedMessages] = useState<string[]>([]);
|
const [queuedMessages, setQueuedMessages] = useState<string[]>([]);
|
||||||
|
|
||||||
const [viewTurnIdx, setViewTurnIdx] = useState(-1);
|
const [viewTurnIdx, setViewTurnIdx] = useState(-1);
|
||||||
const [expandedToolCall, setExpandedToolCall] = useState<string | null>(null);
|
const [toolCallsExpanded, setToolCallsExpanded] = useState(false);
|
||||||
const [scrollOffset, setScrollOffset] = useState(0);
|
const [scrollOffset, setScrollOffset] = useState(0);
|
||||||
|
|
||||||
const clientRef = useRef<GooseClient | null>(null);
|
const clientRef = useRef<GooseClient | null>(null);
|
||||||
@@ -589,7 +589,7 @@ function App({
|
|||||||
}, [turns]);
|
}, [turns]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setExpandedToolCall(null);
|
setToolCallsExpanded(false);
|
||||||
setScrollOffset(0);
|
setScrollOffset(0);
|
||||||
}, [viewTurnIdx, turns.length]);
|
}, [viewTurnIdx, turns.length]);
|
||||||
|
|
||||||
@@ -684,7 +684,7 @@ function App({
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
setViewTurnIdx(-1);
|
setViewTurnIdx(-1);
|
||||||
setExpandedToolCall(null);
|
setToolCallsExpanded(false);
|
||||||
setScrollOffset(0);
|
setScrollOffset(0);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -874,7 +874,7 @@ function App({
|
|||||||
if (!trimmed) return;
|
if (!trimmed) return;
|
||||||
setInput("");
|
setInput("");
|
||||||
setViewTurnIdx(-1);
|
setViewTurnIdx(-1);
|
||||||
setExpandedToolCall(null);
|
setToolCallsExpanded(false);
|
||||||
setScrollOffset(0);
|
setScrollOffset(0);
|
||||||
|
|
||||||
if (loading || isProcessingRef.current) {
|
if (loading || isProcessingRef.current) {
|
||||||
@@ -933,10 +933,7 @@ function App({
|
|||||||
const currentTurn = turns[effectiveIdx];
|
const currentTurn = turns[effectiveIdx];
|
||||||
if (!currentTurn || currentTurn.toolCallOrder.length === 0) return;
|
if (!currentTurn || currentTurn.toolCallOrder.length === 0) return;
|
||||||
|
|
||||||
const featuredId = findFeaturedToolCallId(currentTurn.toolCallOrder, currentTurn.toolCalls);
|
setToolCallsExpanded((prev) => !prev);
|
||||||
if (!featuredId) return;
|
|
||||||
|
|
||||||
setExpandedToolCall((prev) => (prev === featuredId ? null : featuredId));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1019,7 +1016,7 @@ function App({
|
|||||||
spinIdx,
|
spinIdx,
|
||||||
pendingPermission: isLatest ? pendingPermission : null,
|
pendingPermission: isLatest ? pendingPermission : null,
|
||||||
permissionIdx,
|
permissionIdx,
|
||||||
expandedToolCall,
|
toolCallsExpanded,
|
||||||
});
|
});
|
||||||
|
|
||||||
const allBodyLines = isLatest
|
const allBodyLines = isLatest
|
||||||
|
|||||||
Reference in New Issue
Block a user