mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
feat: update ToolCall to open bottom sheet on tap
- Import useToolCallSheet hook from new tool-call-sheet component - Replace inline expansion with openToolCall() to show details in bottom sheet - Remove ~300 lines of unused code (toolCallStylesheet, formatFullValue, buildLineDiffFromStrings, isStructuredToolResult, and inline rendering) - Clean up unused imports (ScrollView, DiffViewer, DiffLine) - Badge display unchanged - still shows icon, label, loading/error state 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { View, Text, Pressable, Animated, ScrollView } from "react-native";
|
||||
import { View, Text, Pressable, Animated } from "react-native";
|
||||
import { useState, useEffect, useRef, memo, useMemo, useCallback } from "react";
|
||||
import type { ReactNode, ComponentType } from "react";
|
||||
import type { AgentProvider } from "@server/server/agent/agent-sdk-types";
|
||||
@@ -27,9 +27,9 @@ import { baseColors, theme } from "@/styles/theme";
|
||||
import { Colors } from "@/constants/theme";
|
||||
import * as Clipboard from "expo-clipboard";
|
||||
import type { TodoEntry, ThoughtStatus } from "@/types/stream";
|
||||
import type { CommandDetails, EditEntry, ReadEntry, DiffLine } from "@/utils/tool-call-parsers";
|
||||
import { DiffViewer } from "./diff-viewer";
|
||||
import type { CommandDetails, EditEntry, ReadEntry } from "@/utils/tool-call-parsers";
|
||||
import { resolveToolCallPreview } from "./tool-call-preview";
|
||||
import { useToolCallSheet } from "./tool-call-sheet";
|
||||
|
||||
interface UserMessageProps {
|
||||
message: string;
|
||||
@@ -1094,191 +1094,14 @@ interface ToolCallProps {
|
||||
parsedCommandDetails?: CommandDetails | null;
|
||||
}
|
||||
|
||||
const toolCallStylesheet = StyleSheet.create((theme) => ({
|
||||
detailContent: {
|
||||
gap: theme.spacing[3],
|
||||
},
|
||||
section: {
|
||||
gap: theme.spacing[1],
|
||||
},
|
||||
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: "monospace",
|
||||
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,
|
||||
},
|
||||
scrollContent: {
|
||||
padding: theme.spacing[2],
|
||||
},
|
||||
scrollText: {
|
||||
fontFamily: "monospace",
|
||||
fontSize: theme.fontSize.xs,
|
||||
color: theme.colors.foreground,
|
||||
lineHeight: 18,
|
||||
},
|
||||
jsonScroll: {
|
||||
borderWidth: theme.borderWidth[1],
|
||||
borderColor: theme.colors.border,
|
||||
borderRadius: theme.borderRadius.base,
|
||||
},
|
||||
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",
|
||||
},
|
||||
metaRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[2],
|
||||
},
|
||||
metaLabel: {
|
||||
color: theme.colors.mutedForeground,
|
||||
fontSize: theme.fontSize.xs,
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
metaValue: {
|
||||
color: theme.colors.foreground,
|
||||
fontFamily: "monospace",
|
||||
fontSize: theme.fontSize.xs,
|
||||
flex: 1,
|
||||
},
|
||||
}));
|
||||
|
||||
// Icon mapping for tool kinds
|
||||
const toolKindIcons: Record<string, any> = {
|
||||
edit: Pencil,
|
||||
read: Eye,
|
||||
execute: SquareTerminal,
|
||||
search: Search,
|
||||
// Add more mappings as needed
|
||||
};
|
||||
|
||||
function formatFullValue(value: unknown): string {
|
||||
if (value === undefined) {
|
||||
return "";
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return value;
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(value, null, 2);
|
||||
} catch {
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to build diff lines (mirrors logic from tool-call-parsers)
|
||||
function buildLineDiffFromStrings(originalText: string, updatedText: string): DiffLine[] {
|
||||
const splitIntoLines = (text: string): string[] => {
|
||||
if (!text) return [];
|
||||
return text.replace(/\r\n/g, "\n").split("\n");
|
||||
};
|
||||
|
||||
const originalLines = splitIntoLines(originalText);
|
||||
const updatedLines = splitIntoLines(updatedText);
|
||||
|
||||
const hasAnyContent = originalLines.length > 0 || updatedLines.length > 0;
|
||||
if (!hasAnyContent) return [];
|
||||
|
||||
const m = originalLines.length;
|
||||
const n = updatedLines.length;
|
||||
const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
||||
|
||||
for (let i = m - 1; i >= 0; i -= 1) {
|
||||
for (let j = n - 1; j >= 0; j -= 1) {
|
||||
if (originalLines[i] === updatedLines[j]) {
|
||||
dp[i][j] = dp[i + 1][j + 1] + 1;
|
||||
} else {
|
||||
dp[i][j] = Math.max(dp[i + 1][j], dp[i][j + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const diff: DiffLine[] = [];
|
||||
let i = 0;
|
||||
let j = 0;
|
||||
|
||||
while (i < m && j < n) {
|
||||
if (originalLines[i] === updatedLines[j]) {
|
||||
diff.push({ type: "context", content: ` ${originalLines[i]}` });
|
||||
i += 1;
|
||||
j += 1;
|
||||
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
||||
diff.push({ type: "remove", content: `-${originalLines[i]}` });
|
||||
i += 1;
|
||||
} else {
|
||||
diff.push({ type: "add", content: `+${updatedLines[j]}` });
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
|
||||
while (i < m) {
|
||||
diff.push({ type: "remove", content: `-${originalLines[i]}` });
|
||||
i += 1;
|
||||
}
|
||||
|
||||
while (j < n) {
|
||||
diff.push({ type: "add", content: `+${updatedLines[j]}` });
|
||||
j += 1;
|
||||
}
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
// Type guard for structured tool results
|
||||
type StructuredToolResult = {
|
||||
type: "command" | "file_write" | "file_edit" | "file_read" | "generic";
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
function isStructuredToolResult(result: unknown): result is StructuredToolResult {
|
||||
return (
|
||||
typeof result === "object" &&
|
||||
result !== null &&
|
||||
"type" in result &&
|
||||
typeof result.type === "string" &&
|
||||
["command", "file_write", "file_edit", "file_read", "generic"].includes(result.type)
|
||||
);
|
||||
}
|
||||
|
||||
export const ToolCall = memo(function ToolCall({
|
||||
toolName,
|
||||
kind,
|
||||
@@ -1290,379 +1113,39 @@ export const ToolCall = memo(function ToolCall({
|
||||
parsedReadEntries,
|
||||
parsedCommandDetails,
|
||||
}: ToolCallProps) {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
// Check if result has a type field for structured rendering
|
||||
const structuredResult = useMemo(
|
||||
() => (isStructuredToolResult(result) ? result : null),
|
||||
[result]
|
||||
);
|
||||
const { openToolCall } = useToolCallSheet();
|
||||
|
||||
const IconComponent = kind
|
||||
? toolKindIcons[kind.toLowerCase()] || Wrench
|
||||
: Wrench;
|
||||
|
||||
const serializedArgs = useMemo(
|
||||
() => (args !== undefined ? formatFullValue(args) : ""),
|
||||
[args]
|
||||
);
|
||||
const serializedResult = useMemo(
|
||||
() => (result !== undefined ? formatFullValue(result) : ""),
|
||||
[result]
|
||||
);
|
||||
const serializedError = useMemo(
|
||||
() => (error !== undefined ? formatFullValue(error) : ""),
|
||||
[error]
|
||||
);
|
||||
// Check if there's any content to display in the sheet
|
||||
const hasDetails = args !== undefined || result !== undefined || error !== undefined;
|
||||
|
||||
const handleToggle = useCallback(() => {
|
||||
setIsExpanded((prev) => !prev);
|
||||
}, []);
|
||||
const handleOpenSheet = useCallback(() => {
|
||||
openToolCall({
|
||||
toolName,
|
||||
kind,
|
||||
status,
|
||||
args,
|
||||
result,
|
||||
error,
|
||||
parsedEditEntries,
|
||||
parsedReadEntries,
|
||||
parsedCommandDetails,
|
||||
});
|
||||
}, [openToolCall, toolName, kind, status, args, result, error, parsedEditEntries, parsedReadEntries, parsedCommandDetails]);
|
||||
|
||||
// Helper functions to extract data from structured results
|
||||
const extractCommandFromStructured = useCallback(
|
||||
(structured: StructuredToolResult): CommandDetails | null => {
|
||||
if (structured.type !== "command") return null;
|
||||
|
||||
const cmd: CommandDetails = {};
|
||||
if (typeof structured.command === "string") cmd.command = structured.command;
|
||||
if (typeof structured.cwd === "string") cmd.cwd = structured.cwd;
|
||||
if (typeof structured.output === "string") cmd.output = structured.output;
|
||||
if (typeof structured.exitCode === "number") cmd.exitCode = structured.exitCode;
|
||||
|
||||
return cmd.command || cmd.output ? cmd : null;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const extractDiffFromStructured = useCallback(
|
||||
(structured: StructuredToolResult): EditEntry[] => {
|
||||
if (structured.type !== "file_write" && structured.type !== "file_edit") {
|
||||
return [];
|
||||
}
|
||||
|
||||
const filePath = typeof structured.filePath === "string" ? structured.filePath : undefined;
|
||||
|
||||
// For file_write, create a diff from oldContent -> newContent
|
||||
if (structured.type === "file_write") {
|
||||
const oldContent = typeof structured.oldContent === "string" ? structured.oldContent : "";
|
||||
const newContent = typeof structured.newContent === "string" ? structured.newContent : "";
|
||||
|
||||
// Use the same diff building logic from tool-call-parsers
|
||||
const diffLines = buildLineDiffFromStrings(oldContent, newContent);
|
||||
if (diffLines.length > 0) {
|
||||
return [{ filePath, diffLines }];
|
||||
}
|
||||
}
|
||||
|
||||
// For file_edit, it might already have diffLines or we need to construct them
|
||||
if (structured.type === "file_edit") {
|
||||
// Check if diffLines are provided directly
|
||||
if (Array.isArray(structured.diffLines)) {
|
||||
return [{ filePath, diffLines: structured.diffLines as DiffLine[] }];
|
||||
}
|
||||
|
||||
// Otherwise try to build from old/new content
|
||||
const oldContent = typeof structured.oldContent === "string" ? structured.oldContent : "";
|
||||
const newContent = typeof structured.newContent === "string" ? structured.newContent : "";
|
||||
const diffLines = buildLineDiffFromStrings(oldContent, newContent);
|
||||
if (diffLines.length > 0) {
|
||||
return [{ filePath, diffLines }];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const extractReadFromStructured = useCallback(
|
||||
(structured: StructuredToolResult): ReadEntry[] => {
|
||||
if (structured.type !== "file_read") return [];
|
||||
|
||||
const filePath = typeof structured.filePath === "string" ? structured.filePath : undefined;
|
||||
const content = typeof structured.content === "string" ? structured.content : "";
|
||||
|
||||
if (content) {
|
||||
return [{ filePath, content }];
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const renderDetails = useCallback(() => {
|
||||
// If we have a structured result, use type-based rendering
|
||||
if (structuredResult) {
|
||||
const sections: ReactNode[] = [];
|
||||
|
||||
// Render based on type
|
||||
switch (structuredResult.type) {
|
||||
case "command": {
|
||||
// Use pre-parsed command details if available, otherwise extract from structured result
|
||||
const cmd = parsedCommandDetails ?? extractCommandFromStructured(structuredResult);
|
||||
if (cmd) {
|
||||
// Reuse the command section rendering logic
|
||||
sections.push(
|
||||
<View key="command" style={toolCallStylesheet.section}>
|
||||
<Text style={toolCallStylesheet.sectionTitle}>Command</Text>
|
||||
{cmd.command ? (
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
style={toolCallStylesheet.jsonScroll}
|
||||
contentContainerStyle={toolCallStylesheet.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text style={toolCallStylesheet.scrollText}>{cmd.command}</Text>
|
||||
</ScrollView>
|
||||
) : null}
|
||||
{cmd.cwd ? (
|
||||
<View style={toolCallStylesheet.metaRow}>
|
||||
<Text style={toolCallStylesheet.metaLabel}>Directory</Text>
|
||||
<Text style={toolCallStylesheet.metaValue}>{cmd.cwd}</Text>
|
||||
</View>
|
||||
) : null}
|
||||
{cmd.exitCode !== undefined ? (
|
||||
<View style={toolCallStylesheet.metaRow}>
|
||||
<Text style={toolCallStylesheet.metaLabel}>Exit Code</Text>
|
||||
<Text style={toolCallStylesheet.metaValue}>
|
||||
{cmd.exitCode === null ? "Unknown" : cmd.exitCode}
|
||||
</Text>
|
||||
</View>
|
||||
) : null}
|
||||
{cmd.output ? (
|
||||
<ScrollView
|
||||
style={toolCallStylesheet.scrollArea}
|
||||
contentContainerStyle={toolCallStylesheet.scrollContent}
|
||||
nestedScrollEnabled
|
||||
showsVerticalScrollIndicator={true}
|
||||
>
|
||||
<Text style={toolCallStylesheet.scrollText}>{cmd.output}</Text>
|
||||
</ScrollView>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "file_write":
|
||||
case "file_edit": {
|
||||
// Use pre-parsed entries if available, otherwise extract from structured result
|
||||
const diffs = parsedEditEntries?.length
|
||||
? parsedEditEntries
|
||||
: extractDiffFromStructured(structuredResult);
|
||||
diffs.forEach((entry, index) => {
|
||||
sections.push(
|
||||
<View
|
||||
key={`diff-${index}`}
|
||||
style={toolCallStylesheet.section}
|
||||
>
|
||||
<Text style={toolCallStylesheet.sectionTitle}>Diff</Text>
|
||||
{entry.filePath ? (
|
||||
<View style={toolCallStylesheet.fileBadge}>
|
||||
<Text style={toolCallStylesheet.fileBadgeText}>{entry.filePath}</Text>
|
||||
</View>
|
||||
) : null}
|
||||
<View style={toolCallStylesheet.diffContainer}>
|
||||
<DiffViewer diffLines={entry.diffLines} maxHeight={240} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "file_read": {
|
||||
// Use pre-parsed entries if available, otherwise extract from structured result
|
||||
const reads = parsedReadEntries?.length
|
||||
? parsedReadEntries
|
||||
: extractReadFromStructured(structuredResult);
|
||||
reads.forEach((entry, index) => {
|
||||
sections.push(
|
||||
<View
|
||||
key={`read-${index}`}
|
||||
style={toolCallStylesheet.section}
|
||||
>
|
||||
<Text style={toolCallStylesheet.sectionTitle}>Read Result</Text>
|
||||
{entry.filePath ? (
|
||||
<View style={toolCallStylesheet.fileBadge}>
|
||||
<Text style={toolCallStylesheet.fileBadgeText}>{entry.filePath}</Text>
|
||||
</View>
|
||||
) : null}
|
||||
<ScrollView
|
||||
style={toolCallStylesheet.scrollArea}
|
||||
contentContainerStyle={toolCallStylesheet.scrollContent}
|
||||
nestedScrollEnabled
|
||||
showsVerticalScrollIndicator={true}
|
||||
>
|
||||
<Text style={toolCallStylesheet.scrollText}>{entry.content}</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "generic":
|
||||
default: {
|
||||
// Show raw JSON for generic type
|
||||
if (result !== undefined) {
|
||||
sections.push(
|
||||
<View key="result" style={toolCallStylesheet.section}>
|
||||
<Text style={toolCallStylesheet.sectionTitle}>Result</Text>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
style={toolCallStylesheet.jsonScroll}
|
||||
contentContainerStyle={toolCallStylesheet.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text style={toolCallStylesheet.scrollText}>{serializedResult}</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Always show args if available
|
||||
if (args !== undefined) {
|
||||
sections.unshift(
|
||||
<View key="args" style={toolCallStylesheet.section}>
|
||||
<Text style={toolCallStylesheet.sectionTitle}>Arguments</Text>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
style={toolCallStylesheet.jsonScroll}
|
||||
contentContainerStyle={toolCallStylesheet.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text style={toolCallStylesheet.scrollText}>{serializedArgs}</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// Always show errors if available
|
||||
if (error !== undefined) {
|
||||
sections.push(
|
||||
<View key="error" style={toolCallStylesheet.section}>
|
||||
<Text style={toolCallStylesheet.sectionTitle}>Error</Text>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
style={[toolCallStylesheet.jsonScroll, toolCallStylesheet.jsonScrollError]}
|
||||
contentContainerStyle={toolCallStylesheet.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text style={[toolCallStylesheet.scrollText, toolCallStylesheet.errorText]}>
|
||||
{serializedError}
|
||||
</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (sections.length === 0) {
|
||||
return (
|
||||
<Text style={toolCallStylesheet.emptyStateText}>
|
||||
No additional details available
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return <View style={toolCallStylesheet.detailContent}>{sections}</View>;
|
||||
}
|
||||
|
||||
// No structured result - show raw JSON
|
||||
const sections: ReactNode[] = [];
|
||||
|
||||
if (args !== undefined) {
|
||||
sections.push(
|
||||
<View key="args" style={toolCallStylesheet.section}>
|
||||
<Text style={toolCallStylesheet.sectionTitle}>Arguments</Text>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
style={toolCallStylesheet.jsonScroll}
|
||||
contentContainerStyle={toolCallStylesheet.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text style={toolCallStylesheet.scrollText}>{serializedArgs}</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (result !== undefined) {
|
||||
sections.push(
|
||||
<View key="result" style={toolCallStylesheet.section}>
|
||||
<Text style={toolCallStylesheet.sectionTitle}>Result</Text>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
style={toolCallStylesheet.jsonScroll}
|
||||
contentContainerStyle={toolCallStylesheet.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text style={toolCallStylesheet.scrollText}>{serializedResult}</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (error !== undefined) {
|
||||
sections.push(
|
||||
<View key="error" style={toolCallStylesheet.section}>
|
||||
<Text style={toolCallStylesheet.sectionTitle}>Error</Text>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
style={[toolCallStylesheet.jsonScroll, toolCallStylesheet.jsonScrollError]}
|
||||
contentContainerStyle={toolCallStylesheet.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text style={[toolCallStylesheet.scrollText, toolCallStylesheet.errorText]}>
|
||||
{serializedError}
|
||||
</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (sections.length === 0) {
|
||||
return (
|
||||
<Text style={toolCallStylesheet.emptyStateText}>
|
||||
No additional details available
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return <View style={toolCallStylesheet.detailContent}>{sections}</View>;
|
||||
}, [
|
||||
structuredResult,
|
||||
extractCommandFromStructured,
|
||||
extractDiffFromStructured,
|
||||
extractReadFromStructured,
|
||||
serializedArgs,
|
||||
serializedResult,
|
||||
serializedError,
|
||||
args,
|
||||
result,
|
||||
error,
|
||||
]);
|
||||
// Dummy renderDetails to make badge tappable - actual content is rendered in the sheet
|
||||
const dummyRenderDetails = useCallback(() => null, []);
|
||||
|
||||
return (
|
||||
<ExpandableBadge
|
||||
label={toolName}
|
||||
icon={IconComponent}
|
||||
isExpanded={isExpanded}
|
||||
onToggle={handleToggle}
|
||||
renderDetails={renderDetails}
|
||||
isExpanded={false}
|
||||
onToggle={handleOpenSheet}
|
||||
renderDetails={hasDetails ? dummyRenderDetails : undefined}
|
||||
isLoading={status === "executing"}
|
||||
isError={status === "failed"}
|
||||
/>
|
||||
|
||||
3
plan.md
3
plan.md
@@ -537,13 +537,14 @@ interface ToolCallSheetData {
|
||||
- Run typecheck after changes.
|
||||
- **Done (2025-12-21 19:15)**: Created `tool-call-sheet.tsx` with `ToolCallSheetProvider` and `useToolCallSheet` hook. Component uses `@gorhom/bottom-sheet` BottomSheetModal with snap points ["50%", "90%"]. Includes header with tool icon, name, and status badge. Content reuses same rendering logic from `ToolCall.renderDetails()` for commands, file edits, file reads, and generic results. Typecheck passes.
|
||||
|
||||
- [ ] **Implement**: Update ToolCall component to open bottom sheet on tap.
|
||||
- [x] **Implement**: Update ToolCall component to open bottom sheet on tap.
|
||||
|
||||
- Import `useToolCallSheet` from new context
|
||||
- Replace inline expansion with `openToolCall(data)` on tap
|
||||
- Keep badge display unchanged (icon, label, loading state)
|
||||
- Remove `isExpanded` state and `renderDetails` prop from ExpandableBadge usage
|
||||
- Run typecheck after changes.
|
||||
- **Done (2025-12-21 19:25)**: Updated `ToolCall` in `message.tsx` to use `useToolCallSheet` hook. On tap, calls `openToolCall()` with tool data instead of expanding inline. Removed ~300 lines of unused code including `toolCallStylesheet`, `formatFullValue`, `buildLineDiffFromStrings`, `isStructuredToolResult`, and inline rendering logic (all now in `tool-call-sheet.tsx`). Cleaned up imports (`ScrollView`, `DiffViewer`, `DiffLine` no longer needed). Typecheck passes.
|
||||
|
||||
- [ ] **Implement**: Integrate ToolCallSheetProvider in agent stream view.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user