@mobileai/react-native 0.1.0 → 0.3.0

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.
Files changed (35) hide show
  1. package/README.md +78 -7
  2. package/lib/module/components/AIAgent.js +40 -4
  3. package/lib/module/components/AIAgent.js.map +1 -1
  4. package/lib/module/components/AgentChatBar.js +177 -29
  5. package/lib/module/components/AgentChatBar.js.map +1 -1
  6. package/lib/module/core/AgentRuntime.js +268 -126
  7. package/lib/module/core/AgentRuntime.js.map +1 -1
  8. package/lib/module/core/FiberTreeWalker.js +74 -20
  9. package/lib/module/core/FiberTreeWalker.js.map +1 -1
  10. package/lib/module/core/systemPrompt.js +164 -0
  11. package/lib/module/core/systemPrompt.js.map +1 -0
  12. package/lib/module/providers/GeminiProvider.js +189 -73
  13. package/lib/module/providers/GeminiProvider.js.map +1 -1
  14. package/lib/typescript/src/components/AIAgent.d.ts +9 -1
  15. package/lib/typescript/src/components/AIAgent.d.ts.map +1 -1
  16. package/lib/typescript/src/components/AgentChatBar.d.ts +4 -3
  17. package/lib/typescript/src/components/AgentChatBar.d.ts.map +1 -1
  18. package/lib/typescript/src/core/AgentRuntime.d.ts +16 -0
  19. package/lib/typescript/src/core/AgentRuntime.d.ts.map +1 -1
  20. package/lib/typescript/src/core/FiberTreeWalker.d.ts +5 -0
  21. package/lib/typescript/src/core/FiberTreeWalker.d.ts.map +1 -1
  22. package/lib/typescript/src/core/systemPrompt.d.ts +9 -0
  23. package/lib/typescript/src/core/systemPrompt.d.ts.map +1 -0
  24. package/lib/typescript/src/core/types.d.ts +51 -13
  25. package/lib/typescript/src/core/types.d.ts.map +1 -1
  26. package/lib/typescript/src/providers/GeminiProvider.d.ts +33 -13
  27. package/lib/typescript/src/providers/GeminiProvider.d.ts.map +1 -1
  28. package/package.json +16 -14
  29. package/src/components/AIAgent.tsx +41 -1
  30. package/src/components/AgentChatBar.tsx +150 -28
  31. package/src/core/AgentRuntime.ts +287 -131
  32. package/src/core/FiberTreeWalker.ts +74 -19
  33. package/src/core/systemPrompt.ts +162 -0
  34. package/src/core/types.ts +58 -10
  35. package/src/providers/GeminiProvider.ts +174 -101
@@ -1,23 +1,43 @@
1
1
  /**
2
- * GeminiProvider — Simplified Gemini API integration.
3
- * Sends dehydrated screen state + tools to Gemini and parses tool call responses.
2
+ * GeminiProvider — Gemini API integration with structured action pattern.
3
+ *
4
+ * Uses a single forced function call (`agent_step`) that bundles
5
+ * structured reasoning (evaluation, memory, plan) alongside the action.
6
+ * This replaces free-form text + separate tool calls for stability.
4
7
  */
