@10play/expo-air 0.12.0 → 0.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/dist/server/gitOperations.d.ts +31 -0
- package/cli/dist/server/gitOperations.d.ts.map +1 -0
- package/cli/dist/server/gitOperations.js +232 -0
- package/cli/dist/server/gitOperations.js.map +1 -0
- package/cli/dist/server/promptServer.d.ts +1 -6
- package/cli/dist/server/promptServer.d.ts.map +1 -1
- package/cli/dist/server/promptServer.js +72 -264
- package/cli/dist/server/promptServer.js.map +1 -1
- package/cli/dist/tsconfig.tsbuildinfo +1 -1
- package/cli/dist/types/messages.d.ts +1 -0
- package/cli/dist/types/messages.d.ts.map +1 -1
- package/ios/FloatingBubbleManager.swift +73 -1
- package/ios/widget.jsbundle +12 -6
- package/package.json +1 -1
- package/widget/BubbleContent.tsx +85 -743
- package/widget/components/BranchSwitcher.tsx +205 -40
- package/widget/components/FormattedText.tsx +320 -0
- package/widget/components/Header.tsx +225 -0
- package/widget/components/MessageItems.tsx +308 -0
- package/widget/components/ResponseArea.tsx +32 -617
- package/widget/components/TabBar.tsx +161 -0
- package/widget/services/websocket.ts +1 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import React, { useRef, useEffect } from "react";
|
|
2
|
+
import { View, Text, StyleSheet, NativeModules, TouchableOpacity, Animated, Easing, type TextProps, type ViewProps } from "react-native";
|
|
3
|
+
import type { ConnectionStatus } from "../services/websocket";
|
|
4
|
+
import { SPACING, LAYOUT, COLORS, TYPOGRAPHY, SIZES } from "../constants/design";
|
|
5
|
+
|
|
6
|
+
// Typed animated components for React 19 compatibility
|
|
7
|
+
const AnimatedView = Animated.View as React.ComponentClass<Animated.AnimatedProps<ViewProps>>;
|
|
8
|
+
|
|
9
|
+
// WidgetBridge is a simple native module available in the widget runtime
|
|
10
|
+
// ExpoAir is the main app's module (fallback)
|
|
11
|
+
const { WidgetBridge, ExpoAir } = NativeModules;
|
|
12
|
+
|
|
13
|
+
function handleCollapse() {
|
|
14
|
+
try {
|
|
15
|
+
// Try WidgetBridge first (widget runtime), then ExpoAir (main app)
|
|
16
|
+
if (WidgetBridge?.collapse) {
|
|
17
|
+
WidgetBridge.collapse();
|
|
18
|
+
} else if (ExpoAir?.collapse) {
|
|
19
|
+
ExpoAir.collapse();
|
|
20
|
+
} else {
|
|
21
|
+
console.warn("[expo-air] No collapse method available");
|
|
22
|
+
}
|
|
23
|
+
} catch (e) {
|
|
24
|
+
console.warn("[expo-air] Failed to collapse:", e);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface HeaderProps {
|
|
29
|
+
status: ConnectionStatus;
|
|
30
|
+
branchName: string;
|
|
31
|
+
onBranchPress: () => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function Header({ status, branchName, onBranchPress }: HeaderProps) {
|
|
35
|
+
const statusColors = {
|
|
36
|
+
disconnected: COLORS.STATUS_ERROR,
|
|
37
|
+
connecting: COLORS.STATUS_INFO,
|
|
38
|
+
connected: COLORS.STATUS_SUCCESS,
|
|
39
|
+
processing: COLORS.STATUS_INFO,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<View style={styles.header}>
|
|
44
|
+
<TouchableOpacity onPress={handleCollapse} style={styles.closeButton}>
|
|
45
|
+
<Text style={styles.closeButtonText}>✕</Text>
|
|
46
|
+
</TouchableOpacity>
|
|
47
|
+
|
|
48
|
+
<TouchableOpacity style={styles.branchButton} onPress={onBranchPress} disabled={!branchName}>
|
|
49
|
+
{branchName ? (
|
|
50
|
+
<>
|
|
51
|
+
<Text style={styles.branchName} numberOfLines={1}>
|
|
52
|
+
{branchName}
|
|
53
|
+
</Text>
|
|
54
|
+
<Text style={styles.branchChevron}>▾</Text>
|
|
55
|
+
</>
|
|
56
|
+
) : (
|
|
57
|
+
<View style={styles.branchLoadingBar} />
|
|
58
|
+
)}
|
|
59
|
+
</TouchableOpacity>
|
|
60
|
+
|
|
61
|
+
<View style={[styles.statusDot, { backgroundColor: statusColors[status] }]} />
|
|
62
|
+
</View>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function PulsingIndicator({ status }: { status: ConnectionStatus }) {
|
|
67
|
+
const colors = {
|
|
68
|
+
disconnected: COLORS.STATUS_ERROR,
|
|
69
|
+
connecting: COLORS.STATUS_INFO,
|
|
70
|
+
connected: COLORS.STATUS_SUCCESS,
|
|
71
|
+
processing: COLORS.STATUS_INFO,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const isAnimating = status === "processing" || status === "connecting";
|
|
75
|
+
|
|
76
|
+
// Animated values for the pulsing ring
|
|
77
|
+
const scaleAnim = useRef(new Animated.Value(1)).current;
|
|
78
|
+
const opacityAnim = useRef(new Animated.Value(0.4)).current;
|
|
79
|
+
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (isAnimating) {
|
|
82
|
+
// Create a soft pulsing animation
|
|
83
|
+
const pulseAnimation = Animated.loop(
|
|
84
|
+
Animated.sequence([
|
|
85
|
+
Animated.parallel([
|
|
86
|
+
Animated.timing(scaleAnim, {
|
|
87
|
+
toValue: 1.3,
|
|
88
|
+
duration: 1200,
|
|
89
|
+
easing: Easing.inOut(Easing.ease),
|
|
90
|
+
useNativeDriver: true,
|
|
91
|
+
}),
|
|
92
|
+
Animated.timing(opacityAnim, {
|
|
93
|
+
toValue: 0,
|
|
94
|
+
duration: 1200,
|
|
95
|
+
easing: Easing.inOut(Easing.ease),
|
|
96
|
+
useNativeDriver: true,
|
|
97
|
+
}),
|
|
98
|
+
]),
|
|
99
|
+
Animated.parallel([
|
|
100
|
+
Animated.timing(scaleAnim, {
|
|
101
|
+
toValue: 1,
|
|
102
|
+
duration: 0,
|
|
103
|
+
useNativeDriver: true,
|
|
104
|
+
}),
|
|
105
|
+
Animated.timing(opacityAnim, {
|
|
106
|
+
toValue: 0.4,
|
|
107
|
+
duration: 0,
|
|
108
|
+
useNativeDriver: true,
|
|
109
|
+
}),
|
|
110
|
+
]),
|
|
111
|
+
])
|
|
112
|
+
);
|
|
113
|
+
pulseAnimation.start();
|
|
114
|
+
return () => pulseAnimation.stop();
|
|
115
|
+
} else {
|
|
116
|
+
// Reset when not animating
|
|
117
|
+
scaleAnim.setValue(1);
|
|
118
|
+
opacityAnim.setValue(0.4);
|
|
119
|
+
}
|
|
120
|
+
}, [isAnimating, scaleAnim, opacityAnim]);
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<View style={styles.indicatorContainer}>
|
|
124
|
+
<View
|
|
125
|
+
style={[
|
|
126
|
+
styles.indicator,
|
|
127
|
+
{ backgroundColor: colors[status] },
|
|
128
|
+
]}
|
|
129
|
+
/>
|
|
130
|
+
{isAnimating && (
|
|
131
|
+
<AnimatedView
|
|
132
|
+
style={[
|
|
133
|
+
styles.indicatorRing,
|
|
134
|
+
{
|
|
135
|
+
borderColor: colors[status],
|
|
136
|
+
transform: [{ scale: scaleAnim }],
|
|
137
|
+
opacity: opacityAnim,
|
|
138
|
+
},
|
|
139
|
+
]}
|
|
140
|
+
/>
|
|
141
|
+
)}
|
|
142
|
+
</View>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const styles = StyleSheet.create({
|
|
147
|
+
header: {
|
|
148
|
+
flexDirection: "row",
|
|
149
|
+
alignItems: "center",
|
|
150
|
+
paddingHorizontal: LAYOUT.CONTENT_PADDING_H,
|
|
151
|
+
paddingVertical: SPACING.MD + 2, // 14px for comfortable header height
|
|
152
|
+
borderBottomWidth: 1,
|
|
153
|
+
borderBottomColor: COLORS.BORDER,
|
|
154
|
+
},
|
|
155
|
+
closeButton: {
|
|
156
|
+
width: SIZES.CLOSE_BUTTON,
|
|
157
|
+
height: SIZES.CLOSE_BUTTON,
|
|
158
|
+
borderRadius: SIZES.CLOSE_BUTTON / 2,
|
|
159
|
+
// Make invisible - native close button handles the tap
|
|
160
|
+
backgroundColor: "transparent",
|
|
161
|
+
alignItems: "center",
|
|
162
|
+
justifyContent: "center",
|
|
163
|
+
marginRight: SPACING.MD,
|
|
164
|
+
},
|
|
165
|
+
closeButtonText: {
|
|
166
|
+
// Hide the text - native button shows the X
|
|
167
|
+
color: "transparent",
|
|
168
|
+
fontSize: TYPOGRAPHY.SIZE_MD,
|
|
169
|
+
fontWeight: TYPOGRAPHY.WEIGHT_SEMIBOLD,
|
|
170
|
+
},
|
|
171
|
+
branchButton: {
|
|
172
|
+
flex: 1,
|
|
173
|
+
flexDirection: "row",
|
|
174
|
+
alignItems: "center",
|
|
175
|
+
},
|
|
176
|
+
branchName: {
|
|
177
|
+
flexShrink: 1,
|
|
178
|
+
color: COLORS.TEXT_SECONDARY,
|
|
179
|
+
fontSize: TYPOGRAPHY.SIZE_MD,
|
|
180
|
+
fontWeight: TYPOGRAPHY.WEIGHT_MEDIUM,
|
|
181
|
+
},
|
|
182
|
+
branchChevron: {
|
|
183
|
+
color: COLORS.TEXT_MUTED,
|
|
184
|
+
fontSize: TYPOGRAPHY.SIZE_SM,
|
|
185
|
+
marginLeft: SPACING.XS,
|
|
186
|
+
},
|
|
187
|
+
branchLoadingBar: {
|
|
188
|
+
width: 80,
|
|
189
|
+
height: 12,
|
|
190
|
+
borderRadius: 6,
|
|
191
|
+
backgroundColor: "rgba(255,255,255,0.08)",
|
|
192
|
+
},
|
|
193
|
+
statusDot: {
|
|
194
|
+
width: SIZES.STATUS_DOT,
|
|
195
|
+
height: SIZES.STATUS_DOT,
|
|
196
|
+
borderRadius: SIZES.STATUS_DOT / 2,
|
|
197
|
+
marginLeft: SPACING.MD, // Match the closeButton marginRight for visual balance
|
|
198
|
+
},
|
|
199
|
+
collapsedPill: {
|
|
200
|
+
width: 100,
|
|
201
|
+
height: 32,
|
|
202
|
+
backgroundColor: "transparent",
|
|
203
|
+
alignItems: "center",
|
|
204
|
+
justifyContent: "center",
|
|
205
|
+
},
|
|
206
|
+
indicatorContainer: {
|
|
207
|
+
width: 20,
|
|
208
|
+
height: 20,
|
|
209
|
+
alignItems: "center",
|
|
210
|
+
justifyContent: "center",
|
|
211
|
+
},
|
|
212
|
+
indicator: {
|
|
213
|
+
width: SIZES.STATUS_DOT,
|
|
214
|
+
height: SIZES.STATUS_DOT,
|
|
215
|
+
borderRadius: SIZES.STATUS_DOT / 2,
|
|
216
|
+
},
|
|
217
|
+
indicatorRing: {
|
|
218
|
+
position: "absolute",
|
|
219
|
+
width: 16,
|
|
220
|
+
height: 16,
|
|
221
|
+
borderRadius: 8,
|
|
222
|
+
borderWidth: 1.5,
|
|
223
|
+
opacity: 0.4,
|
|
224
|
+
},
|
|
225
|
+
});
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { View, Text, StyleSheet, Platform, Image } from "react-native";
|
|
3
|
+
import type {
|
|
4
|
+
ServerMessage,
|
|
5
|
+
ToolMessage,
|
|
6
|
+
ResultMessage,
|
|
7
|
+
UserPromptMessage,
|
|
8
|
+
HistoryResultMessage,
|
|
9
|
+
SystemDisplayMessage,
|
|
10
|
+
AssistantPart,
|
|
11
|
+
AssistantPartsMessage,
|
|
12
|
+
} from "../services/websocket";
|
|
13
|
+
import { FormattedText } from "./FormattedText";
|
|
14
|
+
import { SPACING, LAYOUT, COLORS, TYPOGRAPHY } from "../constants/design";
|
|
15
|
+
|
|
16
|
+
export function MessageItem({ message }: { key?: React.Key; message: ServerMessage }) {
|
|
17
|
+
switch (message.type) {
|
|
18
|
+
case "stream":
|
|
19
|
+
return null; // Handled by currentParts
|
|
20
|
+
|
|
21
|
+
case "tool":
|
|
22
|
+
// Legacy: individual tool messages from history
|
|
23
|
+
return <ToolItem tool={message} />;
|
|
24
|
+
|
|
25
|
+
case "result":
|
|
26
|
+
return <ResultItem result={message} />;
|
|
27
|
+
|
|
28
|
+
case "error":
|
|
29
|
+
return (
|
|
30
|
+
<View style={styles.errorContainer}>
|
|
31
|
+
<Text style={styles.errorText}>{message.message}</Text>
|
|
32
|
+
</View>
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
case "status":
|
|
36
|
+
return null; // Handled by header
|
|
37
|
+
|
|
38
|
+
case "user_prompt":
|
|
39
|
+
return <UserPromptItem message={message} />;
|
|
40
|
+
|
|
41
|
+
case "history_result":
|
|
42
|
+
return <HistoryResultItem message={message} />;
|
|
43
|
+
|
|
44
|
+
case "assistant_parts":
|
|
45
|
+
return <AssistantPartsItem message={message} />;
|
|
46
|
+
|
|
47
|
+
case "system_message":
|
|
48
|
+
return <SystemMessageItem message={message} />;
|
|
49
|
+
|
|
50
|
+
default:
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function UserPromptItem({ message }: { message: UserPromptMessage }) {
|
|
56
|
+
return (
|
|
57
|
+
<View style={styles.userPromptContainer}>
|
|
58
|
+
{message.images && message.images.length > 0 && (
|
|
59
|
+
<View style={styles.userImages}>
|
|
60
|
+
{message.images.map((img, i) => (
|
|
61
|
+
<Image key={i} source={{ uri: img.uri }} style={styles.userImageThumb} />
|
|
62
|
+
))}
|
|
63
|
+
</View>
|
|
64
|
+
)}
|
|
65
|
+
{message.content ? (
|
|
66
|
+
<Text style={styles.userPromptText} selectable>{message.content}</Text>
|
|
67
|
+
) : null}
|
|
68
|
+
</View>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function HistoryResultItem({ message }: { message: HistoryResultMessage }) {
|
|
73
|
+
return (
|
|
74
|
+
<View style={styles.resultContainer}>
|
|
75
|
+
<FormattedText content={message.content} />
|
|
76
|
+
</View>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function SystemMessageItem({ message }: { message: SystemDisplayMessage }) {
|
|
81
|
+
// Use error styling for errors, muted styling for other system messages
|
|
82
|
+
if (message.messageType === "error") {
|
|
83
|
+
return (
|
|
84
|
+
<View style={styles.errorContainer}>
|
|
85
|
+
<Text style={styles.errorText}>{message.content}</Text>
|
|
86
|
+
</View>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
// Stopped and info messages use muted styling
|
|
90
|
+
return (
|
|
91
|
+
<View style={styles.systemContainer}>
|
|
92
|
+
<Text style={styles.systemText}>{message.content}</Text>
|
|
93
|
+
</View>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Renders interleaved text and tool parts in order
|
|
98
|
+
export function PartsRenderer({ parts, isStreaming }: { parts: AssistantPart[], isStreaming: boolean }) {
|
|
99
|
+
return (
|
|
100
|
+
<View style={styles.partsContainer}>
|
|
101
|
+
{parts.map((part, index) => {
|
|
102
|
+
if (part.type === "text") {
|
|
103
|
+
const isLastPart = index === parts.length - 1;
|
|
104
|
+
return (
|
|
105
|
+
<View key={part.id} style={styles.messageContainer}>
|
|
106
|
+
<FormattedText content={part.content} isStreaming={isStreaming && isLastPart} />
|
|
107
|
+
</View>
|
|
108
|
+
);
|
|
109
|
+
} else if (part.type === "tool") {
|
|
110
|
+
return <ToolPartItem key={part.id} part={part} />;
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
})}
|
|
114
|
+
</View>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Renders a completed assistant response with parts
|
|
119
|
+
function AssistantPartsItem({ message }: { message: AssistantPartsMessage }) {
|
|
120
|
+
return (
|
|
121
|
+
<View style={styles.resultContainer}>
|
|
122
|
+
<PartsRenderer parts={message.parts} isStreaming={false} />
|
|
123
|
+
{!message.isComplete && (
|
|
124
|
+
<Text style={styles.interruptedText}>(interrupted)</Text>
|
|
125
|
+
)}
|
|
126
|
+
</View>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Shared helper for tool display info
|
|
131
|
+
function getToolDisplayInfo(toolName: string, input: Record<string, unknown> | undefined): { label: string; value: string } {
|
|
132
|
+
const getFileName = (path: string): string => path.split('/').pop() || path;
|
|
133
|
+
|
|
134
|
+
switch (toolName) {
|
|
135
|
+
case "Read":
|
|
136
|
+
return { label: "read", value: getFileName(input?.file_path as string || "file") };
|
|
137
|
+
case "Edit":
|
|
138
|
+
return { label: "edit", value: getFileName(input?.file_path as string || "file") };
|
|
139
|
+
case "Write":
|
|
140
|
+
return { label: "write", value: getFileName(input?.file_path as string || "file") };
|
|
141
|
+
case "Bash": {
|
|
142
|
+
const cmd = input?.command as string || "";
|
|
143
|
+
return { label: "$", value: cmd.length > 45 ? cmd.slice(0, 45) + "…" : cmd };
|
|
144
|
+
}
|
|
145
|
+
case "Glob":
|
|
146
|
+
return { label: "glob", value: input?.pattern as string || "*" };
|
|
147
|
+
case "Grep":
|
|
148
|
+
return { label: "grep", value: input?.pattern as string || "search" };
|
|
149
|
+
case "Task":
|
|
150
|
+
return { label: "agent", value: input?.description as string || "task" };
|
|
151
|
+
default:
|
|
152
|
+
return { label: toolName.toLowerCase(), value: "" };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Renders a tool display line (shared between ToolPartItem and ToolItem)
|
|
157
|
+
function ToolDisplay({ toolName, input, isFailed }: { toolName: string; input?: unknown; isFailed: boolean }) {
|
|
158
|
+
const { label, value } = getToolDisplayInfo(toolName, input as Record<string, unknown> | undefined);
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<View style={styles.toolLine}>
|
|
162
|
+
<Text style={isFailed ? styles.toolLabelFailed : styles.toolLabel}>{label}</Text>
|
|
163
|
+
<Text style={isFailed ? styles.toolValueFailed : styles.toolValue} numberOfLines={1}>{value}</Text>
|
|
164
|
+
{isFailed && <Text style={styles.toolLabelFailed}> ✕</Text>}
|
|
165
|
+
</View>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Tool part renderer (for parts in AssistantPartsMessage)
|
|
170
|
+
function ToolPartItem({ part }: { key?: React.Key; part: AssistantPart & { type: "tool" } }) {
|
|
171
|
+
if (part.status === "started") return null;
|
|
172
|
+
return <ToolDisplay toolName={part.toolName} input={part.input} isFailed={part.status === "failed"} />;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Tool item renderer (for legacy ToolMessage from history)
|
|
176
|
+
function ToolItem({ tool }: { tool: ToolMessage }) {
|
|
177
|
+
if (tool.status === "started") return null;
|
|
178
|
+
return <ToolDisplay toolName={tool.toolName} input={tool.input} isFailed={tool.status === "failed"} />;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function ResultItem({ result }: { result: ResultMessage }) {
|
|
182
|
+
if (!result.success && result.error) {
|
|
183
|
+
return (
|
|
184
|
+
<View style={styles.errorContainer}>
|
|
185
|
+
<Text style={styles.errorText}>{result.error}</Text>
|
|
186
|
+
</View>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<View style={styles.resultContainer}>
|
|
192
|
+
{result.result && (
|
|
193
|
+
<Text style={styles.responseText} selectable>{result.result}</Text>
|
|
194
|
+
)}
|
|
195
|
+
{result.durationMs !== undefined && (
|
|
196
|
+
<Text style={styles.metaText}>
|
|
197
|
+
{`${result.durationMs}ms`}
|
|
198
|
+
</Text>
|
|
199
|
+
)}
|
|
200
|
+
</View>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const styles = StyleSheet.create({
|
|
205
|
+
messageContainer: {
|
|
206
|
+
flexDirection: "row",
|
|
207
|
+
flexWrap: "wrap",
|
|
208
|
+
},
|
|
209
|
+
responseText: {
|
|
210
|
+
color: "rgba(255,255,255,0.95)",
|
|
211
|
+
fontSize: TYPOGRAPHY.SIZE_LG,
|
|
212
|
+
lineHeight: 22,
|
|
213
|
+
},
|
|
214
|
+
resultContainer: {
|
|
215
|
+
marginTop: SPACING.SM,
|
|
216
|
+
},
|
|
217
|
+
partsContainer: {
|
|
218
|
+
// Container for interleaved parts
|
|
219
|
+
},
|
|
220
|
+
interruptedText: {
|
|
221
|
+
color: COLORS.TEXT_MUTED,
|
|
222
|
+
fontSize: TYPOGRAPHY.SIZE_XS + 1, // 12px
|
|
223
|
+
fontStyle: "italic",
|
|
224
|
+
marginTop: SPACING.XS,
|
|
225
|
+
},
|
|
226
|
+
metaText: {
|
|
227
|
+
color: COLORS.TEXT_MUTED,
|
|
228
|
+
fontSize: TYPOGRAPHY.SIZE_XS + 1, // 12px
|
|
229
|
+
marginTop: SPACING.SM + 2, // 10px
|
|
230
|
+
},
|
|
231
|
+
errorContainer: {
|
|
232
|
+
backgroundColor: "rgba(255,59,48,0.15)",
|
|
233
|
+
borderRadius: SPACING.MD,
|
|
234
|
+
padding: SPACING.MD,
|
|
235
|
+
marginVertical: SPACING.SM - 2, // 6px
|
|
236
|
+
},
|
|
237
|
+
errorText: {
|
|
238
|
+
color: "#FF6B6B",
|
|
239
|
+
fontSize: TYPOGRAPHY.SIZE_MD,
|
|
240
|
+
},
|
|
241
|
+
systemContainer: {
|
|
242
|
+
backgroundColor: "rgba(255,255,255,0.05)",
|
|
243
|
+
borderRadius: SPACING.MD,
|
|
244
|
+
padding: SPACING.MD,
|
|
245
|
+
marginVertical: SPACING.SM - 2, // 6px
|
|
246
|
+
},
|
|
247
|
+
systemText: {
|
|
248
|
+
color: COLORS.TEXT_TERTIARY,
|
|
249
|
+
fontSize: TYPOGRAPHY.SIZE_MD,
|
|
250
|
+
fontStyle: "italic",
|
|
251
|
+
},
|
|
252
|
+
toolLine: {
|
|
253
|
+
flexDirection: "row",
|
|
254
|
+
alignItems: "center",
|
|
255
|
+
marginVertical: SPACING.XS,
|
|
256
|
+
},
|
|
257
|
+
toolLabel: {
|
|
258
|
+
color: COLORS.TEXT_MUTED,
|
|
259
|
+
fontSize: TYPOGRAPHY.SIZE_XS + 1, // 12px
|
|
260
|
+
fontFamily: Platform.OS === "ios" ? "Menlo" : "monospace",
|
|
261
|
+
marginRight: SPACING.SM,
|
|
262
|
+
minWidth: 36,
|
|
263
|
+
},
|
|
264
|
+
toolLabelFailed: {
|
|
265
|
+
color: "rgba(255,100,100,0.6)",
|
|
266
|
+
fontSize: TYPOGRAPHY.SIZE_XS + 1, // 12px
|
|
267
|
+
fontFamily: Platform.OS === "ios" ? "Menlo" : "monospace",
|
|
268
|
+
marginRight: SPACING.SM,
|
|
269
|
+
minWidth: 36,
|
|
270
|
+
},
|
|
271
|
+
toolValue: {
|
|
272
|
+
color: "rgba(255,255,255,0.7)",
|
|
273
|
+
fontSize: TYPOGRAPHY.SIZE_SM,
|
|
274
|
+
fontFamily: Platform.OS === "ios" ? "Menlo" : "monospace",
|
|
275
|
+
flexShrink: 1,
|
|
276
|
+
},
|
|
277
|
+
toolValueFailed: {
|
|
278
|
+
color: "rgba(255,100,100,0.7)",
|
|
279
|
+
fontSize: TYPOGRAPHY.SIZE_SM,
|
|
280
|
+
fontFamily: Platform.OS === "ios" ? "Menlo" : "monospace",
|
|
281
|
+
flexShrink: 1,
|
|
282
|
+
},
|
|
283
|
+
userPromptContainer: {
|
|
284
|
+
backgroundColor: "rgba(0,122,255,0.15)",
|
|
285
|
+
borderRadius: LAYOUT.BORDER_RADIUS_SM + 2, // 16px
|
|
286
|
+
padding: SPACING.MD,
|
|
287
|
+
marginVertical: SPACING.SM,
|
|
288
|
+
alignSelf: "flex-end",
|
|
289
|
+
maxWidth: "85%",
|
|
290
|
+
},
|
|
291
|
+
userPromptText: {
|
|
292
|
+
color: COLORS.TEXT_PRIMARY,
|
|
293
|
+
fontSize: TYPOGRAPHY.SIZE_LG,
|
|
294
|
+
lineHeight: 20,
|
|
295
|
+
},
|
|
296
|
+
userImages: {
|
|
297
|
+
flexDirection: "row",
|
|
298
|
+
flexWrap: "wrap",
|
|
299
|
+
gap: SPACING.SM,
|
|
300
|
+
marginBottom: SPACING.SM,
|
|
301
|
+
},
|
|
302
|
+
userImageThumb: {
|
|
303
|
+
width: 80,
|
|
304
|
+
height: 80,
|
|
305
|
+
borderRadius: SPACING.SM,
|
|
306
|
+
backgroundColor: "rgba(255,255,255,0.1)",
|
|
307
|
+
},
|
|
308
|
+
});
|