mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
feat(app): extract tool call details for inline/sheet display
- Create ToolCallDetailsContent component for shared content rendering - Add useToolCallDetails hook for parsing tool call data - Desktop (md+): expand tool call details inline below badge - Mobile (xs/sm): open bottom sheet on tap (unchanged behavior) - Remove unused props from ToolCall component 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
@@ -280,9 +280,6 @@ export function AgentStreamView({
|
||||
result={data.result}
|
||||
error={data.error}
|
||||
status={data.status as "executing" | "completed" | "failed"}
|
||||
parsedEditEntries={data.parsedEdits}
|
||||
parsedReadEntries={data.parsedReads}
|
||||
parsedCommandDetails={data.parsedCommand ?? null}
|
||||
cwd={agent.cwd}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -22,16 +22,16 @@ import {
|
||||
Search,
|
||||
Brain,
|
||||
} from "lucide-react-native";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { StyleSheet, useUnistyles, UnistylesRuntime } from "react-native-unistyles";
|
||||
import { baseColors, theme } from "@/styles/theme";
|
||||
import { createMarkdownStyles, createCompactMarkdownStyles } from "@/styles/markdown-styles";
|
||||
import { Colors, Fonts } from "@/constants/theme";
|
||||
import * as Clipboard from "expo-clipboard";
|
||||
import type { TodoEntry, ThoughtStatus } from "@/types/stream";
|
||||
import type { CommandDetails, EditEntry, ReadEntry } from "@/utils/tool-call-parsers";
|
||||
import { extractPrincipalParam } from "@/utils/tool-call-parsers";
|
||||
import { resolveToolCallPreview } from "./tool-call-preview";
|
||||
import { useToolCallSheet } from "./tool-call-sheet";
|
||||
import { ToolCallDetailsContent, useToolCallDetails } from "./tool-call-details";
|
||||
|
||||
interface UserMessageProps {
|
||||
message: string;
|
||||
@@ -845,7 +845,7 @@ interface ExpandableBadgeProps {
|
||||
secondaryLabel?: string;
|
||||
icon?: ComponentType<{ size?: number; color?: string }>;
|
||||
isExpanded: boolean;
|
||||
onToggle: () => void;
|
||||
onToggle?: () => void;
|
||||
renderDetails?: () => ReactNode;
|
||||
isLoading?: boolean;
|
||||
isError?: boolean;
|
||||
@@ -1110,9 +1110,6 @@ interface ToolCallProps {
|
||||
result?: any;
|
||||
error?: any;
|
||||
status: "executing" | "completed" | "failed";
|
||||
parsedEditEntries?: EditEntry[];
|
||||
parsedReadEntries?: ReadEntry[];
|
||||
parsedCommandDetails?: CommandDetails | null;
|
||||
cwd?: string;
|
||||
}
|
||||
|
||||
@@ -1141,12 +1138,14 @@ export const ToolCall = memo(function ToolCall({
|
||||
result,
|
||||
error,
|
||||
status,
|
||||
parsedEditEntries,
|
||||
parsedReadEntries,
|
||||
parsedCommandDetails,
|
||||
cwd,
|
||||
}: ToolCallProps) {
|
||||
const { openToolCall } = useToolCallSheet();
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
// Check if we're on mobile (use bottom sheet) or desktop (inline expand)
|
||||
const isMobile =
|
||||
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
||||
|
||||
const kind = getToolKindFromName(toolName);
|
||||
const IconComponent = toolKindIcons[kind] || Wrench;
|
||||
@@ -1157,33 +1156,56 @@ export const ToolCall = memo(function ToolCall({
|
||||
[args, cwd]
|
||||
);
|
||||
|
||||
// Check if there's any content to display in the sheet
|
||||
// Check if there's any content to display
|
||||
const hasDetails = args !== undefined || result !== undefined || error !== undefined;
|
||||
|
||||
const handleOpenSheet = useCallback(() => {
|
||||
openToolCall({
|
||||
toolName,
|
||||
kind,
|
||||
status,
|
||||
args,
|
||||
result,
|
||||
error,
|
||||
});
|
||||
}, [openToolCall, toolName, kind, status, args, result, error]);
|
||||
// Parse tool call details for inline rendering
|
||||
const { display, errorText } = useToolCallDetails({ args, result, error });
|
||||
|
||||
// Dummy renderDetails to make badge tappable - actual content is rendered in the sheet
|
||||
const dummyRenderDetails = useCallback(() => null, []);
|
||||
const handleToggle = useCallback(() => {
|
||||
if (isMobile) {
|
||||
// Mobile: open bottom sheet
|
||||
openToolCall({
|
||||
toolName,
|
||||
kind,
|
||||
status,
|
||||
args,
|
||||
result,
|
||||
error,
|
||||
});
|
||||
} else {
|
||||
// Desktop: toggle inline expansion
|
||||
setIsExpanded((prev) => !prev);
|
||||
}
|
||||
}, [isMobile, openToolCall, toolName, kind, status, args, result, error]);
|
||||
|
||||
// Render inline details for desktop
|
||||
const renderDetails = useCallback(() => {
|
||||
if (isMobile) return null;
|
||||
return (
|
||||
<View style={toolCallInlineStyles.detailsContainer}>
|
||||
<ToolCallDetailsContent display={display} errorText={errorText} maxHeight={400} />
|
||||
</View>
|
||||
);
|
||||
}, [isMobile, display, errorText]);
|
||||
|
||||
return (
|
||||
<ExpandableBadge
|
||||
label={toolName}
|
||||
secondaryLabel={principalParam}
|
||||
icon={IconComponent}
|
||||
isExpanded={false}
|
||||
onToggle={handleOpenSheet}
|
||||
renderDetails={hasDetails ? dummyRenderDetails : undefined}
|
||||
isExpanded={!isMobile && isExpanded}
|
||||
onToggle={hasDetails ? handleToggle : undefined}
|
||||
renderDetails={hasDetails && !isMobile ? renderDetails : (hasDetails ? () => null : undefined)}
|
||||
isLoading={status === "executing"}
|
||||
isError={status === "failed"}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const toolCallInlineStyles = StyleSheet.create((theme) => ({
|
||||
detailsContainer: {
|
||||
paddingTop: theme.spacing[3],
|
||||
paddingBottom: theme.spacing[2],
|
||||
},
|
||||
}));
|
||||
|
||||
318
packages/app/src/components/tool-call-details.tsx
Normal file
318
packages/app/src/components/tool-call-details.tsx
Normal file
@@ -0,0 +1,318 @@
|
||||
import React, { useMemo, ReactNode } from "react";
|
||||
import { View, Text } from "react-native";
|
||||
import { ScrollView } from "react-native-gesture-handler";
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
import { Fonts } from "@/constants/theme";
|
||||
import {
|
||||
parseToolCallDisplay,
|
||||
buildLineDiff,
|
||||
type ToolCallDisplay,
|
||||
} from "@/utils/tool-call-parsers";
|
||||
import { DiffViewer } from "./diff-viewer";
|
||||
|
||||
// ---- Types ----
|
||||
|
||||
export interface ToolCallDetailsData {
|
||||
args?: unknown;
|
||||
result?: unknown;
|
||||
error?: unknown;
|
||||
}
|
||||
|
||||
// ---- Helper ----
|
||||
|
||||
function formatValue(value: unknown): string {
|
||||
if (value === undefined) {
|
||||
return "";
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return value;
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(value, null, 2);
|
||||
} catch {
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Content Component ----
|
||||
|
||||
interface ToolCallDetailsContentProps {
|
||||
display: ToolCallDisplay;
|
||||
errorText?: string;
|
||||
maxHeight?: number;
|
||||
}
|
||||
|
||||
export function ToolCallDetailsContent({
|
||||
display,
|
||||
errorText,
|
||||
maxHeight = 300,
|
||||
}: ToolCallDetailsContentProps) {
|
||||
// Compute diff lines for edit type
|
||||
const diffLines = useMemo(() => {
|
||||
if (display.type !== "edit") return undefined;
|
||||
return buildLineDiff(display.oldString, display.newString);
|
||||
}, [display]);
|
||||
|
||||
const sections: ReactNode[] = [];
|
||||
|
||||
if (display.type === "shell") {
|
||||
sections.push(
|
||||
<View key="shell" style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Command</Text>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
style={styles.jsonScroll}
|
||||
contentContainerStyle={styles.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text style={styles.scrollText}>{display.command}</Text>
|
||||
</ScrollView>
|
||||
{display.output ? (
|
||||
<ScrollView
|
||||
style={[styles.scrollArea, { maxHeight }]}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
nestedScrollEnabled
|
||||
showsVerticalScrollIndicator={true}
|
||||
>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text style={styles.scrollText}>{display.output}</Text>
|
||||
</ScrollView>
|
||||
</ScrollView>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
} else if (display.type === "edit") {
|
||||
sections.push(
|
||||
<View key="edit" style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>File</Text>
|
||||
<View style={styles.fileBadge}>
|
||||
<Text style={styles.fileBadgeText}>{display.filePath}</Text>
|
||||
</View>
|
||||
{diffLines && diffLines.length > 0 ? (
|
||||
<View style={styles.diffContainer}>
|
||||
<DiffViewer diffLines={diffLines} maxHeight={maxHeight} />
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
} else if (display.type === "read") {
|
||||
sections.push(
|
||||
<View key="read" style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>File</Text>
|
||||
<View style={styles.fileBadge}>
|
||||
<Text style={styles.fileBadgeText}>{display.filePath}</Text>
|
||||
</View>
|
||||
{(display.offset !== undefined || display.limit !== undefined) ? (
|
||||
<Text style={styles.rangeText}>
|
||||
{display.offset !== undefined ? `Offset: ${display.offset}` : ""}
|
||||
{display.offset !== undefined && display.limit !== undefined ? " • " : ""}
|
||||
{display.limit !== undefined ? `Limit: ${display.limit}` : ""}
|
||||
</Text>
|
||||
) : null}
|
||||
{display.content ? (
|
||||
<ScrollView
|
||||
style={[styles.scrollArea, { maxHeight }]}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
nestedScrollEnabled
|
||||
showsVerticalScrollIndicator={true}
|
||||
>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text style={styles.scrollText}>{display.content}</Text>
|
||||
</ScrollView>
|
||||
</ScrollView>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
// Generic tool: show input/output as key-value pairs
|
||||
if (display.input.length > 0) {
|
||||
sections.push(
|
||||
<View key="input-header" style={styles.groupHeader}>
|
||||
<Text style={styles.groupHeaderText}>Input</Text>
|
||||
</View>
|
||||
);
|
||||
display.input.forEach((pair, index) => {
|
||||
sections.push(
|
||||
<View key={`input-${index}-${pair.key}`} style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>{pair.key}</Text>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
style={styles.jsonScroll}
|
||||
contentContainerStyle={styles.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text style={styles.scrollText}>{pair.value}</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (display.output.length > 0) {
|
||||
sections.push(
|
||||
<View key="output-header" style={styles.groupHeader}>
|
||||
<Text style={styles.groupHeaderText}>Output</Text>
|
||||
</View>
|
||||
);
|
||||
display.output.forEach((pair, index) => {
|
||||
sections.push(
|
||||
<View key={`output-${index}-${pair.key}`} style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>{pair.key}</Text>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
style={styles.jsonScroll}
|
||||
contentContainerStyle={styles.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text style={styles.scrollText}>{pair.value}</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Always show errors if available
|
||||
if (errorText) {
|
||||
sections.push(
|
||||
<View key="error" style={styles.section}>
|
||||
<Text style={[styles.sectionTitle, styles.errorText]}>Error</Text>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
style={[styles.jsonScroll, styles.jsonScrollError]}
|
||||
contentContainerStyle={styles.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text style={[styles.scrollText, styles.errorText]}>
|
||||
{errorText}
|
||||
</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (sections.length === 0) {
|
||||
return (
|
||||
<Text style={styles.emptyStateText}>No additional details available</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return <View style={styles.container}>{sections}</View>;
|
||||
}
|
||||
|
||||
// ---- Hook for parsing tool call data ----
|
||||
|
||||
export function useToolCallDetails(data: ToolCallDetailsData) {
|
||||
const { args, result, error } = data;
|
||||
|
||||
return useMemo(() => {
|
||||
const display = parseToolCallDisplay(args, result);
|
||||
const errorText = error !== undefined ? formatValue(error) : undefined;
|
||||
return { display, errorText };
|
||||
}, [args, result, error]);
|
||||
}
|
||||
|
||||
// ---- Styles ----
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
container: {
|
||||
gap: theme.spacing[4],
|
||||
},
|
||||
groupHeader: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[2],
|
||||
paddingBottom: theme.spacing[1],
|
||||
borderBottomWidth: theme.borderWidth[1],
|
||||
borderBottomColor: theme.colors.border,
|
||||
},
|
||||
groupHeaderText: {
|
||||
color: theme.colors.primary,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontWeight: theme.fontWeight.bold,
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1,
|
||||
},
|
||||
section: {
|
||||
gap: theme.spacing[2],
|
||||
},
|
||||
sectionTitle: {
|
||||
color: theme.colors.mutedForeground,
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontWeight: theme.fontWeight.semibold,
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
fileBadge: {
|
||||
alignSelf: "flex-start",
|
||||
paddingHorizontal: theme.spacing[2],
|
||||
paddingVertical: theme.spacing[1],
|
||||
borderRadius: theme.borderRadius.base,
|
||||
borderWidth: theme.borderWidth[1],
|
||||
borderColor: theme.colors.border,
|
||||
backgroundColor: theme.colors.card,
|
||||
},
|
||||
fileBadgeText: {
|
||||
color: theme.colors.foreground,
|
||||
fontFamily: Fonts.mono,
|
||||
fontSize: theme.fontSize.xs,
|
||||
},
|
||||
rangeText: {
|
||||
color: theme.colors.mutedForeground,
|
||||
fontSize: theme.fontSize.xs,
|
||||
},
|
||||
diffContainer: {
|
||||
borderWidth: theme.borderWidth[1],
|
||||
borderColor: theme.colors.border,
|
||||
borderRadius: theme.borderRadius.base,
|
||||
overflow: "hidden",
|
||||
backgroundColor: theme.colors.card,
|
||||
},
|
||||
scrollArea: {
|
||||
borderWidth: theme.borderWidth[1],
|
||||
borderColor: theme.colors.border,
|
||||
borderRadius: theme.borderRadius.base,
|
||||
backgroundColor: theme.colors.card,
|
||||
},
|
||||
scrollContent: {
|
||||
padding: theme.spacing[2],
|
||||
},
|
||||
scrollText: {
|
||||
fontFamily: Fonts.mono,
|
||||
fontSize: theme.fontSize.xs,
|
||||
color: theme.colors.foreground,
|
||||
lineHeight: 18,
|
||||
},
|
||||
jsonScroll: {
|
||||
borderWidth: theme.borderWidth[1],
|
||||
borderColor: theme.colors.border,
|
||||
borderRadius: theme.borderRadius.base,
|
||||
backgroundColor: theme.colors.card,
|
||||
},
|
||||
jsonScrollError: {
|
||||
borderColor: theme.colors.destructive,
|
||||
},
|
||||
jsonContent: {
|
||||
padding: theme.spacing[2],
|
||||
},
|
||||
errorText: {
|
||||
color: theme.colors.destructive,
|
||||
},
|
||||
emptyStateText: {
|
||||
color: theme.colors.mutedForeground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontStyle: "italic",
|
||||
},
|
||||
}));
|
||||
@@ -7,17 +7,14 @@ import React, {
|
||||
ReactNode,
|
||||
} from "react";
|
||||
import { View, Text, Pressable } from "react-native";
|
||||
import { ScrollView } from "react-native-gesture-handler";
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
import { Fonts } from "@/constants/theme";
|
||||
import {
|
||||
BottomSheetModal,
|
||||
BottomSheetScrollView,
|
||||
BottomSheetBackdrop,
|
||||
} from "@gorhom/bottom-sheet";
|
||||
import { Pencil, Eye, SquareTerminal, Search, Wrench, X } from "lucide-react-native";
|
||||
import { parseToolCallDisplay, buildLineDiff, type DiffLine } from "@/utils/tool-call-parsers";
|
||||
import { DiffViewer } from "./diff-viewer";
|
||||
import { ToolCallDetailsContent, useToolCallDetails } from "./tool-call-details";
|
||||
|
||||
// ----- Types -----
|
||||
|
||||
@@ -56,22 +53,6 @@ const toolKindIcons: Record<string, React.ComponentType<{ size?: number; color?:
|
||||
search: Search,
|
||||
};
|
||||
|
||||
// ----- Helper Functions -----
|
||||
|
||||
function formatValue(value: unknown): string {
|
||||
if (value === undefined) {
|
||||
return "";
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return value;
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(value, null, 2);
|
||||
} catch {
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Provider Component -----
|
||||
|
||||
interface ToolCallSheetProviderProps {
|
||||
@@ -150,184 +131,7 @@ function ToolCallSheetContent({ data, onClose }: ToolCallSheetContentProps) {
|
||||
? toolKindIcons[kind.toLowerCase()] || Wrench
|
||||
: Wrench;
|
||||
|
||||
const serializedError = useMemo(
|
||||
() => (error !== undefined ? formatValue(error) : ""),
|
||||
[error]
|
||||
);
|
||||
|
||||
// Parse tool call display using discriminated union
|
||||
const toolCallDisplay = useMemo(
|
||||
() => parseToolCallDisplay(args, result),
|
||||
[args, result]
|
||||
);
|
||||
|
||||
// Compute diff lines for edit type
|
||||
const editDiffLines = useMemo((): DiffLine[] => {
|
||||
if (toolCallDisplay.type !== "edit") return [];
|
||||
return buildLineDiff(toolCallDisplay.oldString, toolCallDisplay.newString);
|
||||
}, [toolCallDisplay]);
|
||||
|
||||
// Render content sections
|
||||
const renderSections = useCallback(() => {
|
||||
const sections: ReactNode[] = [];
|
||||
|
||||
if (toolCallDisplay.type === "shell") {
|
||||
// Shell tool: show command and output as single block
|
||||
sections.push(
|
||||
<View key="shell" style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Command</Text>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
style={styles.jsonScroll}
|
||||
contentContainerStyle={styles.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text style={styles.scrollText}>{toolCallDisplay.command}</Text>
|
||||
</ScrollView>
|
||||
{toolCallDisplay.output ? (
|
||||
<ScrollView
|
||||
style={styles.scrollArea}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
nestedScrollEnabled
|
||||
showsVerticalScrollIndicator={true}
|
||||
>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text style={styles.scrollText}>{toolCallDisplay.output}</Text>
|
||||
</ScrollView>
|
||||
</ScrollView>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
} else if (toolCallDisplay.type === "edit") {
|
||||
// Edit tool: show file path and diff
|
||||
sections.push(
|
||||
<View key="edit" style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>File</Text>
|
||||
<View style={styles.fileBadge}>
|
||||
<Text style={styles.fileBadgeText}>{toolCallDisplay.filePath}</Text>
|
||||
</View>
|
||||
{editDiffLines.length > 0 ? (
|
||||
<View style={styles.diffContainer}>
|
||||
<DiffViewer diffLines={editDiffLines} maxHeight={300} />
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
} else if (toolCallDisplay.type === "read") {
|
||||
// Read tool: show file path and content
|
||||
sections.push(
|
||||
<View key="read" style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>File</Text>
|
||||
<View style={styles.fileBadge}>
|
||||
<Text style={styles.fileBadgeText}>{toolCallDisplay.filePath}</Text>
|
||||
</View>
|
||||
{(toolCallDisplay.offset !== undefined || toolCallDisplay.limit !== undefined) ? (
|
||||
<Text style={styles.rangeText}>
|
||||
{toolCallDisplay.offset !== undefined ? `Offset: ${toolCallDisplay.offset}` : ""}
|
||||
{toolCallDisplay.offset !== undefined && toolCallDisplay.limit !== undefined ? " • " : ""}
|
||||
{toolCallDisplay.limit !== undefined ? `Limit: ${toolCallDisplay.limit}` : ""}
|
||||
</Text>
|
||||
) : null}
|
||||
{toolCallDisplay.content ? (
|
||||
<ScrollView
|
||||
style={styles.scrollArea}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
nestedScrollEnabled
|
||||
showsVerticalScrollIndicator={true}
|
||||
>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text style={styles.scrollText}>{toolCallDisplay.content}</Text>
|
||||
</ScrollView>
|
||||
</ScrollView>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
// Generic tool: show input/output as key-value pairs
|
||||
if (toolCallDisplay.input.length > 0) {
|
||||
sections.push(
|
||||
<View key="input-header" style={styles.groupHeader}>
|
||||
<Text style={styles.groupHeaderText}>Input</Text>
|
||||
</View>
|
||||
);
|
||||
toolCallDisplay.input.forEach((pair, index) => {
|
||||
sections.push(
|
||||
<View key={`input-${index}-${pair.key}`} style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>{pair.key}</Text>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
style={styles.jsonScroll}
|
||||
contentContainerStyle={styles.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text style={styles.scrollText}>{pair.value}</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (toolCallDisplay.output.length > 0) {
|
||||
sections.push(
|
||||
<View key="output-header" style={styles.groupHeader}>
|
||||
<Text style={styles.groupHeaderText}>Output</Text>
|
||||
</View>
|
||||
);
|
||||
toolCallDisplay.output.forEach((pair, index) => {
|
||||
sections.push(
|
||||
<View key={`output-${index}-${pair.key}`} style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>{pair.key}</Text>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
style={styles.jsonScroll}
|
||||
contentContainerStyle={styles.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text style={styles.scrollText}>{pair.value}</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Always show errors if available
|
||||
if (error !== undefined) {
|
||||
sections.push(
|
||||
<View key="error" style={styles.section}>
|
||||
<Text style={[styles.sectionTitle, styles.errorText]}>Error</Text>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
style={[styles.jsonScroll, styles.jsonScrollError]}
|
||||
contentContainerStyle={styles.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text style={[styles.scrollText, styles.errorText]}>{serializedError}</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (sections.length === 0) {
|
||||
return (
|
||||
<Text style={styles.emptyStateText}>No additional details available</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return sections;
|
||||
}, [toolCallDisplay, editDiffLines, error, serializedError]);
|
||||
const { display, errorText } = useToolCallDetails({ args, result, error });
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
@@ -349,7 +153,7 @@ function ToolCallSheetContent({ data, onClose }: ToolCallSheetContentProps) {
|
||||
style={styles.content}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
>
|
||||
{renderSections()}
|
||||
<ToolCallDetailsContent display={display} errorText={errorText} />
|
||||
</BottomSheetScrollView>
|
||||
</View>
|
||||
);
|
||||
@@ -406,92 +210,5 @@ const styles = StyleSheet.create((theme) => ({
|
||||
paddingHorizontal: theme.spacing[4],
|
||||
paddingTop: theme.spacing[4],
|
||||
paddingBottom: theme.spacing[8],
|
||||
gap: theme.spacing[6],
|
||||
},
|
||||
groupHeader: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[2],
|
||||
paddingBottom: theme.spacing[1],
|
||||
borderBottomWidth: theme.borderWidth[1],
|
||||
borderBottomColor: theme.colors.border,
|
||||
},
|
||||
groupHeaderText: {
|
||||
color: theme.colors.primary,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontWeight: theme.fontWeight.bold,
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1,
|
||||
},
|
||||
section: {
|
||||
gap: theme.spacing[2],
|
||||
},
|
||||
sectionTitle: {
|
||||
color: theme.colors.mutedForeground,
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontWeight: theme.fontWeight.semibold,
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
fileBadge: {
|
||||
alignSelf: "flex-start",
|
||||
paddingHorizontal: theme.spacing[2],
|
||||
paddingVertical: theme.spacing[1],
|
||||
borderRadius: theme.borderRadius.base,
|
||||
borderWidth: theme.borderWidth[1],
|
||||
borderColor: theme.colors.border,
|
||||
backgroundColor: theme.colors.card,
|
||||
},
|
||||
fileBadgeText: {
|
||||
color: theme.colors.foreground,
|
||||
fontFamily: Fonts.mono,
|
||||
fontSize: theme.fontSize.xs,
|
||||
},
|
||||
rangeText: {
|
||||
color: theme.colors.mutedForeground,
|
||||
fontSize: theme.fontSize.xs,
|
||||
},
|
||||
diffContainer: {
|
||||
borderWidth: theme.borderWidth[1],
|
||||
borderColor: theme.colors.border,
|
||||
borderRadius: theme.borderRadius.base,
|
||||
overflow: "hidden",
|
||||
backgroundColor: theme.colors.card,
|
||||
},
|
||||
scrollArea: {
|
||||
borderWidth: theme.borderWidth[1],
|
||||
borderColor: theme.colors.border,
|
||||
borderRadius: theme.borderRadius.base,
|
||||
maxHeight: 260,
|
||||
backgroundColor: theme.colors.card,
|
||||
},
|
||||
scrollContent: {
|
||||
padding: theme.spacing[2],
|
||||
},
|
||||
scrollText: {
|
||||
fontFamily: Fonts.mono,
|
||||
fontSize: theme.fontSize.xs,
|
||||
color: theme.colors.foreground,
|
||||
lineHeight: 18,
|
||||
},
|
||||
jsonScroll: {
|
||||
borderWidth: theme.borderWidth[1],
|
||||
borderColor: theme.colors.border,
|
||||
borderRadius: theme.borderRadius.base,
|
||||
backgroundColor: theme.colors.card,
|
||||
},
|
||||
jsonScrollError: {
|
||||
borderColor: theme.colors.destructive,
|
||||
},
|
||||
jsonContent: {
|
||||
padding: theme.spacing[2],
|
||||
},
|
||||
errorText: {
|
||||
color: theme.colors.destructive,
|
||||
},
|
||||
emptyStateText: {
|
||||
color: theme.colors.mutedForeground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontStyle: "italic",
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user