5
- import type { AIProvider, ToolDefinition, AgentStep } from '../core/types';
8
+ import type { AIProvider, ToolDefinition, AgentStep, ProviderResult } from '../core/types';
6
9
  export declare class GeminiProvider implements AIProvider {
7
10
  private apiKey;
8
11
  private model;
9
12
  constructor(apiKey: string, model?: string);
10
- generateContent(systemPrompt: string, userMessage: string, tools: ToolDefinition[], history: AgentStep[]): Promise<{
11
- toolCalls: Array<{
12
- name: string;
13
- args: Record<string, any>;
14
- }>;
15
- text?: string;
16
- }>;
17
- private buildGeminiTools;
13
+ generateContent(systemPrompt: string, userMessage: string, tools: ToolDefinition[], history: AgentStep[]): Promise<ProviderResult>;
14
+ /**
15
+ * Builds a single `agent_step` function declaration that combines:
16
+ * - Structured reasoning fields (previous_goal_eval, memory, plan)
17
+ * - action_name (enum of all available tool names)
18
+ * - All tool parameter fields as flat top-level properties
19
+ *
20
+ * Flat schema avoids Gemini's "deeply nested schema" rejection in ANY mode.
21
+ */
22
+ private buildAgentStepDeclaration;
18
23
  private mapParamType;
24
+ /**
25
+ * Builds Gemini conversation contents.
26
+ *
27
+ * Each step is a STATELESS single-turn request (matching page-agent's approach):
28
+ * - System prompt has general instructions
29
+ * - User message contains full context: task, history, screen state
30
+ * - Model responds with agent_step function call
31
+ *
32
+ * History is embedded as text in assembleUserPrompt (via <agent_history>),
33
+ * NOT as functionCall/functionResponse pairs. This avoids Gemini's
34
+ * conversation format requirements and thought_signature complexity.
35
+ */
19
36
  private buildContents;
20
- private ensureAlternatingRoles;
21
- private parseResponse;
37
+ /**
38
+ * Parses the Gemini response expecting a single agent_step function call.
39
+ * Extracts structured reasoning + action, and determines which tool to execute.
40
+ */
41
+ private parseAgentStepResponse;
22
42
  }
23
43
  //# sourceMappingURL=GeminiProvider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"GeminiProvider.d.ts","sourceRoot":"","sources":["../../../../src/providers/GeminiProvider.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAyB3E,qBAAa,cAAe,YAAW,UAAU;IAC/C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;gBAGV,MAAM,EAAE,MAAM,EAAE,KAAK,GAAE,MAA2B;IAKxD,eAAe,CACnB,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,cAAc,EAAE,EACvB,OAAO,EAAE,SAAS,EAAE,GACnB,OAAO,CAAC;QAAE,SAAS,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;SAAE,CAAC,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAqD5F,OAAO,CAAC,gBAAgB;IAyBxB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,aAAa;IAsBrB,OAAO,CAAC,sBAAsB;IAsB9B,OAAO,CAAC,aAAa;CA8BtB"}
