|
|
|
@@ -76,7 +76,8 @@ export default function FlowScreen() {
|
|
|
|
const [newContent, setNewContent] = useState('');
|
|
|
|
const [newContent, setNewContent] = useState('');
|
|
|
|
const [isSending, setIsSending] = useState(false);
|
|
|
|
const [isSending, setIsSending] = useState(false);
|
|
|
|
const [isRecording, setIsRecording] = useState(false);
|
|
|
|
const [isRecording, setIsRecording] = useState(false);
|
|
|
|
const [selectedImage, setSelectedImage] = useState<string | null>(null);
|
|
|
|
/** Attached image for next send (uri + base64); user can add optional text then send together */
|
|
|
|
|
|
|
|
const [attachedImage, setAttachedImage] = useState<{ uri: string; base64: string } | null>(null);
|
|
|
|
|
|
|
|
|
|
|
|
// AI Role state - start with null to detect first load
|
|
|
|
// AI Role state - start with null to detect first load
|
|
|
|
const [selectedRole, setSelectedRole] = useState<AIRole | null>(aiRoles[0] || null);
|
|
|
|
const [selectedRole, setSelectedRole] = useState<AIRole | null>(aiRoles[0] || null);
|
|
|
|
@@ -264,10 +265,12 @@ export default function FlowScreen() {
|
|
|
|
// =============================================================================
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Handle sending a message to AI
|
|
|
|
* Handle sending a message to AI (text-only via LangGraph, or image + optional text via vision API)
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
const handleSendMessage = async () => {
|
|
|
|
const handleSendMessage = async () => {
|
|
|
|
if (!newContent.trim() || isSending || !selectedRole) return;
|
|
|
|
const hasText = !!newContent.trim();
|
|
|
|
|
|
|
|
const hasImage = !!attachedImage;
|
|
|
|
|
|
|
|
if ((!hasText && !hasImage) || isSending || !selectedRole) return;
|
|
|
|
|
|
|
|
|
|
|
|
// Check authentication
|
|
|
|
// Check authentication
|
|
|
|
if (!token) {
|
|
|
|
if (!token) {
|
|
|
|
@@ -279,11 +282,64 @@ export default function FlowScreen() {
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const userMessage = newContent.trim();
|
|
|
|
|
|
|
|
setIsSending(true);
|
|
|
|
setIsSending(true);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// --- Path: send with image (optional text) ---
|
|
|
|
|
|
|
|
if (hasImage && attachedImage) {
|
|
|
|
|
|
|
|
const imageUri = attachedImage.uri;
|
|
|
|
|
|
|
|
const imageBase64 = attachedImage.base64;
|
|
|
|
|
|
|
|
const userText = newContent.trim() || '请描述或分析这张图片';
|
|
|
|
|
|
|
|
setAttachedImage(null);
|
|
|
|
|
|
|
|
setNewContent('');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const userMsg: ChatMessage = {
|
|
|
|
|
|
|
|
id: Date.now().toString(),
|
|
|
|
|
|
|
|
role: 'user',
|
|
|
|
|
|
|
|
content: userText,
|
|
|
|
|
|
|
|
imageUri,
|
|
|
|
|
|
|
|
createdAt: new Date(),
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
setMessages(prev => [...prev, userMsg]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const aiResponse = await aiService.sendMessageWithImage(userText, imageBase64, token);
|
|
|
|
|
|
|
|
const aiMsg: ChatMessage = {
|
|
|
|
|
|
|
|
id: (Date.now() + 1).toString(),
|
|
|
|
|
|
|
|
role: 'assistant',
|
|
|
|
|
|
|
|
content: aiResponse,
|
|
|
|
|
|
|
|
createdAt: new Date(),
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
setMessages(prev => [...prev, aiMsg]);
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
console.error('AI image request failed:', error);
|
|
|
|
|
|
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
|
|
|
|
|
|
const isAuthError =
|
|
|
|
|
|
|
|
errorMessage.includes('401') ||
|
|
|
|
|
|
|
|
errorMessage.includes('Unauthorized') ||
|
|
|
|
|
|
|
|
errorMessage.includes('credentials') ||
|
|
|
|
|
|
|
|
errorMessage.includes('validate');
|
|
|
|
|
|
|
|
if (isAuthError) {
|
|
|
|
|
|
|
|
signOut();
|
|
|
|
|
|
|
|
Alert.alert('Session Expired', 'Your login session has expired. Please login again.', [{ text: 'OK' }]);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const errorMsg: ChatMessage = {
|
|
|
|
|
|
|
|
id: (Date.now() + 1).toString(),
|
|
|
|
|
|
|
|
role: 'assistant',
|
|
|
|
|
|
|
|
content: `⚠️ Error: ${errorMessage}`,
|
|
|
|
|
|
|
|
createdAt: new Date(),
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
setMessages(prev => [...prev, errorMsg]);
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
setIsSending(false);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// --- Path: text-only via LangGraph (unchanged) ---
|
|
|
|
|
|
|
|
const userMessage = newContent.trim();
|
|
|
|
setNewContent('');
|
|
|
|
setNewContent('');
|
|
|
|
|
|
|
|
|
|
|
|
// Add user message immediately
|
|
|
|
|
|
|
|
const userMsg: ChatMessage = {
|
|
|
|
const userMsg: ChatMessage = {
|
|
|
|
id: Date.now().toString(),
|
|
|
|
id: Date.now().toString(),
|
|
|
|
role: 'user',
|
|
|
|
role: 'user',
|
|
|
|
@@ -293,25 +349,15 @@ export default function FlowScreen() {
|
|
|
|
setMessages(prev => [...prev, userMsg]);
|
|
|
|
setMessages(prev => [...prev, userMsg]);
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
// 1. Convert current messages history to LangChain format
|
|
|
|
|
|
|
|
const history: (HumanMessage | LangChainAIMessage | SystemMessage)[] = messages.map(msg => {
|
|
|
|
const history: (HumanMessage | LangChainAIMessage | SystemMessage)[] = messages.map(msg => {
|
|
|
|
if (msg.role === 'user') return new HumanMessage(msg.content);
|
|
|
|
if (msg.role === 'user') return new HumanMessage(msg.content);
|
|
|
|
return new LangChainAIMessage(msg.content);
|
|
|
|
return new LangChainAIMessage(msg.content);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 2. Add system prompt
|
|
|
|
|
|
|
|
const systemPrompt = new SystemMessage(selectedRole?.systemPrompt || '');
|
|
|
|
const systemPrompt = new SystemMessage(selectedRole?.systemPrompt || '');
|
|
|
|
|
|
|
|
|
|
|
|
// 3. Add current new message
|
|
|
|
|
|
|
|
const currentMsg = new HumanMessage(userMessage);
|
|
|
|
const currentMsg = new HumanMessage(userMessage);
|
|
|
|
|
|
|
|
|
|
|
|
// 4. Combine all messages for LangGraph processing
|
|
|
|
|
|
|
|
const fullMessages = [systemPrompt, ...history, currentMsg];
|
|
|
|
const fullMessages = [systemPrompt, ...history, currentMsg];
|
|
|
|
|
|
|
|
|
|
|
|
// 5. Execute via LangGraph service (handles token limits and context)
|
|
|
|
|
|
|
|
const aiResponse = await langGraphService.execute(fullMessages, token);
|
|
|
|
const aiResponse = await langGraphService.execute(fullMessages, token);
|
|
|
|
|
|
|
|
|
|
|
|
// Add AI response
|
|
|
|
|
|
|
|
const aiMsg: ChatMessage = {
|
|
|
|
const aiMsg: ChatMessage = {
|
|
|
|
id: (Date.now() + 1).toString(),
|
|
|
|
id: (Date.now() + 1).toString(),
|
|
|
|
role: 'assistant',
|
|
|
|
role: 'assistant',
|
|
|
|
@@ -319,20 +365,15 @@ export default function FlowScreen() {
|
|
|
|
createdAt: new Date(),
|
|
|
|
createdAt: new Date(),
|
|
|
|
};
|
|
|
|
};
|
|
|
|
setMessages(prev => [...prev, aiMsg]);
|
|
|
|
setMessages(prev => [...prev, aiMsg]);
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
} catch (error) {
|
|
|
|
console.error('AI request failed:', error);
|
|
|
|
console.error('AI request failed:', error);
|
|
|
|
|
|
|
|
|
|
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
|
|
|
|
|
|
|
|
|
|
// Handle authentication errors (401, credentials, unauthorized)
|
|
|
|
|
|
|
|
const isAuthError =
|
|
|
|
const isAuthError =
|
|
|
|
errorMessage.includes('401') ||
|
|
|
|
errorMessage.includes('401') ||
|
|
|
|
errorMessage.includes('credentials') ||
|
|
|
|
errorMessage.includes('credentials') ||
|
|
|
|
errorMessage.includes('Unauthorized') ||
|
|
|
|
errorMessage.includes('Unauthorized') ||
|
|
|
|
errorMessage.includes('Not authenticated') ||
|
|
|
|
errorMessage.includes('Not authenticated') ||
|
|
|
|
errorMessage.includes('validate');
|
|
|
|
errorMessage.includes('validate');
|
|
|
|
|
|
|
|
|
|
|
|
if (isAuthError) {
|
|
|
|
if (isAuthError) {
|
|
|
|
signOut();
|
|
|
|
signOut();
|
|
|
|
Alert.alert(
|
|
|
|
Alert.alert(
|
|
|
|
@@ -342,8 +383,6 @@ export default function FlowScreen() {
|
|
|
|
);
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Show error as AI message
|
|
|
|
|
|
|
|
const errorMsg: ChatMessage = {
|
|
|
|
const errorMsg: ChatMessage = {
|
|
|
|
id: (Date.now() + 1).toString(),
|
|
|
|
id: (Date.now() + 1).toString(),
|
|
|
|
role: 'assistant',
|
|
|
|
role: 'assistant',
|
|
|
|
@@ -365,17 +404,15 @@ export default function FlowScreen() {
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Handle image attachment - pick image and analyze with AI
|
|
|
|
* Handle image attachment - pick image and attach to next message (user can add text then send)
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
const handleAddImage = async () => {
|
|
|
|
const handleAddImage = async () => {
|
|
|
|
// Request permission
|
|
|
|
|
|
|
|
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
|
|
|
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
|
|
|
if (status !== 'granted') {
|
|
|
|
if (status !== 'granted') {
|
|
|
|
Alert.alert('Permission Required', 'Please grant permission to access photos');
|
|
|
|
Alert.alert('Permission Required', 'Please grant permission to access photos');
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Pick image
|
|
|
|
|
|
|
|
const result = await ImagePicker.launchImageLibraryAsync({
|
|
|
|
const result = await ImagePicker.launchImageLibraryAsync({
|
|
|
|
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
|
|
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
|
|
allowsEditing: true,
|
|
|
|
allowsEditing: true,
|
|
|
|
@@ -384,78 +421,11 @@ export default function FlowScreen() {
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (!result.canceled && result.assets[0]) {
|
|
|
|
if (!result.canceled && result.assets[0]) {
|
|
|
|
const imageAsset = result.assets[0];
|
|
|
|
const asset = result.assets[0];
|
|
|
|
setSelectedImage(imageAsset.uri);
|
|
|
|
setAttachedImage({
|
|
|
|
|
|
|
|
uri: asset.uri,
|
|
|
|
// Check authentication
|
|
|
|
base64: asset.base64 || '',
|
|
|
|
if (!token) {
|
|
|
|
});
|
|
|
|
Alert.alert(
|
|
|
|
|
|
|
|
'Login Required',
|
|
|
|
|
|
|
|
'Please login to analyze images',
|
|
|
|
|
|
|
|
[{ text: 'OK', onPress: () => signOut() }]
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setIsSending(true);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Add user message with image
|
|
|
|
|
|
|
|
const userMsg: ChatMessage = {
|
|
|
|
|
|
|
|
id: Date.now().toString(),
|
|
|
|
|
|
|
|
role: 'user',
|
|
|
|
|
|
|
|
content: 'Analyze this image',
|
|
|
|
|
|
|
|
imageUri: imageAsset.uri,
|
|
|
|
|
|
|
|
createdAt: new Date(),
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
setMessages(prev => [...prev, userMsg]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
// Call AI with image (using base64)
|
|
|
|
|
|
|
|
const aiResponse = await aiService.sendMessageWithImage(
|
|
|
|
|
|
|
|
'Please describe and analyze this image in detail.',
|
|
|
|
|
|
|
|
imageAsset.base64 || '',
|
|
|
|
|
|
|
|
token
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const aiMsg: ChatMessage = {
|
|
|
|
|
|
|
|
id: (Date.now() + 1).toString(),
|
|
|
|
|
|
|
|
role: 'assistant',
|
|
|
|
|
|
|
|
content: aiResponse,
|
|
|
|
|
|
|
|
createdAt: new Date(),
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
setMessages(prev => [...prev, aiMsg]);
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
console.error('AI image analysis failed:', error);
|
|
|
|
|
|
|
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Handle authentication errors
|
|
|
|
|
|
|
|
const isAuthError =
|
|
|
|
|
|
|
|
errorMessage.includes('401') ||
|
|
|
|
|
|
|
|
errorMessage.includes('Unauthorized') ||
|
|
|
|
|
|
|
|
errorMessage.includes('credentials') ||
|
|
|
|
|
|
|
|
errorMessage.includes('validate');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (isAuthError) {
|
|
|
|
|
|
|
|
signOut();
|
|
|
|
|
|
|
|
Alert.alert(
|
|
|
|
|
|
|
|
'Session Expired',
|
|
|
|
|
|
|
|
'Your login session has expired. Please login again.',
|
|
|
|
|
|
|
|
[{ text: 'OK' }]
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const errorMsg: ChatMessage = {
|
|
|
|
|
|
|
|
id: (Date.now() + 1).toString(),
|
|
|
|
|
|
|
|
role: 'assistant',
|
|
|
|
|
|
|
|
content: `⚠️ Error analyzing image: ${errorMessage}`,
|
|
|
|
|
|
|
|
createdAt: new Date(),
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
setMessages(prev => [...prev, errorMsg]);
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
setIsSending(false);
|
|
|
|
|
|
|
|
setSelectedImage(null);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
@@ -836,21 +806,35 @@ export default function FlowScreen() {
|
|
|
|
|
|
|
|
|
|
|
|
{/* Bottom Input Bar */}
|
|
|
|
{/* Bottom Input Bar */}
|
|
|
|
<View style={styles.inputBarContainer}>
|
|
|
|
<View style={styles.inputBarContainer}>
|
|
|
|
|
|
|
|
{/* Attached image preview (optional text then send) */}
|
|
|
|
|
|
|
|
{attachedImage && (
|
|
|
|
|
|
|
|
<View style={styles.attachedImageRow}>
|
|
|
|
|
|
|
|
<Image source={{ uri: attachedImage.uri }} style={styles.attachedImageThumb} resizeMode="cover" />
|
|
|
|
|
|
|
|
<Text style={styles.attachedImageHint} numberOfLines={1}>可输入文字后发送</Text>
|
|
|
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
|
|
|
style={styles.attachedImageRemove}
|
|
|
|
|
|
|
|
onPress={() => setAttachedImage(null)}
|
|
|
|
|
|
|
|
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<Ionicons name="close-circle" size={24} color={colors.flow.textSecondary} />
|
|
|
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
|
|
</View>
|
|
|
|
|
|
|
|
)}
|
|
|
|
<View style={styles.inputBar}>
|
|
|
|
<View style={styles.inputBar}>
|
|
|
|
{/* Image attachment button */}
|
|
|
|
{/* Image attachment button */}
|
|
|
|
<TouchableOpacity
|
|
|
|
<TouchableOpacity
|
|
|
|
style={styles.inputBarButton}
|
|
|
|
style={[styles.inputBarButton, attachedImage && styles.inputBarButtonActive]}
|
|
|
|
onPress={handleAddImage}
|
|
|
|
onPress={handleAddImage}
|
|
|
|
activeOpacity={0.7}
|
|
|
|
activeOpacity={0.7}
|
|
|
|
>
|
|
|
|
>
|
|
|
|
<Feather name="image" size={22} color={colors.flow.textSecondary} />
|
|
|
|
<Feather name="image" size={22} color={attachedImage ? colors.nautical.teal : colors.flow.textSecondary} />
|
|
|
|
</TouchableOpacity>
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Text Input */}
|
|
|
|
{/* Text Input */}
|
|
|
|
<View style={styles.inputWrapper}>
|
|
|
|
<View style={styles.inputWrapper}>
|
|
|
|
<TextInput
|
|
|
|
<TextInput
|
|
|
|
style={styles.inputBarText}
|
|
|
|
style={styles.inputBarText}
|
|
|
|
placeholder="Message..."
|
|
|
|
placeholder={attachedImage ? '输入对图片的说明(可选)...' : 'Message...'}
|
|
|
|
placeholderTextColor={colors.flow.textSecondary}
|
|
|
|
placeholderTextColor={colors.flow.textSecondary}
|
|
|
|
value={newContent}
|
|
|
|
value={newContent}
|
|
|
|
onChangeText={setNewContent}
|
|
|
|
onChangeText={setNewContent}
|
|
|
|
@@ -859,8 +843,8 @@ export default function FlowScreen() {
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
</View>
|
|
|
|
</View>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Send or Voice button */}
|
|
|
|
{/* Send or Voice button: show send when has text or attached image */}
|
|
|
|
{newContent.trim() || isSending ? (
|
|
|
|
{newContent.trim() || attachedImage || isSending ? (
|
|
|
|
<TouchableOpacity
|
|
|
|
<TouchableOpacity
|
|
|
|
style={[styles.sendButton, isSending && styles.sendButtonDisabled]}
|
|
|
|
style={[styles.sendButton, isSending && styles.sendButtonDisabled]}
|
|
|
|
onPress={handleSendMessage}
|
|
|
|
onPress={handleSendMessage}
|
|
|
|
@@ -1533,6 +1517,33 @@ const styles = StyleSheet.create({
|
|
|
|
paddingTop: spacing.sm,
|
|
|
|
paddingTop: spacing.sm,
|
|
|
|
backgroundColor: 'transparent',
|
|
|
|
backgroundColor: 'transparent',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
attachedImageRow: {
|
|
|
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
|
|
backgroundColor: colors.flow.cardBackground,
|
|
|
|
|
|
|
|
borderRadius: borderRadius.lg,
|
|
|
|
|
|
|
|
padding: spacing.sm,
|
|
|
|
|
|
|
|
marginBottom: spacing.sm,
|
|
|
|
|
|
|
|
borderWidth: 1,
|
|
|
|
|
|
|
|
borderColor: colors.flow.cardBorder,
|
|
|
|
|
|
|
|
gap: spacing.sm,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
attachedImageThumb: {
|
|
|
|
|
|
|
|
width: 48,
|
|
|
|
|
|
|
|
height: 48,
|
|
|
|
|
|
|
|
borderRadius: borderRadius.md,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
attachedImageHint: {
|
|
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
|
|
fontSize: typography.fontSize.sm,
|
|
|
|
|
|
|
|
color: colors.flow.textSecondary,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
attachedImageRemove: {
|
|
|
|
|
|
|
|
padding: spacing.xs,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
inputBarButtonActive: {
|
|
|
|
|
|
|
|
backgroundColor: colors.nautical.paleAqua,
|
|
|
|
|
|
|
|
},
|
|
|
|
inputBar: {
|
|
|
|
inputBar: {
|
|
|
|
flexDirection: 'row',
|
|
|
|
flexDirection: 'row',
|
|
|
|
alignItems: 'flex-end',
|
|
|
|
alignItems: 'flex-end',
|
|
|
|
|