125 lines
2.9 KiB
TypeScript
125 lines
2.9 KiB
TypeScript
import { memo, useState } from "react";
|
||
import { Pressable, StyleSheet, Text, View } from "react-native";
|
||
import { colors, monoFont } from "@/lib/theme";
|
||
|
||
type Props = {
|
||
toolName: string;
|
||
status: "running" | "done" | "error";
|
||
argsPreview?: string;
|
||
detail?: string;
|
||
};
|
||
|
||
const STATUS_COLOR: Record<Props["status"], string> = {
|
||
running: colors.warning,
|
||
done: colors.success,
|
||
error: colors.error,
|
||
};
|
||
|
||
function ToolCallCardComponent({ toolName, status, argsPreview, detail }: Props) {
|
||
const [expanded, setExpanded] = useState(false);
|
||
const output = detail?.trim() ?? "";
|
||
const expandable = output.length > 0;
|
||
|
||
return (
|
||
<Pressable
|
||
style={styles.container}
|
||
onPress={expandable ? () => setExpanded((value) => !value) : undefined}
|
||
>
|
||
<View style={styles.header}>
|
||
<Text style={[styles.prompt, { color: STATUS_COLOR[status] }]}>
|
||
{status === "error" ? "✕" : "❯"}
|
||
</Text>
|
||
<Text style={styles.name} numberOfLines={1}>
|
||
{toolName}
|
||
</Text>
|
||
{argsPreview ? (
|
||
<Text style={styles.args} numberOfLines={1}>
|
||
{argsPreview}
|
||
</Text>
|
||
) : (
|
||
<View style={styles.spacer} />
|
||
)}
|
||
{status === "running" ? <Text style={styles.statusLabel}>running…</Text> : null}
|
||
{expandable ? <Text style={styles.chevron}>{expanded ? "−" : "+"}</Text> : null}
|
||
</View>
|
||
|
||
{expandable ? (
|
||
<View style={styles.outputWrap}>
|
||
{expanded ? (
|
||
<Text style={styles.output} selectable>
|
||
{output}
|
||
</Text>
|
||
) : (
|
||
<Text style={styles.outputPreview} numberOfLines={2}>
|
||
{output}
|
||
</Text>
|
||
)}
|
||
</View>
|
||
) : null}
|
||
</Pressable>
|
||
);
|
||
}
|
||
|
||
export const ToolCallCard = memo(ToolCallCardComponent);
|
||
|
||
const styles = StyleSheet.create({
|
||
container: {
|
||
alignSelf: "stretch",
|
||
paddingVertical: 2,
|
||
gap: 4,
|
||
},
|
||
header: {
|
||
flexDirection: "row",
|
||
alignItems: "center",
|
||
gap: 8,
|
||
},
|
||
prompt: {
|
||
fontFamily: monoFont,
|
||
fontSize: 12,
|
||
fontWeight: "700",
|
||
},
|
||
name: {
|
||
color: colors.textSecondary,
|
||
fontFamily: monoFont,
|
||
fontSize: 13,
|
||
fontWeight: "600",
|
||
},
|
||
args: {
|
||
flex: 1,
|
||
color: colors.textMuted,
|
||
fontFamily: monoFont,
|
||
fontSize: 12,
|
||
},
|
||
spacer: {
|
||
flex: 1,
|
||
},
|
||
statusLabel: {
|
||
color: colors.warning,
|
||
fontFamily: monoFont,
|
||
fontSize: 11,
|
||
},
|
||
chevron: {
|
||
color: colors.textMuted,
|
||
fontFamily: monoFont,
|
||
fontSize: 13,
|
||
},
|
||
outputWrap: {
|
||
borderLeftWidth: 1,
|
||
borderLeftColor: colors.borderSubtle,
|
||
marginLeft: 4,
|
||
paddingLeft: 10,
|
||
},
|
||
output: {
|
||
color: colors.textSecondary,
|
||
fontFamily: monoFont,
|
||
fontSize: 12,
|
||
lineHeight: 17,
|
||
},
|
||
outputPreview: {
|
||
color: colors.textMuted,
|
||
fontFamily: monoFont,
|
||
fontSize: 12,
|
||
lineHeight: 17,
|
||
},
|
||
});
|