1
+ {"version":3,"file":"GeminiProvider.d.ts","sourceRoot":"","sources":["../../../../src/providers/GeminiProvider.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,SAAS,EAAE,cAAc,EAAkB,MAAM,eAAe,CAAC;AAsB3G,qBAAa,cAAe,YAAW,UAAU;IAC/C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;gBAEV,MAAM,EAAE,MAAM,EAAE,KAAK,GAAE,MAA2B;IAKxD,eAAe,CACnB,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,cAAc,EAAE,EACvB,OAAO,EAAE,SAAS,EAAE,GACnB,OAAO,CAAC,cAAc,CAAC;IA2D1B;;;;;;;OAOG;IACH,OAAO,CAAC,yBAAyB;IA0DjC,OAAO,CAAC,YAAY;IAYpB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,aAAa;IASrB;;;OAGG;IACH,OAAO,CAAC,sBAAsB;CA2E/B"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mobileai/react-native",
3
- "version": "0.1.0",
4
- "description": "Autonomous AI agent for React Native reads your UI via Fiber tree, understands layout context, and interacts with your app. Zero wrappers.",
3
+ "version": "0.3.0",
4
+ "description": "Build autonomous AI agents for React Native and Expo apps. Provides AI-native UI traversal, tool calling, and structured reasoning.",
5
5
  "main": "./lib/module/index.js",
6
6
  "source": "./src/index.ts",
7
7
  "types": "./lib/typescript/src/index.d.ts",
@@ -40,32 +40,34 @@
40
40
  "prepare": "bob build",
41
41
  "typecheck": "tsc",
42
42
  "lint": "eslint \"**/*.{js,ts,tsx}\"",
43
- "test": "jest"
43
+ "test": "jest",
44
+ "publish:dual": "bash scripts/publish-dual.sh",
45
+ "publish:dual:dry": "bash scripts/publish-dual.sh --dry-run"
44
46
  },
45
47
  "keywords": [
46
48
  "react-native",
49
+ "expo",
47
50
  "ai",
51
+ "llm",
48
52
  "agent",
49
- "agentic",
53
+ "autonomous-agent",
50
54
  "gemini",
51
- "llm",
52
- "autonomous",
53
- "fiber-tree",
54
- "page-agent",
55
- "mcp",
56
- "ios",
57
- "android"
55
+ "openai",
56
+ "artificial-intelligence",
57
+ "mobile-ai",
58
+ "ui-automation",
59
+ "react native ai agent"
58
60
  ],
59
61
  "repository": {
60
62
  "type": "git",
61
- "url": "git+https://github.com/mohamed2m2018/react-native-ai-agent.git"
63
+ "url": "git+https://github.com/mohamed2m2018/mobileai-react-native.git"
62
64
  },
63
65
  "author": "Mohamed Salah <mohamedsalah@example.com> (https://example.com)",
64
66
  "license": "MIT",
65
67
  "bugs": {
66
- "url": "https://github.com/mohamed2m2018/react-native-ai-agent/issues"
68
+ "url": "https://github.com/mohamed2m2018/mobileai-react-native/issues"
67
69
  },
68
- "homepage": "https://github.com/mohamed2m2018/react-native-ai-agent#readme",
70
+ "homepage": "https://github.com/mohamed2m2018/mobileai-react-native#readme",
69
71
  "publishConfig": {
70
72
  "registry": "https://registry.npmjs.org/"
71
73
  },
@@ -75,6 +75,14 @@ interface AIAgentProps {
75
75
  stepDelay?: number;
76
76
  /** WebSocket URL to companion MCP server bridge (e.g., ws://localhost:3101) */
77
77
  mcpServerUrl?: string;
78
+ /** Expo Router instance (from useRouter()) */
79
+ router?: {
80
+ push: (href: string) => void;
81
+ replace: (href: string) => void;
82
+ back: () => void;
83
+ };
84
+ /** Expo Router pathname (from usePathname()) */
85
+ pathname?: string;
78
86
  }
79
87
 
80
88
  // ─── Component ─────────────────────────────────────────────────
@@ -100,12 +108,17 @@ export function AIAgent({
100
108
  instructions,
101
109
  stepDelay,
102
110
  mcpServerUrl,
111
+ router,
112
+ pathname,
103
113
  }: AIAgentProps) {
104
114
  const rootViewRef = useRef<any>(null);
105
115
  const [isThinking, setIsThinking] = useState(false);
106
116
  const [statusText, setStatusText] = useState('');
107
117
  const [lastResult, setLastResult] = useState<ExecutionResult | null>(null);
108
118
 
119
+ // Ref-based resolver for ask_user — stays alive across renders
120
+ const askUserResolverRef = useRef<((answer: string) => void) | null>(null);
121
+
109
122
  // ─── Create Runtime ──────────────────────────────────────────
110
123
 
111
124
  const config: AgentConfig = useMemo(() => ({
@@ -124,12 +137,25 @@ export function AIAgent({
124
137
  instructions,
125
138
  stepDelay,
126
139
  mcpServerUrl,
140
+ router,
141
+ pathname,
142
+ onStatusUpdate: setStatusText,
143
+ // Page-agent pattern: block the agent loop until user responds
144
+ onAskUser: (question: string) => {
145
+ return new Promise<string>((resolve) => {
146
+ askUserResolverRef.current = resolve;
147
+ // Show question in chat bar, allow user input
148
+ setLastResult({ success: true, message: `❓ ${question}`, steps: [] });
149
+ setIsThinking(false);
150
+ setStatusText('');
151
+ });
152
+ },
127
153
  }), [
128
154
  apiKey, model, language, maxSteps,
129
155
  interactiveBlacklist, interactiveWhitelist,
130
156
  onBeforeStep, onAfterStep, onBeforeTask, onAfterTask,
131
157
  transformScreenContent, customTools, instructions, stepDelay,
132
- mcpServerUrl,
158
+ mcpServerUrl, router, pathname,
133
159
  ]);
134
160
 
135
161
  const provider = useMemo(() => new GeminiProvider(apiKey, model), [apiKey, model]);
@@ -164,6 +190,19 @@ export function AIAgent({
164
190
  if (!message.trim()) return;
165
191
 
166
192
  logger.info('AIAgent', `User message: "${message}"`);
193
+
194
+ // If there's a pending ask_user, resolve it instead of starting a new execution
195
+ if (askUserResolverRef.current) {
196
+ const resolver = askUserResolverRef.current;
197
+ askUserResolverRef.current = null;
198
+ setIsThinking(true);
199
+ setStatusText('Processing your answer...');
200
+ setLastResult(null);
201
+ resolver(message);
202
+ return;
203
+ }
204
+
205
+ // Normal execution — new task
167
206
  setIsThinking(true);
168
207
  setStatusText('Thinking...');
169
208
  setLastResult(null);
@@ -209,6 +248,7 @@ export function AIAgent({
209
248
  isThinking={isThinking}
210
249
  lastResult={lastResult}
211
250
  language={language}
251
+ onDismiss={() => setLastResult(null)}
212
252
  />
213
253
  )}
214
254
  </AgentContext.Provider>
@@ -1,17 +1,18 @@
1
1
  /**
2
- * AgentChatBar — Floating chat input at the bottom of the screen.
3
- * User sends messages here, and results are shown inline.
2
+ * AgentChatBar — Floating, draggable, compressible chat widget.
3
+ * Does not block underlying UI natively.
4
4
  */
5
5
 
6
- import { useState } from 'react';
6
+ import { useState, useRef } from 'react';
7
7
  import {
8
8
  View,
9
9
  TextInput,
10
10
  Pressable,
11
11
  Text,
12
12
  StyleSheet,
13
- KeyboardAvoidingView,
14
- Platform,
13
+ Animated,
14
+ PanResponder,
15
+ useWindowDimensions,
15
16
  } from 'react-native';
16
17
  import type { ExecutionResult } from '../core/types';
17
18
 
@@ -20,12 +21,43 @@ interface AgentChatBarProps {
20
21
  isThinking: boolean;
21
22
  lastResult: ExecutionResult | null;
22
23
  language: 'en' | 'ar';
24
+ onDismiss?: () => void;
23
25
  }
24
26
 
25
- export function AgentChatBar({ onSend, isThinking, lastResult, language }: AgentChatBarProps) {
27
+ export function AgentChatBar({ onSend, isThinking, lastResult, language, onDismiss }: AgentChatBarProps) {
26
28
  const [text, setText] = useState('');
29
+ const [isExpanded, setIsExpanded] = useState(false);
30
+ const { height } = useWindowDimensions();
27
31
  const isArabic = language === 'ar';
28
32
 
33
+ // Initial position: Bottom right for FAB, Bottom center for Expanded
34
+ // For simplicity, we just initialize to a safe generic spot on screen.
35
+ const pan = useRef(new Animated.ValueXY({ x: 10, y: height - 200 })).current;
36
+
37
+ // PanResponder for dragging the widget
38
+ const panResponder = useRef(
39
+ PanResponder.create({
40
+ onMoveShouldSetPanResponder: (_, gestureState) => {
41
+ // Only trigger drag if moving more than 5px (allows taps to register inside)
42
+ return Math.abs(gestureState.dx) > 5 || Math.abs(gestureState.dy) > 5;
43
+ },
44
+ onPanResponderGrant: () => {
45
+ pan.setOffset({
46
+ x: (pan.x as any)._value,
47
+ y: (pan.y as any)._value,
48
+ });
49
+ pan.setValue({ x: 0, y: 0 });
50
+ },
51
+ onPanResponderMove: Animated.event(
52
+ [null, { dx: pan.x, dy: pan.y }],
53
+ { useNativeDriver: false }
54
+ ),
55
+ onPanResponderRelease: () => {
56
+ pan.flattenOffset();
57
+ },
58
+ })
59
+ ).current;
60
+
29
61
  const handleSend = () => {
30
62
  if (text.trim() && !isThinking) {
31
63
  onSend(text.trim());
@@ -33,15 +65,41 @@ export function AgentChatBar({ onSend, isThinking, lastResult, language }: Agent
33
65
  }
34
66
  };
35
67
 
68
+ // ─── Compressed State (FAB) ───
69
+ if (!isExpanded) {
70
+ return (
71
+ <Animated.View style={[styles.fabContainer, pan.getLayout()]} {...panResponder.panHandlers}>
72
+ <Pressable
73
+ style={styles.fab}
74
+ onPress={() => setIsExpanded(true)}
75
+ accessibilityLabel="Open AI Agent Chat"
76
+ >
77
+ <Text style={styles.fabIcon}>{isThinking ? '⏳' : '🤖'}</Text>
78
+ </Pressable>
79
+ </Animated.View>
80
+ );
81
+ }
82
+
83
+ // ─── Expanded State (Widget) ───
36
84
  return (
37
- <KeyboardAvoidingView
38
- behavior={Platform.OS === 'ios' ? 'padding' : undefined}
39
- style={styles.container}
40
- >
85
+ <Animated.View style={[styles.expandedContainer, pan.getLayout()]}>
86
+ {/* Drag Handle Area */}
87
+ <View {...panResponder.panHandlers} style={styles.dragHandleArea} accessibilityLabel="Drag AI Agent">
88
+ <View style={styles.dragGrip} />
89
+ <Pressable onPress={() => setIsExpanded(false)} style={styles.minimizeBtn} accessibilityLabel="Minimize AI Agent">
90
+ <Text style={styles.minimizeText}>—</Text>
91
+ </Pressable>
92
+ </View>
93
+
41
94
  {/* Result message */}
42
95
  {lastResult && (
43
96
  <View style={[styles.resultBubble, lastResult.success ? styles.resultSuccess : styles.resultError]}>
44
97
  <Text style={styles.resultText}>{lastResult.message}</Text>
98
+ {onDismiss && (
99
+ <Pressable style={styles.dismissButton} onPress={onDismiss} hitSlop={12}>
100
+ <Text style={styles.dismissText}>✕</Text>
101
+ </Pressable>
102
+ )}
45
103
  </View>
46
104
  )}
47
105
 
@@ -49,7 +107,7 @@ export function AgentChatBar({ onSend, isThinking, lastResult, language }: Agent
49
107
  <View style={styles.inputRow}>
50
108
  <TextInput
51
109
  style={[styles.input, isArabic && styles.inputRTL]}
52
- placeholder={isArabic ? 'اكتب طلبك...' : 'Ask the AI agent...'}
110
+ placeholder={isArabic ? 'اكتب طلبك...' : 'Ask AI...'}
53
111
  placeholderTextColor="#999"
54
112
  value={text}
55
113
  onChangeText={setText}
@@ -62,33 +120,87 @@ export function AgentChatBar({ onSend, isThinking, lastResult, language }: Agent
62
120
  style={[styles.sendButton, isThinking && styles.sendButtonDisabled]}
63
121
  onPress={handleSend}
64
122
  disabled={isThinking || !text.trim()}
123
+ accessibilityLabel="Send request to AI Agent"
65
124
  >
66
125
  <Text style={styles.sendButtonText}>
67
126
  {isThinking ? '⏳' : '🚀'}
68
127
  </Text>
69
128
  </Pressable>
70
129
  </View>
71
- </KeyboardAvoidingView>
130
+ </Animated.View>
72
131
  );
73
132
  }
74
133
 
75
134
  const styles = StyleSheet.create({
76
- container: {
135
+ // FAB Styles
136
+ fabContainer: {
77
137
  position: 'absolute',
78
- bottom: 0,
79
- left: 0,
80
- right: 0,
81
- paddingHorizontal: 12,
82
- paddingBottom: Platform.OS === 'ios' ? 34 : 12,
83
- paddingTop: 8,
138
+ zIndex: 9999,
139
+ },
140
+ fab: {
141
+ width: 60,
142
+ height: 60,
143
+ borderRadius: 30,
144
+ backgroundColor: '#1a1a2e',
145
+ justifyContent: 'center',
146
+ alignItems: 'center',
147
+ elevation: 5,
148
+ shadowColor: '#000',
149
+ shadowOffset: { width: 0, height: 4 },
150
+ shadowOpacity: 0.3,
151
+ shadowRadius: 6,
152
+ },
153
+ fabIcon: {
154
+ fontSize: 28,
155
+ },
156
+
157
+ // Expanded Styles
158
+ expandedContainer: {
159
+ position: 'absolute',
160
+ zIndex: 9999,
161
+ width: 340,
84
162
  backgroundColor: 'rgba(26, 26, 46, 0.95)',
85
- borderTopLeftRadius: 20,
86
- borderTopRightRadius: 20,
163
+ borderRadius: 24,
164
+ padding: 16,
165
+ paddingTop: 8,
166
+ elevation: 8,
167
+ shadowColor: '#000',
168
+ shadowOffset: { width: 0, height: 8 },
169
+ shadowOpacity: 0.4,
170
+ shadowRadius: 10,
171
+ },
172
+ dragHandleArea: {
173
+ width: '100%',
174
+ height: 30,
175
+ justifyContent: 'center',
176
+ alignItems: 'center',
177
+ marginBottom: 8,
178
+ },
179
+ dragGrip: {
180
+ width: 40,
181
+ height: 5,
182
+ backgroundColor: 'rgba(255, 255, 255, 0.3)',
183
+ borderRadius: 4,
184
+ },
185
+ minimizeBtn: {
186
+ position: 'absolute',
187
+ right: 0,
188
+ top: 0,
189
+ padding: 8,
87
190
  },
191
+ minimizeText: {
192
+ color: '#fff',
193
+ fontSize: 18,
194
+ fontWeight: 'bold',
195
+ },
196
+
197
+ // Results & Input
88
198
  resultBubble: {
89
199
  borderRadius: 12,
90
200
  padding: 12,
91
- marginBottom: 8,
201
+ marginBottom: 12,
202
+ flexDirection: 'row',
203
+ alignItems: 'flex-start',
92
204
  },
93
205
  resultSuccess: {
94
206
  backgroundColor: 'rgba(40, 167, 69, 0.2)',
@@ -100,6 +212,16 @@ const styles = StyleSheet.create({
100
212
  color: '#fff',
101
213
  fontSize: 14,
102
214
  lineHeight: 20,
215
+ flex: 1,
216
+ },
217
+ dismissButton: {
218
+ marginLeft: 8,
219
+ padding: 2,
220
+ },
221
+ dismissText: {
222
+ color: 'rgba(255, 255, 255, 0.6)',
223
+ fontSize: 14,
224
+ fontWeight: 'bold',
103
225
  },
104
226
  inputRow: {
105
227
  flexDirection: 'row',
@@ -109,9 +231,9 @@ const styles = StyleSheet.create({
109
231
  input: {
110
232
  flex: 1,
111
233
  backgroundColor: 'rgba(255, 255, 255, 0.1)',
112
- borderRadius: 24,
234
+ borderRadius: 20,
113
235
  paddingHorizontal: 16,
114
- paddingVertical: 12,
236
+ paddingVertical: 10,
115
237
  color: '#fff',
116
238
  fontSize: 16,
117
239
  },
@@ -120,9 +242,9 @@ const styles = StyleSheet.create({
120
242
  writingDirection: 'rtl',
121
243
  },
122
244
  sendButton: {
123
- width: 44,
124
- height: 44,
125
- borderRadius: 22,
245
+ width: 40,
246
+ height: 40,
247
+ borderRadius: 20,
126
248
  backgroundColor: 'rgba(255, 255, 255, 0.15)',
127
249
  justifyContent: 'center',
128
250
  alignItems: 'center',
@@ -131,6 +253,6 @@ const styles = StyleSheet.create({
131
253
  opacity: 0.5,
132
254
  },
133
255
  sendButtonText: {
134
- fontSize: 20,
256
+ fontSize: 18,
135
257
  },
136
258
  });