refactor: improve UI consistency across message components and theme styles
This commit is contained in:
@@ -1,12 +1,21 @@
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { View, Text, ScrollView, Pressable } from 'react-native';
|
import { View, Text, ScrollView, Pressable } from "react-native";
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { StyleSheet, useUnistyles } from 'react-native-unistyles';
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import { BottomSheetModal } from '@gorhom/bottom-sheet';
|
import { BottomSheetModal } from "@gorhom/bottom-sheet";
|
||||||
import { AssistantMessage, UserMessage, ActivityLog, ToolCall } from './message';
|
import {
|
||||||
import { ToolCallBottomSheet } from './tool-call-bottom-sheet';
|
AssistantMessage,
|
||||||
import type { StreamItem } from '@/types/stream';
|
UserMessage,
|
||||||
import type { SelectedToolCall, PendingPermission, AgentInfo } from '@/types/shared';
|
ActivityLog,
|
||||||
|
ToolCall,
|
||||||
|
} from "./message";
|
||||||
|
import { ToolCallBottomSheet } from "./tool-call-bottom-sheet";
|
||||||
|
import type { StreamItem } from "@/types/stream";
|
||||||
|
import type {
|
||||||
|
SelectedToolCall,
|
||||||
|
PendingPermission,
|
||||||
|
AgentInfo,
|
||||||
|
} from "@/types/shared";
|
||||||
|
|
||||||
export interface AgentStreamViewProps {
|
export interface AgentStreamViewProps {
|
||||||
agentId: string;
|
agentId: string;
|
||||||
@@ -26,7 +35,8 @@ export function AgentStreamView({
|
|||||||
const scrollViewRef = useRef<ScrollView>(null);
|
const scrollViewRef = useRef<ScrollView>(null);
|
||||||
const bottomSheetRef = useRef<BottomSheetModal | null>(null);
|
const bottomSheetRef = useRef<BottomSheetModal | null>(null);
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const [selectedToolCall, setSelectedToolCall] = useState<SelectedToolCall | null>(null);
|
const [selectedToolCall, setSelectedToolCall] =
|
||||||
|
useState<SelectedToolCall | null>(null);
|
||||||
|
|
||||||
// Auto-scroll to bottom when new items arrive
|
// Auto-scroll to bottom when new items arrive
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -44,7 +54,10 @@ export function AgentStreamView({
|
|||||||
<ScrollView
|
<ScrollView
|
||||||
ref={scrollViewRef}
|
ref={scrollViewRef}
|
||||||
style={stylesheet.scrollView}
|
style={stylesheet.scrollView}
|
||||||
contentContainerStyle={{ paddingTop: 24, paddingBottom: Math.max(insets.bottom, 32) }}
|
contentContainerStyle={{
|
||||||
|
paddingTop: 24,
|
||||||
|
paddingBottom: Math.max(insets.bottom, 32),
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{streamItems.length === 0 ? (
|
{streamItems.length === 0 ? (
|
||||||
<View style={stylesheet.emptyState}>
|
<View style={stylesheet.emptyState}>
|
||||||
@@ -55,7 +68,7 @@ export function AgentStreamView({
|
|||||||
) : (
|
) : (
|
||||||
streamItems.map((item) => {
|
streamItems.map((item) => {
|
||||||
switch (item.kind) {
|
switch (item.kind) {
|
||||||
case 'user_message':
|
case "user_message":
|
||||||
return (
|
return (
|
||||||
<UserMessage
|
<UserMessage
|
||||||
key={item.id}
|
key={item.id}
|
||||||
@@ -64,7 +77,7 @@ export function AgentStreamView({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
case 'assistant_message':
|
case "assistant_message":
|
||||||
return (
|
return (
|
||||||
<AssistantMessage
|
<AssistantMessage
|
||||||
key={item.id}
|
key={item.id}
|
||||||
@@ -73,7 +86,7 @@ export function AgentStreamView({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
case 'thought':
|
case "thought":
|
||||||
return (
|
return (
|
||||||
<ActivityLog
|
<ActivityLog
|
||||||
key={item.id}
|
key={item.id}
|
||||||
@@ -83,27 +96,31 @@ export function AgentStreamView({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
case 'tool_call': {
|
case "tool_call": {
|
||||||
const { payload } = item;
|
const { payload } = item;
|
||||||
|
|
||||||
// Extract data based on source
|
// Extract data based on source
|
||||||
if (payload.source === 'acp') {
|
if (payload.source === "acp") {
|
||||||
const data = payload.data;
|
const data = payload.data;
|
||||||
// Map ACP status to display status
|
// Map ACP status to display status
|
||||||
const toolStatus = data.status === 'pending' || data.status === 'in_progress'
|
const toolStatus =
|
||||||
? 'executing' as const
|
data.status === "pending" || data.status === "in_progress"
|
||||||
: data.status === 'completed'
|
? ("executing" as const)
|
||||||
? 'completed' as const
|
: data.status === "completed"
|
||||||
: 'failed' as const;
|
? ("completed" as const)
|
||||||
|
: ("failed" as const);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolCall
|
<ToolCall
|
||||||
key={item.id}
|
key={item.id}
|
||||||
toolName={data.title ?? 'Unknown Tool'}
|
toolName={data.title ?? "Unknown Tool"}
|
||||||
|
kind={data.kind}
|
||||||
args={data.rawInput}
|
args={data.rawInput}
|
||||||
result={data.rawOutput}
|
result={data.rawOutput}
|
||||||
status={toolStatus}
|
status={toolStatus}
|
||||||
onOpenDetails={() => handleOpenToolCallDetails({ payload })}
|
onOpenDetails={() =>
|
||||||
|
handleOpenToolCallDetails({ payload })
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -116,18 +133,20 @@ export function AgentStreamView({
|
|||||||
args={data.arguments}
|
args={data.arguments}
|
||||||
result={data.result}
|
result={data.result}
|
||||||
status={data.status}
|
status={data.status}
|
||||||
onOpenDetails={() => handleOpenToolCallDetails({ payload })}
|
onOpenDetails={() =>
|
||||||
|
handleOpenToolCallDetails({ payload })
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'plan':
|
case "plan":
|
||||||
// TODO: Render plan component
|
// TODO: Render plan component
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
case 'activity_log':
|
case "activity_log":
|
||||||
case 'artifact':
|
case "artifact":
|
||||||
// These are orchestrator-only, skip for now
|
// These are orchestrator-only, skip for now
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@@ -178,9 +197,9 @@ function PermissionRequestCard({
|
|||||||
// Determine permission type and content based on toolCall
|
// Determine permission type and content based on toolCall
|
||||||
const getPermissionInfo = () => {
|
const getPermissionInfo = () => {
|
||||||
const rawInput = permission.toolCall?.rawInput || {};
|
const rawInput = permission.toolCall?.rawInput || {};
|
||||||
const toolCallId = permission.toolCall?.toolCallId || '';
|
const toolCallId = permission.toolCall?.toolCallId || "";
|
||||||
|
|
||||||
console.log('[PermissionCard] Tool call details:', {
|
console.log("[PermissionCard] Tool call details:", {
|
||||||
toolCallId,
|
toolCallId,
|
||||||
rawInputKeys: Object.keys(rawInput),
|
rawInputKeys: Object.keys(rawInput),
|
||||||
rawInput,
|
rawInput,
|
||||||
@@ -189,41 +208,44 @@ function PermissionRequestCard({
|
|||||||
// Check if this is a plan (ExitPlanMode)
|
// Check if this is a plan (ExitPlanMode)
|
||||||
if (rawInput.plan) {
|
if (rawInput.plan) {
|
||||||
return {
|
return {
|
||||||
title: 'Plan Ready for Review',
|
title: "Plan Ready for Review",
|
||||||
content: rawInput.plan,
|
content: rawInput.plan,
|
||||||
type: 'plan' as const,
|
type: "plan" as const,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is a file operation (Write, Edit, etc.)
|
// Check if this is a file operation (Write, Edit, etc.)
|
||||||
if (rawInput.file_path) {
|
if (rawInput.file_path) {
|
||||||
const operation = toolCallId.includes('Write') ? 'Create' : 'Edit';
|
const operation = toolCallId.includes("Write") ? "Create" : "Edit";
|
||||||
const fileContent = rawInput.content || rawInput.new_string || '';
|
const fileContent = rawInput.content || rawInput.new_string || "";
|
||||||
const preview = fileContent.length > 500
|
const preview =
|
||||||
? fileContent.slice(0, 500) + '\n\n... (truncated)'
|
fileContent.length > 500
|
||||||
: fileContent;
|
? fileContent.slice(0, 500) + "\n\n... (truncated)"
|
||||||
|
: fileContent;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: `${operation} File Permission`,
|
title: `${operation} File Permission`,
|
||||||
content: `File: ${rawInput.file_path}\n\n${preview || '(empty file)'}`,
|
content: `File: ${rawInput.file_path}\n\n${preview || "(empty file)"}`,
|
||||||
type: 'file' as const,
|
type: "file" as const,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is a command (Bash)
|
// Check if this is a command (Bash)
|
||||||
if (rawInput.command) {
|
if (rawInput.command) {
|
||||||
return {
|
return {
|
||||||
title: 'Run Command Permission',
|
title: "Run Command Permission",
|
||||||
content: `Command: ${rawInput.command}\n\nDescription: ${rawInput.description || 'No description'}`,
|
content: `Command: ${rawInput.command}\n\nDescription: ${
|
||||||
type: 'command' as const,
|
rawInput.description || "No description"
|
||||||
|
}`,
|
||||||
|
type: "command" as const,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback - show whatever is in rawInput
|
// Fallback - show whatever is in rawInput
|
||||||
return {
|
return {
|
||||||
title: 'Permission Required',
|
title: "Permission Required",
|
||||||
content: JSON.stringify(rawInput, null, 2),
|
content: JSON.stringify(rawInput, null, 2),
|
||||||
type: 'unknown' as const,
|
type: "unknown" as const,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -233,22 +255,42 @@ function PermissionRequestCard({
|
|||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
permissionStyles.container,
|
permissionStyles.container,
|
||||||
{ backgroundColor: theme.colors.secondary, borderColor: theme.colors.border },
|
{
|
||||||
|
backgroundColor: theme.colors.secondary,
|
||||||
|
borderColor: theme.colors.border,
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Text style={[permissionStyles.title, { color: theme.colors.foreground }]}>
|
<Text
|
||||||
|
style={[permissionStyles.title, { color: theme.colors.foreground }]}
|
||||||
|
>
|
||||||
{permissionInfo.title}
|
{permissionInfo.title}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{permissionInfo.content && (
|
{permissionInfo.content && (
|
||||||
<View style={[permissionStyles.planContainer, { backgroundColor: theme.colors.background }]}>
|
<View
|
||||||
<Text style={[permissionStyles.planText, { color: theme.colors.foreground }]}>
|
style={[
|
||||||
|
permissionStyles.planContainer,
|
||||||
|
{ backgroundColor: theme.colors.background },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
permissionStyles.planText,
|
||||||
|
{ color: theme.colors.foreground },
|
||||||
|
]}
|
||||||
|
>
|
||||||
{permissionInfo.content}
|
{permissionInfo.content}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Text style={[permissionStyles.question, { color: theme.colors.mutedForeground }]}>
|
<Text
|
||||||
|
style={[
|
||||||
|
permissionStyles.question,
|
||||||
|
{ color: theme.colors.mutedForeground },
|
||||||
|
]}
|
||||||
|
>
|
||||||
How would you like to proceed?
|
How would you like to proceed?
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
@@ -259,14 +301,19 @@ function PermissionRequestCard({
|
|||||||
style={[
|
style={[
|
||||||
permissionStyles.optionButton,
|
permissionStyles.optionButton,
|
||||||
{
|
{
|
||||||
backgroundColor: option.kind.includes('reject')
|
backgroundColor: option.kind.includes("reject")
|
||||||
? theme.colors.destructive
|
? theme.colors.destructive
|
||||||
: theme.colors.primary,
|
: theme.colors.primary,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
onPress={() => onResponse(permission.requestId, option.optionId)}
|
onPress={() => onResponse(permission.requestId, option.optionId)}
|
||||||
>
|
>
|
||||||
<Text style={[permissionStyles.optionText, { color: theme.colors.primaryForeground }]}>
|
<Text
|
||||||
|
style={[
|
||||||
|
permissionStyles.optionText,
|
||||||
|
{ color: theme.colors.primaryForeground },
|
||||||
|
]}
|
||||||
|
>
|
||||||
{option.name}
|
{option.name}
|
||||||
</Text>
|
</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
@@ -283,7 +330,7 @@ const stylesheet = StyleSheet.create((theme) => ({
|
|||||||
},
|
},
|
||||||
scrollView: {
|
scrollView: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
paddingHorizontal: theme.spacing[4],
|
paddingHorizontal: theme.spacing[2],
|
||||||
},
|
},
|
||||||
emptyState: {
|
emptyState: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|||||||
@@ -1,8 +1,25 @@
|
|||||||
import { View, Text, Pressable, Animated } from 'react-native';
|
import { View, Text, Pressable, Animated } from "react-native";
|
||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef } from "react";
|
||||||
import Markdown from 'react-native-markdown-display';
|
import Markdown from "react-native-markdown-display";
|
||||||
import { Circle, Info, CheckCircle, XCircle, FileText, ChevronRight, ChevronDown, RefreshCw } from 'lucide-react-native';
|
import {
|
||||||
import { StyleSheet } from 'react-native-unistyles';
|
Circle,
|
||||||
|
Info,
|
||||||
|
CheckCircle,
|
||||||
|
XCircle,
|
||||||
|
FileText,
|
||||||
|
ChevronRight,
|
||||||
|
ChevronDown,
|
||||||
|
Loader2,
|
||||||
|
Check,
|
||||||
|
X,
|
||||||
|
Wrench,
|
||||||
|
Pencil,
|
||||||
|
Eye,
|
||||||
|
SquareTerminal,
|
||||||
|
} from "lucide-react-native";
|
||||||
|
import { StyleSheet } from "react-native-unistyles";
|
||||||
|
import { baseColors, theme } from "@/styles/theme";
|
||||||
|
import { Colors } from "@/constants/theme";
|
||||||
|
|
||||||
interface UserMessageProps {
|
interface UserMessageProps {
|
||||||
message: string;
|
message: string;
|
||||||
@@ -11,18 +28,18 @@ interface UserMessageProps {
|
|||||||
|
|
||||||
const userMessageStylesheet = StyleSheet.create((theme) => ({
|
const userMessageStylesheet = StyleSheet.create((theme) => ({
|
||||||
container: {
|
container: {
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
justifyContent: 'flex-end',
|
justifyContent: "flex-end",
|
||||||
marginBottom: theme.spacing[3],
|
marginBottom: theme.spacing[3],
|
||||||
paddingHorizontal: theme.spacing[4],
|
paddingHorizontal: theme.spacing[4],
|
||||||
},
|
},
|
||||||
bubble: {
|
bubble: {
|
||||||
backgroundColor: theme.colors.primary,
|
backgroundColor: theme.colors.primary,
|
||||||
borderRadius: theme.borderRadius['2xl'],
|
borderRadius: theme.borderRadius["2xl"],
|
||||||
borderTopRightRadius: theme.borderRadius.sm,
|
borderTopRightRadius: theme.borderRadius.sm,
|
||||||
paddingHorizontal: theme.spacing[4],
|
paddingHorizontal: theme.spacing[4],
|
||||||
paddingVertical: theme.spacing[3],
|
paddingVertical: theme.spacing[3],
|
||||||
maxWidth: '80%',
|
maxWidth: "80%",
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
color: theme.colors.primaryForeground,
|
color: theme.colors.primaryForeground,
|
||||||
@@ -61,9 +78,66 @@ const assistantMessageStylesheet = StyleSheet.create((theme) => ({
|
|||||||
fontSize: theme.fontSize.xs,
|
fontSize: theme.fontSize.xs,
|
||||||
fontWeight: theme.fontWeight.bold,
|
fontWeight: theme.fontWeight.bold,
|
||||||
},
|
},
|
||||||
|
// Markdown styles
|
||||||
|
markdownBody: {
|
||||||
|
color: theme.colors.foreground,
|
||||||
|
fontSize: theme.fontSize.base,
|
||||||
|
lineHeight: 24,
|
||||||
|
},
|
||||||
|
markdownParagraph: {
|
||||||
|
marginTop: 0,
|
||||||
|
marginBottom: theme.spacing[2],
|
||||||
|
},
|
||||||
|
markdownStrong: {
|
||||||
|
fontWeight: theme.fontWeight.bold,
|
||||||
|
},
|
||||||
|
markdownEm: {
|
||||||
|
fontStyle: "italic" as const,
|
||||||
|
},
|
||||||
|
markdownCodeInline: {
|
||||||
|
backgroundColor: theme.colors.secondary,
|
||||||
|
color: theme.colors.secondaryForeground,
|
||||||
|
paddingHorizontal: theme.spacing[2],
|
||||||
|
paddingVertical: 2,
|
||||||
|
borderRadius: theme.borderRadius.sm,
|
||||||
|
fontFamily: "monospace",
|
||||||
|
fontSize: 13,
|
||||||
|
},
|
||||||
|
markdownCodeBlock: {
|
||||||
|
backgroundColor: theme.colors.secondary,
|
||||||
|
color: theme.colors.secondaryForeground,
|
||||||
|
padding: theme.spacing[3],
|
||||||
|
borderRadius: theme.borderRadius.md,
|
||||||
|
fontFamily: "monospace",
|
||||||
|
fontSize: 13,
|
||||||
|
},
|
||||||
|
markdownFence: {
|
||||||
|
backgroundColor: theme.colors.secondary,
|
||||||
|
borderColor: theme.colors.border,
|
||||||
|
color: theme.colors.secondaryForeground,
|
||||||
|
padding: theme.spacing[3],
|
||||||
|
borderRadius: theme.borderRadius.md,
|
||||||
|
marginVertical: theme.spacing[2],
|
||||||
|
fontFamily: "monospace",
|
||||||
|
fontSize: 13,
|
||||||
|
},
|
||||||
|
markdownLink: {
|
||||||
|
color: theme.colors.primary,
|
||||||
|
textDecorationLine: "underline" as const,
|
||||||
|
},
|
||||||
|
markdownList: {
|
||||||
|
marginBottom: theme.spacing[2],
|
||||||
|
},
|
||||||
|
markdownListItem: {
|
||||||
|
marginBottom: theme.spacing[1],
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export function AssistantMessage({ message, timestamp, isStreaming = false }: AssistantMessageProps) {
|
export function AssistantMessage({
|
||||||
|
message,
|
||||||
|
timestamp,
|
||||||
|
isStreaming = false,
|
||||||
|
}: AssistantMessageProps) {
|
||||||
const fadeAnim = useRef(new Animated.Value(0.3)).current;
|
const fadeAnim = useRef(new Animated.Value(0.3)).current;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -89,65 +163,29 @@ export function AssistantMessage({ message, timestamp, isStreaming = false }: As
|
|||||||
}, [isStreaming, fadeAnim]);
|
}, [isStreaming, fadeAnim]);
|
||||||
|
|
||||||
const markdownStyles = {
|
const markdownStyles = {
|
||||||
body: {
|
body: assistantMessageStylesheet.markdownBody,
|
||||||
color: '#f0fdfa',
|
paragraph: assistantMessageStylesheet.markdownParagraph,
|
||||||
fontSize: 16,
|
strong: assistantMessageStylesheet.markdownStrong,
|
||||||
lineHeight: 24,
|
em: assistantMessageStylesheet.markdownEm,
|
||||||
},
|
code_inline: assistantMessageStylesheet.markdownCodeInline,
|
||||||
paragraph: {
|
code_block: assistantMessageStylesheet.markdownCodeBlock,
|
||||||
marginTop: 0,
|
fence: assistantMessageStylesheet.markdownFence,
|
||||||
marginBottom: 8,
|
link: assistantMessageStylesheet.markdownLink,
|
||||||
},
|
bullet_list: assistantMessageStylesheet.markdownList,
|
||||||
strong: {
|
ordered_list: assistantMessageStylesheet.markdownList,
|
||||||
fontWeight: '700' as const,
|
list_item: assistantMessageStylesheet.markdownListItem,
|
||||||
},
|
|
||||||
em: {
|
|
||||||
fontStyle: 'italic' as const,
|
|
||||||
},
|
|
||||||
code_inline: {
|
|
||||||
backgroundColor: '#134e4a',
|
|
||||||
color: '#ccfbf1',
|
|
||||||
paddingHorizontal: 4,
|
|
||||||
paddingVertical: 2,
|
|
||||||
borderRadius: 3,
|
|
||||||
fontFamily: 'monospace',
|
|
||||||
},
|
|
||||||
code_block: {
|
|
||||||
backgroundColor: '#134e4a',
|
|
||||||
color: '#ccfbf1',
|
|
||||||
padding: 12,
|
|
||||||
borderRadius: 6,
|
|
||||||
fontFamily: 'monospace',
|
|
||||||
fontSize: 14,
|
|
||||||
},
|
|
||||||
fence: {
|
|
||||||
backgroundColor: '#134e4a',
|
|
||||||
color: '#ccfbf1',
|
|
||||||
padding: 12,
|
|
||||||
borderRadius: 6,
|
|
||||||
fontFamily: 'monospace',
|
|
||||||
fontSize: 14,
|
|
||||||
},
|
|
||||||
link: {
|
|
||||||
color: '#5eead4',
|
|
||||||
textDecorationLine: 'underline' as const,
|
|
||||||
},
|
|
||||||
bullet_list: {
|
|
||||||
marginBottom: 8,
|
|
||||||
},
|
|
||||||
ordered_list: {
|
|
||||||
marginBottom: 8,
|
|
||||||
},
|
|
||||||
list_item: {
|
|
||||||
marginBottom: 4,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={assistantMessageStylesheet.container}>
|
<View style={assistantMessageStylesheet.container}>
|
||||||
<Markdown style={markdownStyles}>{message}</Markdown>
|
<Markdown style={markdownStyles}>{message}</Markdown>
|
||||||
{isStreaming && (
|
{isStreaming && (
|
||||||
<Animated.View style={[assistantMessageStylesheet.streamingIndicator, { opacity: fadeAnim }]}>
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
assistantMessageStylesheet.streamingIndicator,
|
||||||
|
{ opacity: fadeAnim },
|
||||||
|
]}
|
||||||
|
>
|
||||||
<Text style={assistantMessageStylesheet.streamingText}>...</Text>
|
<Text style={assistantMessageStylesheet.streamingText}>...</Text>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
)}
|
)}
|
||||||
@@ -156,7 +194,7 @@ export function AssistantMessage({ message, timestamp, isStreaming = false }: As
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ActivityLogProps {
|
interface ActivityLogProps {
|
||||||
type: 'system' | 'info' | 'success' | 'error' | 'artifact';
|
type: "system" | "info" | "success" | "error" | "artifact";
|
||||||
message: string;
|
message: string;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
metadata?: Record<string, unknown>;
|
metadata?: Record<string, unknown>;
|
||||||
@@ -171,33 +209,33 @@ const activityLogStylesheet = StyleSheet.create((theme) => ({
|
|||||||
marginHorizontal: theme.spacing[2],
|
marginHorizontal: theme.spacing[2],
|
||||||
marginBottom: theme.spacing[1],
|
marginBottom: theme.spacing[1],
|
||||||
borderRadius: theme.borderRadius.md,
|
borderRadius: theme.borderRadius.md,
|
||||||
overflow: 'hidden',
|
overflow: "hidden",
|
||||||
},
|
},
|
||||||
pressableActive: {
|
pressableActive: {
|
||||||
opacity: 0.7,
|
opacity: 0.7,
|
||||||
},
|
},
|
||||||
systemBg: {
|
systemBg: {
|
||||||
backgroundColor: 'rgba(39, 39, 42, 0.5)',
|
backgroundColor: "rgba(39, 39, 42, 0.5)",
|
||||||
},
|
},
|
||||||
infoBg: {
|
infoBg: {
|
||||||
backgroundColor: 'rgba(30, 58, 138, 0.3)',
|
backgroundColor: "rgba(30, 58, 138, 0.3)",
|
||||||
},
|
},
|
||||||
successBg: {
|
successBg: {
|
||||||
backgroundColor: 'rgba(20, 83, 45, 0.3)',
|
backgroundColor: "rgba(20, 83, 45, 0.3)",
|
||||||
},
|
},
|
||||||
errorBg: {
|
errorBg: {
|
||||||
backgroundColor: 'rgba(127, 29, 29, 0.3)',
|
backgroundColor: "rgba(127, 29, 29, 0.3)",
|
||||||
},
|
},
|
||||||
artifactBg: {
|
artifactBg: {
|
||||||
backgroundColor: 'rgba(30, 58, 138, 0.4)',
|
backgroundColor: "rgba(30, 58, 138, 0.4)",
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
paddingHorizontal: theme.spacing[3],
|
paddingHorizontal: theme.spacing[3],
|
||||||
paddingVertical: 10,
|
paddingVertical: 10,
|
||||||
},
|
},
|
||||||
row: {
|
row: {
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'flex-start',
|
alignItems: "flex-start",
|
||||||
gap: theme.spacing[2],
|
gap: theme.spacing[2],
|
||||||
},
|
},
|
||||||
iconContainer: {
|
iconContainer: {
|
||||||
@@ -211,8 +249,8 @@ const activityLogStylesheet = StyleSheet.create((theme) => ({
|
|||||||
lineHeight: 20,
|
lineHeight: 20,
|
||||||
},
|
},
|
||||||
detailsRow: {
|
detailsRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
marginTop: theme.spacing[1],
|
marginTop: theme.spacing[1],
|
||||||
},
|
},
|
||||||
detailsText: {
|
detailsText: {
|
||||||
@@ -222,7 +260,7 @@ const activityLogStylesheet = StyleSheet.create((theme) => ({
|
|||||||
},
|
},
|
||||||
metadataContainer: {
|
metadataContainer: {
|
||||||
marginTop: theme.spacing[2],
|
marginTop: theme.spacing[2],
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
||||||
borderRadius: theme.borderRadius.base,
|
borderRadius: theme.borderRadius.base,
|
||||||
padding: theme.spacing[2],
|
padding: theme.spacing[2],
|
||||||
borderWidth: theme.borderWidth[1],
|
borderWidth: theme.borderWidth[1],
|
||||||
@@ -231,7 +269,7 @@ const activityLogStylesheet = StyleSheet.create((theme) => ({
|
|||||||
metadataText: {
|
metadataText: {
|
||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
fontSize: theme.fontSize.xs,
|
fontSize: theme.fontSize.xs,
|
||||||
fontFamily: 'monospace',
|
fontFamily: "monospace",
|
||||||
lineHeight: 16,
|
lineHeight: 16,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
@@ -244,34 +282,51 @@ export function ActivityLog({
|
|||||||
artifactId,
|
artifactId,
|
||||||
artifactType,
|
artifactType,
|
||||||
title,
|
title,
|
||||||
onArtifactClick
|
onArtifactClick,
|
||||||
}: ActivityLogProps) {
|
}: ActivityLogProps) {
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
|
||||||
const typeConfig = {
|
const typeConfig = {
|
||||||
system: { bg: activityLogStylesheet.systemBg, color: '#a1a1aa', Icon: Circle },
|
system: {
|
||||||
info: { bg: activityLogStylesheet.infoBg, color: '#60a5fa', Icon: Info },
|
bg: activityLogStylesheet.systemBg,
|
||||||
success: { bg: activityLogStylesheet.successBg, color: '#4ade80', Icon: CheckCircle },
|
color: "#a1a1aa",
|
||||||
error: { bg: activityLogStylesheet.errorBg, color: '#f87171', Icon: XCircle },
|
Icon: Circle,
|
||||||
artifact: { bg: activityLogStylesheet.artifactBg, color: '#93c5fd', Icon: FileText },
|
},
|
||||||
|
info: { bg: activityLogStylesheet.infoBg, color: "#60a5fa", Icon: Info },
|
||||||
|
success: {
|
||||||
|
bg: activityLogStylesheet.successBg,
|
||||||
|
color: "#4ade80",
|
||||||
|
Icon: CheckCircle,
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
bg: activityLogStylesheet.errorBg,
|
||||||
|
color: "#f87171",
|
||||||
|
Icon: XCircle,
|
||||||
|
},
|
||||||
|
artifact: {
|
||||||
|
bg: activityLogStylesheet.artifactBg,
|
||||||
|
color: "#93c5fd",
|
||||||
|
Icon: FileText,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = typeConfig[type];
|
const config = typeConfig[type];
|
||||||
const IconComponent = config.Icon;
|
const IconComponent = config.Icon;
|
||||||
|
|
||||||
const handlePress = () => {
|
const handlePress = () => {
|
||||||
if (type === 'artifact' && artifactId && onArtifactClick) {
|
if (type === "artifact" && artifactId && onArtifactClick) {
|
||||||
onArtifactClick(artifactId);
|
onArtifactClick(artifactId);
|
||||||
} else if (metadata) {
|
} else if (metadata) {
|
||||||
setIsExpanded(!isExpanded);
|
setIsExpanded(!isExpanded);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const displayMessage = type === 'artifact' && artifactType && title
|
const displayMessage =
|
||||||
? `${artifactType}: ${title}`
|
type === "artifact" && artifactType && title
|
||||||
: message;
|
? `${artifactType}: ${title}`
|
||||||
|
: message;
|
||||||
|
|
||||||
const isInteractive = type === 'artifact' || metadata;
|
const isInteractive = type === "artifact" || metadata;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
@@ -289,7 +344,12 @@ export function ActivityLog({
|
|||||||
<IconComponent size={16} color={config.color} />
|
<IconComponent size={16} color={config.color} />
|
||||||
</View>
|
</View>
|
||||||
<View style={activityLogStylesheet.textContainer}>
|
<View style={activityLogStylesheet.textContainer}>
|
||||||
<Text style={[activityLogStylesheet.messageText, { color: config.color }]}>
|
<Text
|
||||||
|
style={[
|
||||||
|
activityLogStylesheet.messageText,
|
||||||
|
{ color: config.color },
|
||||||
|
]}
|
||||||
|
>
|
||||||
{displayMessage}
|
{displayMessage}
|
||||||
</Text>
|
</Text>
|
||||||
{metadata && (
|
{metadata && (
|
||||||
@@ -318,10 +378,11 @@ export function ActivityLog({
|
|||||||
|
|
||||||
interface ToolCallProps {
|
interface ToolCallProps {
|
||||||
toolName: string;
|
toolName: string;
|
||||||
|
kind?: string; // Optional kind for ACP tool calls
|
||||||
args: any;
|
args: any;
|
||||||
result?: any;
|
result?: any;
|
||||||
error?: any;
|
error?: any;
|
||||||
status: 'executing' | 'completed' | 'failed';
|
status: "executing" | "completed" | "failed";
|
||||||
onOpenDetails?: () => void;
|
onOpenDetails?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,16 +393,16 @@ const toolCallStylesheet = StyleSheet.create((theme) => ({
|
|||||||
backgroundColor: theme.colors.card,
|
backgroundColor: theme.colors.card,
|
||||||
borderRadius: theme.borderRadius.lg,
|
borderRadius: theme.borderRadius.lg,
|
||||||
borderWidth: theme.borderWidth[1],
|
borderWidth: theme.borderWidth[1],
|
||||||
overflow: 'hidden',
|
overflow: "hidden",
|
||||||
},
|
},
|
||||||
pressableActive: {
|
pressableActive: {
|
||||||
opacity: 0.8,
|
opacity: 0.8,
|
||||||
},
|
},
|
||||||
executingBorder: {
|
executingBorder: {
|
||||||
borderColor: theme.colors.palette.amber[500],
|
borderColor: theme.colors.primary,
|
||||||
},
|
},
|
||||||
completedBorder: {
|
completedBorder: {
|
||||||
borderColor: theme.colors.palette.green[500],
|
borderColor: theme.colors.border,
|
||||||
},
|
},
|
||||||
failedBorder: {
|
failedBorder: {
|
||||||
borderColor: theme.colors.destructive,
|
borderColor: theme.colors.destructive,
|
||||||
@@ -350,40 +411,36 @@ const toolCallStylesheet = StyleSheet.create((theme) => ({
|
|||||||
padding: theme.spacing[3],
|
padding: theme.spacing[3],
|
||||||
},
|
},
|
||||||
headerRow: {
|
headerRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
|
gap: theme.spacing[2],
|
||||||
},
|
},
|
||||||
chevronContainer: {
|
chevronContainer: {
|
||||||
marginRight: theme.spacing[2],
|
marginRight: theme.spacing[2],
|
||||||
},
|
},
|
||||||
toolName: {
|
toolName: {
|
||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
fontFamily: 'monospace',
|
fontFamily: "monospace",
|
||||||
fontWeight: theme.fontWeight.semibold,
|
fontWeight: theme.fontWeight.semibold,
|
||||||
fontSize: theme.fontSize.sm,
|
fontSize: theme.fontSize.sm,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
statusBadge: {
|
statusBadge: {
|
||||||
flexDirection: 'row',
|
alignItems: "center",
|
||||||
alignItems: 'center',
|
justifyContent: "center",
|
||||||
gap: 6,
|
padding: theme.spacing[1],
|
||||||
paddingHorizontal: theme.spacing[2],
|
|
||||||
paddingVertical: theme.spacing[1],
|
|
||||||
borderRadius: theme.borderRadius.base,
|
borderRadius: theme.borderRadius.base,
|
||||||
|
width: 28,
|
||||||
|
height: 28,
|
||||||
},
|
},
|
||||||
executingBadgeBg: {
|
executingBadgeBg: {
|
||||||
backgroundColor: 'rgba(245, 158, 11, 0.2)',
|
backgroundColor: theme.colors.accent,
|
||||||
},
|
},
|
||||||
completedBadgeBg: {
|
completedBadgeBg: {
|
||||||
backgroundColor: 'rgba(34, 197, 94, 0.2)',
|
backgroundColor: "transparent",
|
||||||
},
|
},
|
||||||
failedBadgeBg: {
|
failedBadgeBg: {
|
||||||
backgroundColor: 'rgba(239, 68, 68, 0.2)',
|
backgroundColor: "rgba(239, 68, 68, 0.2)",
|
||||||
},
|
|
||||||
statusText: {
|
|
||||||
fontSize: theme.fontSize.xs,
|
|
||||||
fontWeight: theme.fontWeight.semibold,
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
},
|
},
|
||||||
expandedContent: {
|
expandedContent: {
|
||||||
marginTop: theme.spacing[3],
|
marginTop: theme.spacing[3],
|
||||||
@@ -396,7 +453,7 @@ const toolCallStylesheet = StyleSheet.create((theme) => ({
|
|||||||
color: theme.colors.mutedForeground,
|
color: theme.colors.mutedForeground,
|
||||||
fontSize: theme.fontSize.xs,
|
fontSize: theme.fontSize.xs,
|
||||||
fontWeight: theme.fontWeight.semibold,
|
fontWeight: theme.fontWeight.semibold,
|
||||||
textTransform: 'uppercase',
|
textTransform: "uppercase",
|
||||||
letterSpacing: 0.5,
|
letterSpacing: 0.5,
|
||||||
marginBottom: 6,
|
marginBottom: 6,
|
||||||
},
|
},
|
||||||
@@ -416,81 +473,113 @@ const toolCallStylesheet = StyleSheet.create((theme) => ({
|
|||||||
sectionText: {
|
sectionText: {
|
||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
fontSize: theme.fontSize.xs,
|
fontSize: theme.fontSize.xs,
|
||||||
fontFamily: 'monospace',
|
fontFamily: "monospace",
|
||||||
lineHeight: 16,
|
lineHeight: 16,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export function ToolCall({ toolName, args, result, error, status, onOpenDetails }: ToolCallProps) {
|
// Icon mapping for tool kinds
|
||||||
|
const toolKindIcons: Record<string, any> = {
|
||||||
|
edit: Pencil,
|
||||||
|
read: Eye,
|
||||||
|
execute: SquareTerminal,
|
||||||
|
// Add more mappings as needed
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ToolCall({
|
||||||
|
toolName,
|
||||||
|
kind,
|
||||||
|
args,
|
||||||
|
result,
|
||||||
|
error,
|
||||||
|
status,
|
||||||
|
onOpenDetails,
|
||||||
|
}: ToolCallProps) {
|
||||||
const spinAnim = useRef(new Animated.Value(0)).current;
|
const spinAnim = useRef(new Animated.Value(0)).current;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (status === 'executing') {
|
if (status === "executing") {
|
||||||
|
// Reset to 0 before starting
|
||||||
|
spinAnim.setValue(0);
|
||||||
Animated.loop(
|
Animated.loop(
|
||||||
Animated.timing(spinAnim, {
|
Animated.timing(spinAnim, {
|
||||||
toValue: 1,
|
toValue: 1,
|
||||||
duration: 1000,
|
duration: 1500,
|
||||||
useNativeDriver: true,
|
useNativeDriver: true,
|
||||||
|
easing: (t) => t, // Linear easing for smooth continuous rotation
|
||||||
})
|
})
|
||||||
).start();
|
).start();
|
||||||
} else {
|
} else {
|
||||||
spinAnim.stopAnimation();
|
spinAnim.stopAnimation();
|
||||||
spinAnim.setValue(0);
|
|
||||||
}
|
}
|
||||||
}, [status, spinAnim]);
|
}, [status, spinAnim]);
|
||||||
|
|
||||||
const spin = spinAnim.interpolate({
|
const spin = spinAnim.interpolate({
|
||||||
inputRange: [0, 1],
|
inputRange: [0, 1],
|
||||||
outputRange: ['0deg', '360deg'],
|
outputRange: ["0deg", "360deg"],
|
||||||
});
|
});
|
||||||
|
|
||||||
const statusConfig = {
|
const statusConfig = {
|
||||||
executing: {
|
executing: {
|
||||||
border: toolCallStylesheet.executingBorder,
|
border: toolCallStylesheet.executingBorder,
|
||||||
badgeBg: toolCallStylesheet.executingBadgeBg,
|
badgeBg: toolCallStylesheet.executingBadgeBg,
|
||||||
color: '#fcd34d',
|
color: "#fff",
|
||||||
label: 'executing',
|
|
||||||
},
|
},
|
||||||
completed: {
|
completed: {
|
||||||
border: toolCallStylesheet.completedBorder,
|
border: toolCallStylesheet.completedBorder,
|
||||||
badgeBg: toolCallStylesheet.completedBadgeBg,
|
badgeBg: toolCallStylesheet.completedBadgeBg,
|
||||||
color: '#86efac',
|
color: "#71717a",
|
||||||
label: 'completed',
|
|
||||||
},
|
},
|
||||||
failed: {
|
failed: {
|
||||||
border: toolCallStylesheet.failedBorder,
|
border: toolCallStylesheet.failedBorder,
|
||||||
badgeBg: toolCallStylesheet.failedBadgeBg,
|
badgeBg: toolCallStylesheet.failedBadgeBg,
|
||||||
color: '#fca5a5',
|
color: "#fca5a5",
|
||||||
label: 'failed',
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = statusConfig[status];
|
const config = statusConfig[status];
|
||||||
|
|
||||||
|
// Get the appropriate icon for the tool kind
|
||||||
|
const getToolIcon = () => {
|
||||||
|
if (kind) {
|
||||||
|
const IconComponent = toolKindIcons[kind.toLowerCase()] || Wrench;
|
||||||
|
return IconComponent;
|
||||||
|
}
|
||||||
|
return Wrench; // Default icon
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
onPress={onOpenDetails}
|
onPress={onOpenDetails}
|
||||||
style={[toolCallStylesheet.pressable, toolCallStylesheet.pressableActive, config.border]}
|
style={[
|
||||||
|
toolCallStylesheet.pressable,
|
||||||
|
toolCallStylesheet.pressableActive,
|
||||||
|
config.border,
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<View style={toolCallStylesheet.content}>
|
<View style={toolCallStylesheet.content}>
|
||||||
<View style={toolCallStylesheet.headerRow}>
|
<View style={toolCallStylesheet.headerRow}>
|
||||||
<Text style={toolCallStylesheet.toolName}>
|
<View style={[toolCallStylesheet.statusBadge, config.badgeBg]}>
|
||||||
|
{status === "executing" ? (
|
||||||
|
<Animated.View style={{ transform: [{ rotate: spin }] }}>
|
||||||
|
<Loader2 size={16} color={config.color} />
|
||||||
|
</Animated.View>
|
||||||
|
) : status === "completed" ? (
|
||||||
|
(() => {
|
||||||
|
const IconComponent = getToolIcon();
|
||||||
|
return <IconComponent size={16} color={config.color} />;
|
||||||
|
})()
|
||||||
|
) : (
|
||||||
|
<X size={16} color={config.color} />
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
<Text
|
||||||
|
style={toolCallStylesheet.toolName}
|
||||||
|
numberOfLines={1}
|
||||||
|
ellipsizeMode="tail"
|
||||||
|
>
|
||||||
{toolName}
|
{toolName}
|
||||||
</Text>
|
</Text>
|
||||||
<View style={[toolCallStylesheet.statusBadge, config.badgeBg]}>
|
|
||||||
{status === 'executing' ? (
|
|
||||||
<Animated.View style={{ transform: [{ rotate: spin }] }}>
|
|
||||||
<RefreshCw size={14} color={config.color} />
|
|
||||||
</Animated.View>
|
|
||||||
) : status === 'completed' ? (
|
|
||||||
<CheckCircle size={14} color={config.color} />
|
|
||||||
) : (
|
|
||||||
<XCircle size={14} color={config.color} />
|
|
||||||
)}
|
|
||||||
<Text style={[toolCallStylesheet.statusText, { color: config.color }]}>
|
|
||||||
{config.label}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import React, { useCallback, useMemo } from "react";
|
import React, { useCallback, useMemo } from "react";
|
||||||
import { View, Text, StyleSheet } from "react-native";
|
import { View, Text } from "react-native";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { ScrollView } from "react-native-gesture-handler";
|
import { ScrollView } from "react-native-gesture-handler";
|
||||||
|
import { StyleSheet } from "react-native-unistyles";
|
||||||
import {
|
import {
|
||||||
BottomSheetModal,
|
BottomSheetModal,
|
||||||
BottomSheetView,
|
BottomSheetView,
|
||||||
@@ -156,64 +157,64 @@ export function ToolCallBottomSheet({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create((theme) => ({
|
||||||
handleIndicator: {
|
handleIndicator: {
|
||||||
backgroundColor: "#4b5563",
|
backgroundColor: theme.colors.border,
|
||||||
},
|
},
|
||||||
background: {
|
background: {
|
||||||
backgroundColor: "#1f2937",
|
backgroundColor: theme.colors.popover,
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
paddingHorizontal: 20,
|
paddingHorizontal: theme.spacing[6],
|
||||||
paddingTop: 4,
|
paddingTop: theme.spacing[1],
|
||||||
paddingBottom: 16,
|
paddingBottom: theme.spacing[4],
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: theme.borderWidth[1],
|
||||||
borderBottomColor: "#374151",
|
borderBottomColor: theme.colors.border,
|
||||||
backgroundColor: "#1f2937",
|
backgroundColor: theme.colors.popover,
|
||||||
},
|
},
|
||||||
toolName: {
|
toolName: {
|
||||||
fontSize: 18,
|
fontSize: theme.fontSize.xl,
|
||||||
fontWeight: "600",
|
fontWeight: theme.fontWeight.semibold,
|
||||||
color: "#f9fafb",
|
color: theme.colors.popoverForeground,
|
||||||
},
|
},
|
||||||
sheetContent: {
|
sheetContent: {
|
||||||
paddingTop: 20,
|
paddingTop: theme.spacing[6],
|
||||||
paddingBottom: 20,
|
paddingBottom: theme.spacing[6],
|
||||||
},
|
},
|
||||||
section: {
|
section: {
|
||||||
marginBottom: 20,
|
marginBottom: theme.spacing[6],
|
||||||
paddingHorizontal: 20,
|
paddingHorizontal: theme.spacing[6],
|
||||||
},
|
},
|
||||||
sectionTitle: {
|
sectionTitle: {
|
||||||
fontSize: 14,
|
fontSize: theme.fontSize.sm,
|
||||||
fontWeight: "600",
|
fontWeight: theme.fontWeight.semibold,
|
||||||
color: "#9ca3af",
|
color: theme.colors.mutedForeground,
|
||||||
marginBottom: 8,
|
marginBottom: theme.spacing[2],
|
||||||
textTransform: "uppercase",
|
textTransform: "uppercase" as const,
|
||||||
letterSpacing: 0.5,
|
letterSpacing: 0.5,
|
||||||
},
|
},
|
||||||
jsonContainer: {
|
jsonContainer: {
|
||||||
backgroundColor: "#111827",
|
backgroundColor: theme.colors.background,
|
||||||
borderRadius: 8,
|
borderRadius: theme.borderRadius.lg,
|
||||||
borderWidth: 1,
|
borderWidth: theme.borderWidth[1],
|
||||||
borderColor: "#374151",
|
borderColor: theme.colors.border,
|
||||||
// Natural height based on content
|
// Natural height based on content
|
||||||
},
|
},
|
||||||
jsonContent: {
|
jsonContent: {
|
||||||
padding: 12,
|
padding: theme.spacing[3],
|
||||||
},
|
},
|
||||||
jsonText: {
|
jsonText: {
|
||||||
fontFamily: "monospace",
|
fontFamily: "monospace",
|
||||||
fontSize: 12,
|
fontSize: theme.fontSize.sm,
|
||||||
color: "#e5e7eb",
|
color: theme.colors.foreground,
|
||||||
lineHeight: 18,
|
lineHeight: 20,
|
||||||
// Text maintains whitespace and formatting
|
// Text maintains whitespace and formatting
|
||||||
},
|
},
|
||||||
errorContainer: {
|
errorContainer: {
|
||||||
borderColor: "#ef4444",
|
borderColor: theme.colors.destructive,
|
||||||
backgroundColor: "#1f1416",
|
backgroundColor: theme.colors.background,
|
||||||
},
|
},
|
||||||
errorText: {
|
errorText: {
|
||||||
color: "#fca5a5",
|
color: theme.colors.destructive,
|
||||||
},
|
},
|
||||||
});
|
}));
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const baseColors = {
|
export const baseColors = {
|
||||||
// Base colors
|
// Base colors
|
||||||
white: "#ffffff",
|
white: "#ffffff",
|
||||||
black: "#000000",
|
black: "#000000",
|
||||||
|
|||||||
Reference in New Issue
Block a user