Refine tool call summaries and scrolling (#2069)
* feat(app): refine tool call summaries and scrolling Keep summary groups stable from the first tool call, preserve live activity feedback, and scroll expanded groups to the latest entry. Consolidate web scrollbar styling around the native browser mechanism. * refactor(app): reuse tool call height constant * fix(app): preserve tool call summary state * fix(app): refresh web tool groups on expand * fix(app): preserve grouped tool errors
This commit is contained in:
@@ -60,6 +60,7 @@ import Animated, {
|
||||
} from "react-native-reanimated";
|
||||
import Svg, { Defs, LinearGradient as SvgLinearGradient, Rect, Stop } from "react-native-svg";
|
||||
import { CODE_SURFACE_DATASET } from "@/styles/code-surface";
|
||||
import { inlineUnistylesStyle } from "@/styles/unistyles-inline-style";
|
||||
import { MarkdownRenderer, type MarkdownStyles } from "@/components/markdown/renderer";
|
||||
import type { TodoEntry, UserMessageImageAttachment } from "@/types/stream";
|
||||
import type { AgentAttachment } from "@getpaseo/protocol/messages";
|
||||
@@ -115,6 +116,7 @@ import type { AgentCapabilityFlags } from "@getpaseo/protocol/agent-types";
|
||||
import { RewindMenu, type RewindMode } from "@/components/rewind/rewind-menu";
|
||||
import { useRewindAgentMutation } from "@/components/rewind/use-rewind-agent-mutation";
|
||||
import { AssistantForkMenu, type AssistantForkTarget } from "@/components/assistant-fork-menu";
|
||||
import { useRetainedPanelActive } from "@/components/retained-panel";
|
||||
export type { InlinePathTarget } from "@/assistant-file-links";
|
||||
export type { AssistantForkTarget };
|
||||
|
||||
@@ -1273,7 +1275,6 @@ const expandableBadgeStylesheet = StyleSheet.create((theme) => ({
|
||||
},
|
||||
chevron: {
|
||||
flexShrink: 0,
|
||||
transform: [{ scale: 1.3 }],
|
||||
},
|
||||
openFileButton: {
|
||||
marginLeft: theme.spacing[1],
|
||||
@@ -1285,9 +1286,6 @@ const expandableBadgeStylesheet = StyleSheet.create((theme) => ({
|
||||
width: 14,
|
||||
height: 14,
|
||||
},
|
||||
chevronExpanded: {
|
||||
transform: [{ scale: 1.3 }, { rotate: "90deg" }],
|
||||
},
|
||||
detailWrapper: {
|
||||
borderBottomLeftRadius: theme.borderRadius.lg,
|
||||
borderBottomRightRadius: theme.borderRadius.lg,
|
||||
@@ -1302,11 +1300,16 @@ const expandableBadgeStylesheet = StyleSheet.create((theme) => ({
|
||||
...(isWeb ? { cursor: "auto" as const, userSelect: "text" as const } : {}),
|
||||
},
|
||||
pressableExpanded: {
|
||||
borderColor: theme.colors.border,
|
||||
backgroundColor: theme.colors.surface1,
|
||||
},
|
||||
pressableExpandedAttached: {
|
||||
borderColor: theme.colors.border,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
},
|
||||
detailWrapperBorderless: {
|
||||
borderWidth: 0,
|
||||
},
|
||||
shimmerOverlay: {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
@@ -1358,9 +1361,14 @@ const NativeExpandableBadgeShimmer = memo(function NativeExpandableBadgeShimmer(
|
||||
durationSeconds,
|
||||
gradientId,
|
||||
}: NativeExpandableBadgeShimmerProps) {
|
||||
const isPanelActive = useRetainedPanelActive();
|
||||
const shimmerTranslateX = useSharedValue(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPanelActive) {
|
||||
cancelAnimation(shimmerTranslateX);
|
||||
return;
|
||||
}
|
||||
const startPosition = -peakWidth;
|
||||
const endPosition = rowWidth + peakWidth;
|
||||
shimmerTranslateX.value = startPosition;
|
||||
@@ -1375,7 +1383,7 @@ const NativeExpandableBadgeShimmer = memo(function NativeExpandableBadgeShimmer(
|
||||
return () => {
|
||||
cancelAnimation(shimmerTranslateX);
|
||||
};
|
||||
}, [durationSeconds, peakWidth, rowWidth, shimmerTranslateX]);
|
||||
}, [durationSeconds, isPanelActive, peakWidth, rowWidth, shimmerTranslateX]);
|
||||
|
||||
const nativeShimmerPeakStyle = useAnimatedStyle(() => ({
|
||||
transform: [{ translateX: shimmerTranslateX.value }],
|
||||
@@ -2366,6 +2374,7 @@ interface ExpandableBadgeProps {
|
||||
isError?: boolean;
|
||||
isLastInSequence?: boolean;
|
||||
disableOuterSpacing?: boolean;
|
||||
borderlessWhenExpanded?: boolean;
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
@@ -2604,7 +2613,9 @@ function renderExpandableBadgeIconSlot({
|
||||
}): ReactNode {
|
||||
if (showChevron) {
|
||||
return (
|
||||
<ThemedChevronRightIcon size={12} style={chevronStyle} uniProps={foregroundColorMapping} />
|
||||
<View style={chevronStyle}>
|
||||
<ThemedChevronRightIcon size={12} uniProps={foregroundColorMapping} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return iconNode;
|
||||
@@ -2695,7 +2706,7 @@ function buildShimmerTextStyle(input: {
|
||||
offsetX: number;
|
||||
}): object | null {
|
||||
if (!input.isWebShimmer) return null;
|
||||
return {
|
||||
return inlineUnistylesStyle({
|
||||
opacity: 1,
|
||||
color: "transparent",
|
||||
backgroundImage: SHIMMER_GRADIENT,
|
||||
@@ -2707,10 +2718,10 @@ function buildShimmerTextStyle(input: {
|
||||
animation: `${WEB_TOOLCALL_SHIMMER_ANIMATION_NAME} ${input.shimmerDuration}s linear infinite`,
|
||||
"--paseo-shimmer-start": `${input.webShimmerTrackStart - input.offsetX}px`,
|
||||
"--paseo-shimmer-end": `${input.webShimmerTrackEnd - input.offsetX}px`,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const ExpandableBadge = memo(function ExpandableBadge({
|
||||
export const ExpandableBadge = memo(function ExpandableBadge({
|
||||
label,
|
||||
style,
|
||||
secondaryLabel,
|
||||
@@ -2724,6 +2735,7 @@ const ExpandableBadge = memo(function ExpandableBadge({
|
||||
isError = false,
|
||||
isLastInSequence = false,
|
||||
disableOuterSpacing,
|
||||
borderlessWhenExpanded = false,
|
||||
testID,
|
||||
}: ExpandableBadgeProps) {
|
||||
const resolvedDisableOuterSpacing = useDisableOuterSpacing(disableOuterSpacing);
|
||||
@@ -2894,8 +2906,17 @@ const ExpandableBadge = memo(function ExpandableBadge({
|
||||
expandableBadgeStylesheet.pressable,
|
||||
isPressed && isInteractive ? expandableBadgeStylesheet.pressablePressed : null,
|
||||
isExpanded && expandableBadgeStylesheet.pressableExpanded,
|
||||
isExpanded && !borderlessWhenExpanded && expandableBadgeStylesheet.pressableExpandedAttached,
|
||||
],
|
||||
[isExpanded, isInteractive, isPressed],
|
||||
[borderlessWhenExpanded, isExpanded, isInteractive, isPressed],
|
||||
);
|
||||
|
||||
const detailWrapperStyle = useMemo(
|
||||
() => [
|
||||
expandableBadgeStylesheet.detailWrapper,
|
||||
borderlessWhenExpanded && expandableBadgeStylesheet.detailWrapperBorderless,
|
||||
],
|
||||
[borderlessWhenExpanded],
|
||||
);
|
||||
|
||||
const accessibilityState = useMemo(
|
||||
@@ -2944,8 +2965,10 @@ const ExpandableBadge = memo(function ExpandableBadge({
|
||||
const chevronStyle = useMemo(
|
||||
() => [
|
||||
expandableBadgeStylesheet.chevron,
|
||||
isExpanded && expandableBadgeStylesheet.chevronExpanded,
|
||||
LUCIDE_CHEVRON_NUDGE_LEFT,
|
||||
inlineUnistylesStyle({
|
||||
transform: isExpanded ? [{ scale: 1.3 }, { rotate: "90deg" }] : [{ scale: 1.3 }],
|
||||
}),
|
||||
],
|
||||
[isExpanded],
|
||||
);
|
||||
@@ -2953,7 +2976,7 @@ const ExpandableBadge = memo(function ExpandableBadge({
|
||||
const ThemedIcon = useMemo(() => (icon ? withUnistyles(icon) : null), [icon]);
|
||||
const iconNode = renderExpandableBadgeIcon({ isError, isActive, ThemedIcon });
|
||||
const iconSlotNode = renderExpandableBadgeIconSlot({
|
||||
showChevron: isInteractive && isHovered,
|
||||
showChevron: isInteractive && (isHovered || isExpanded),
|
||||
chevronStyle,
|
||||
iconNode,
|
||||
});
|
||||
@@ -3012,7 +3035,7 @@ const ExpandableBadge = memo(function ExpandableBadge({
|
||||
{detailContent ? (
|
||||
<Pressable
|
||||
ref={detailWrapperRef}
|
||||
style={expandableBadgeStylesheet.detailWrapper}
|
||||
style={detailWrapperStyle}
|
||||
onHoverIn={handleDetailHoverIn}
|
||||
onHoverOut={handleDetailHoverOut}
|
||||
>
|
||||
@@ -3033,6 +3056,7 @@ function areExpandableBadgePropsEqual(previous: ExpandableBadgeProps, next: Expa
|
||||
if (previous.isError !== next.isError) return false;
|
||||
if (previous.isLastInSequence !== next.isLastInSequence) return false;
|
||||
if (previous.disableOuterSpacing !== next.disableOuterSpacing) return false;
|
||||
if (previous.borderlessWhenExpanded !== next.borderlessWhenExpanded) return false;
|
||||
if (previous.testID !== next.testID) return false;
|
||||
if (previous.onToggle !== next.onToggle) return false;
|
||||
if (previous.onOpenFile !== next.onOpenFile) return false;
|
||||
@@ -3057,6 +3081,7 @@ interface ToolCallProps {
|
||||
onOpenFilePath?: (filePath: string) => void;
|
||||
defaultExpanded?: boolean;
|
||||
forceInline?: boolean;
|
||||
maxDetailHeight?: number;
|
||||
}
|
||||
|
||||
export const ToolCall = memo(function ToolCall({
|
||||
@@ -3075,6 +3100,7 @@ export const ToolCall = memo(function ToolCall({
|
||||
onOpenFilePath,
|
||||
defaultExpanded,
|
||||
forceInline = false,
|
||||
maxDetailHeight = 400,
|
||||
}: ToolCallProps) {
|
||||
const { openToolCall } = useToolCallSheet();
|
||||
const [isExpanded, setIsExpanded] = useState(defaultExpanded ?? false);
|
||||
@@ -3175,11 +3201,17 @@ export const ToolCall = memo(function ToolCall({
|
||||
<ToolCallDetailsContent
|
||||
detail={effectiveDetail}
|
||||
errorText={presentation.errorText}
|
||||
maxHeight={400}
|
||||
maxHeight={maxDetailHeight}
|
||||
showLoadingSkeleton={presentation.isLoadingDetails}
|
||||
/>
|
||||
);
|
||||
}, [shouldRenderInline, effectiveDetail, presentation.errorText, presentation.isLoadingDetails]);
|
||||
}, [
|
||||
shouldRenderInline,
|
||||
effectiveDetail,
|
||||
presentation.errorText,
|
||||
presentation.isLoadingDetails,
|
||||
maxDetailHeight,
|
||||
]);
|
||||
|
||||
if (presentation.isPlan && effectiveDetail?.type === "plan") {
|
||||
return (
|
||||
@@ -3224,5 +3256,6 @@ function areToolCallPropsEqual(previous: ToolCallProps, next: ToolCallProps) {
|
||||
if (previous.onOpenFilePath !== next.onOpenFilePath) return false;
|
||||
if (previous.defaultExpanded !== next.defaultExpanded) return false;
|
||||
if (previous.forceInline !== next.forceInline) return false;
|
||||
if (previous.maxDetailHeight !== next.maxDetailHeight) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user