multi_prompt_local_storage
This commit is contained in:
@@ -31,6 +31,8 @@ import * as ImagePicker from 'expo-image-picker';
|
||||
import { colors, typography, spacing, borderRadius, shadows } from '../theme/colors';
|
||||
import { aiService } from '../services/ai.service';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { AI_CONFIG } from '../config';
|
||||
import { storageService } from '../services/storage.service';
|
||||
|
||||
// =============================================================================
|
||||
// Type Definitions
|
||||
@@ -57,16 +59,21 @@ interface ChatSession {
|
||||
// =============================================================================
|
||||
|
||||
export default function FlowScreen() {
|
||||
const { token, signOut } = useAuth();
|
||||
const { token, user, signOut } = useAuth();
|
||||
const scrollViewRef = useRef<ScrollView>(null);
|
||||
|
||||
|
||||
// Current conversation state
|
||||
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
||||
const [newContent, setNewContent] = useState('');
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
const [isRecording, setIsRecording] = useState(false);
|
||||
const [selectedImage, setSelectedImage] = useState<string | null>(null);
|
||||
|
||||
|
||||
// AI Role state
|
||||
const [selectedRole, setSelectedRole] = useState(AI_CONFIG.ROLES[0]);
|
||||
const [showRoleModal, setShowRoleModal] = useState(false);
|
||||
const [expandedRoleId, setExpandedRoleId] = useState<string | null>(null);
|
||||
|
||||
// History modal state
|
||||
const [showHistoryModal, setShowHistoryModal] = useState(false);
|
||||
const modalSlideAnim = useRef(new Animated.Value(0)).current;
|
||||
@@ -95,23 +102,87 @@ export default function FlowScreen() {
|
||||
updatedAt: new Date('2024-01-16T20:30:00'),
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
// Header date display
|
||||
const today = new Date();
|
||||
const dateStr = today.toLocaleDateString('en-US', {
|
||||
weekday: 'long',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
const dateStr = today.toLocaleDateString('en-US', {
|
||||
weekday: 'long',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
|
||||
// Auto-scroll to bottom when new messages arrive
|
||||
// Load history on mount
|
||||
useEffect(() => {
|
||||
const loadHistory = async () => {
|
||||
if (!user) return;
|
||||
try {
|
||||
console.log('[FlowScreen] Loading chat history...');
|
||||
const savedHistory = await storageService.getChatHistory(user.id);
|
||||
if (savedHistory && savedHistory.length > 0) {
|
||||
const formattedHistory = savedHistory.map((session: any) => ({
|
||||
...session,
|
||||
createdAt: new Date(session.createdAt),
|
||||
updatedAt: new Date(session.updatedAt),
|
||||
messages: session.messages.map((msg: any) => ({
|
||||
...msg,
|
||||
createdAt: new Date(msg.createdAt)
|
||||
}))
|
||||
}));
|
||||
setChatHistory(formattedHistory);
|
||||
console.log('[FlowScreen] Chat history loaded:', formattedHistory.length, 'sessions');
|
||||
} else {
|
||||
console.log('[FlowScreen] No chat history found');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load history:', error);
|
||||
}
|
||||
};
|
||||
loadHistory();
|
||||
}, [user]);
|
||||
|
||||
// Load messages whenever role changes
|
||||
useEffect(() => {
|
||||
const loadRoleMessages = async () => {
|
||||
if (!user) return;
|
||||
try {
|
||||
const savedMessages = await storageService.getCurrentChat(selectedRole.id, user.id);
|
||||
if (savedMessages) {
|
||||
const formattedMessages = savedMessages.map((msg: any) => ({
|
||||
...msg,
|
||||
createdAt: new Date(msg.createdAt)
|
||||
}));
|
||||
setMessages(formattedMessages);
|
||||
} else {
|
||||
setMessages([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to load messages for role ${selectedRole.id}:`, error);
|
||||
setMessages([]);
|
||||
}
|
||||
};
|
||||
|
||||
loadRoleMessages();
|
||||
}, [selectedRole.id, user]);
|
||||
|
||||
// Save current messages for the active role when they change
|
||||
useEffect(() => {
|
||||
if (user && messages.length >= 0) { // Save even if empty to allow clearing
|
||||
storageService.saveCurrentChat(selectedRole.id, messages, user.id);
|
||||
}
|
||||
|
||||
if (messages.length > 0) {
|
||||
setTimeout(() => {
|
||||
scrollViewRef.current?.scrollToEnd({ animated: true });
|
||||
}, 100);
|
||||
}
|
||||
}, [messages]);
|
||||
}, [messages, selectedRole.id, user]);
|
||||
|
||||
// Save history when it changes
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
storageService.saveChatHistory(chatHistory, user.id);
|
||||
}
|
||||
}, [chatHistory, user]);
|
||||
|
||||
// Modal animation control
|
||||
const openHistoryModal = () => {
|
||||
@@ -143,7 +214,7 @@ export default function FlowScreen() {
|
||||
*/
|
||||
const handleSendMessage = async () => {
|
||||
if (!newContent.trim() || isSending) return;
|
||||
|
||||
|
||||
// Check authentication
|
||||
if (!token) {
|
||||
Alert.alert(
|
||||
@@ -153,11 +224,11 @@ export default function FlowScreen() {
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const userMessage = newContent.trim();
|
||||
setIsSending(true);
|
||||
setNewContent('');
|
||||
|
||||
|
||||
// Add user message immediately
|
||||
const userMsg: ChatMessage = {
|
||||
id: Date.now().toString(),
|
||||
@@ -166,11 +237,11 @@ export default function FlowScreen() {
|
||||
createdAt: new Date(),
|
||||
};
|
||||
setMessages(prev => [...prev, userMsg]);
|
||||
|
||||
|
||||
try {
|
||||
// Call AI proxy
|
||||
const aiResponse = await aiService.sendMessage(userMessage, token);
|
||||
|
||||
// Call AI proxy with selected role's system prompt
|
||||
const aiResponse = await aiService.sendMessage(userMessage, token, selectedRole.systemPrompt);
|
||||
|
||||
// Add AI response
|
||||
const aiMsg: ChatMessage = {
|
||||
id: (Date.now() + 1).toString(),
|
||||
@@ -179,20 +250,20 @@ export default function FlowScreen() {
|
||||
createdAt: new Date(),
|
||||
};
|
||||
setMessages(prev => [...prev, aiMsg]);
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('AI request failed:', error);
|
||||
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
|
||||
|
||||
// Handle authentication errors (401, credentials, unauthorized)
|
||||
const isAuthError =
|
||||
errorMessage.includes('401') ||
|
||||
const isAuthError =
|
||||
errorMessage.includes('401') ||
|
||||
errorMessage.includes('credentials') ||
|
||||
errorMessage.includes('Unauthorized') ||
|
||||
errorMessage.includes('Not authenticated') ||
|
||||
errorMessage.includes('validate');
|
||||
|
||||
|
||||
if (isAuthError) {
|
||||
signOut();
|
||||
Alert.alert(
|
||||
@@ -202,7 +273,7 @@ export default function FlowScreen() {
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Show error as AI message
|
||||
const errorMsg: ChatMessage = {
|
||||
id: (Date.now() + 1).toString(),
|
||||
@@ -246,7 +317,7 @@ export default function FlowScreen() {
|
||||
if (!result.canceled && result.assets[0]) {
|
||||
const imageAsset = result.assets[0];
|
||||
setSelectedImage(imageAsset.uri);
|
||||
|
||||
|
||||
// Check authentication
|
||||
if (!token) {
|
||||
Alert.alert(
|
||||
@@ -289,9 +360,9 @@ export default function FlowScreen() {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
|
||||
// Handle authentication errors
|
||||
const isAuthError =
|
||||
errorMessage.includes('401') ||
|
||||
errorMessage.includes('Unauthorized') ||
|
||||
const isAuthError =
|
||||
errorMessage.includes('401') ||
|
||||
errorMessage.includes('Unauthorized') ||
|
||||
errorMessage.includes('credentials') ||
|
||||
errorMessage.includes('validate');
|
||||
|
||||
@@ -334,9 +405,12 @@ export default function FlowScreen() {
|
||||
};
|
||||
setChatHistory(prev => [newSession, ...prev]);
|
||||
}
|
||||
|
||||
// Clear current messages
|
||||
|
||||
// Clear current messages and storage for this role
|
||||
setMessages([]);
|
||||
if (user) {
|
||||
storageService.saveCurrentChat(selectedRole.id, [], user.id);
|
||||
}
|
||||
closeHistoryModal();
|
||||
};
|
||||
|
||||
@@ -355,7 +429,7 @@ export default function FlowScreen() {
|
||||
};
|
||||
setChatHistory(prev => [currentSession, ...prev.filter(s => s.id !== session.id)]);
|
||||
}
|
||||
|
||||
|
||||
// Load selected conversation
|
||||
setMessages(session.messages);
|
||||
closeHistoryModal();
|
||||
@@ -370,8 +444,8 @@ export default function FlowScreen() {
|
||||
'Are you sure you want to delete this conversation?',
|
||||
[
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
{
|
||||
text: 'Delete',
|
||||
{
|
||||
text: 'Delete',
|
||||
style: 'destructive',
|
||||
onPress: () => setChatHistory(prev => prev.filter(s => s.id !== sessionId))
|
||||
},
|
||||
@@ -400,10 +474,10 @@ export default function FlowScreen() {
|
||||
*/
|
||||
const renderMessage = (message: ChatMessage, index: number) => {
|
||||
const isUser = message.role === 'user';
|
||||
|
||||
|
||||
return (
|
||||
<View
|
||||
key={message.id}
|
||||
<View
|
||||
key={message.id}
|
||||
style={[
|
||||
styles.messageBubble,
|
||||
isUser ? styles.userBubble : styles.aiBubble
|
||||
@@ -420,8 +494,8 @@ export default function FlowScreen() {
|
||||
]}>
|
||||
{/* Show image if present */}
|
||||
{message.imageUri && (
|
||||
<Image
|
||||
source={{ uri: message.imageUri }}
|
||||
<Image
|
||||
source={{ uri: message.imageUri }}
|
||||
style={styles.messageImage}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
@@ -451,9 +525,9 @@ export default function FlowScreen() {
|
||||
<View style={styles.emptyIcon}>
|
||||
<Feather name="feather" size={48} color={colors.nautical.seafoam} />
|
||||
</View>
|
||||
<Text style={styles.emptyTitle}>Start a conversation</Text>
|
||||
<Text style={styles.emptyTitle}>Chatting with {selectedRole.name}</Text>
|
||||
<Text style={styles.emptySubtitle}>
|
||||
Ask me anything or share your thoughts
|
||||
{selectedRole.description}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
@@ -462,7 +536,7 @@ export default function FlowScreen() {
|
||||
* Render history item in modal
|
||||
*/
|
||||
const renderHistoryItem = ({ item }: { item: ChatSession }) => (
|
||||
<TouchableOpacity
|
||||
<TouchableOpacity
|
||||
style={styles.historyItem}
|
||||
onPress={() => handleLoadHistory(item)}
|
||||
onLongPress={() => handleDeleteHistory(item.id)}
|
||||
@@ -504,9 +578,26 @@ export default function FlowScreen() {
|
||||
<Text style={styles.headerDate}>{dateStr}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
||||
{/* Role Header Dropdown */}
|
||||
<TouchableOpacity
|
||||
style={styles.headerRoleButton}
|
||||
onPress={() => setShowRoleModal(true)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Ionicons
|
||||
name={selectedRole.icon as any}
|
||||
size={16}
|
||||
color={colors.nautical.teal}
|
||||
/>
|
||||
<Text style={styles.headerRoleText} numberOfLines={1}>
|
||||
{selectedRole.name}
|
||||
</Text>
|
||||
<Ionicons name="chevron-down" size={14} color={colors.flow.textSecondary} />
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* History Button */}
|
||||
<TouchableOpacity
|
||||
<TouchableOpacity
|
||||
style={styles.historyButton}
|
||||
onPress={openHistoryModal}
|
||||
>
|
||||
@@ -515,7 +606,7 @@ export default function FlowScreen() {
|
||||
</View>
|
||||
|
||||
{/* Chat Messages */}
|
||||
<ScrollView
|
||||
<ScrollView
|
||||
ref={scrollViewRef}
|
||||
style={styles.messagesContainer}
|
||||
showsVerticalScrollIndicator={false}
|
||||
@@ -529,7 +620,7 @@ export default function FlowScreen() {
|
||||
) : (
|
||||
messages.map((message, index) => renderMessage(message, index))
|
||||
)}
|
||||
|
||||
|
||||
{/* Loading indicator when sending */}
|
||||
{isSending && (
|
||||
<View style={[styles.messageBubble, styles.aiBubble]}>
|
||||
@@ -541,7 +632,7 @@ export default function FlowScreen() {
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
|
||||
<View style={{ height: 20 }} />
|
||||
</ScrollView>
|
||||
|
||||
@@ -549,7 +640,7 @@ export default function FlowScreen() {
|
||||
<View style={styles.inputBarContainer}>
|
||||
<View style={styles.inputBar}>
|
||||
{/* Image attachment button */}
|
||||
<TouchableOpacity
|
||||
<TouchableOpacity
|
||||
style={styles.inputBarButton}
|
||||
onPress={handleAddImage}
|
||||
activeOpacity={0.7}
|
||||
@@ -572,7 +663,7 @@ export default function FlowScreen() {
|
||||
|
||||
{/* Send or Voice button */}
|
||||
{newContent.trim() || isSending ? (
|
||||
<TouchableOpacity
|
||||
<TouchableOpacity
|
||||
style={[styles.sendButton, isSending && styles.sendButtonDisabled]}
|
||||
onPress={handleSendMessage}
|
||||
activeOpacity={0.8}
|
||||
@@ -590,15 +681,15 @@ export default function FlowScreen() {
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<TouchableOpacity
|
||||
<TouchableOpacity
|
||||
style={[styles.inputBarButton, isRecording && styles.recordingButton]}
|
||||
onPress={handleVoiceRecord}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Feather
|
||||
name="mic"
|
||||
size={22}
|
||||
color={isRecording ? '#fff' : colors.flow.textSecondary}
|
||||
<Feather
|
||||
name="mic"
|
||||
size={22}
|
||||
color={isRecording ? '#fff' : colors.flow.textSecondary}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
@@ -607,17 +698,16 @@ export default function FlowScreen() {
|
||||
</SafeAreaView>
|
||||
</LinearGradient>
|
||||
|
||||
{/* History Modal - Background appears instantly, content slides up */}
|
||||
<Modal
|
||||
visible={showHistoryModal}
|
||||
<Modal
|
||||
visible={showHistoryModal}
|
||||
animationType="none"
|
||||
transparent
|
||||
transparent
|
||||
onRequestClose={closeHistoryModal}
|
||||
>
|
||||
<TouchableWithoutFeedback onPress={closeHistoryModal}>
|
||||
<View style={styles.modalOverlay}>
|
||||
<TouchableWithoutFeedback onPress={(e) => e.stopPropagation()}>
|
||||
<Animated.View
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.modalContent,
|
||||
{
|
||||
@@ -630,46 +720,129 @@ export default function FlowScreen() {
|
||||
}
|
||||
]}
|
||||
>
|
||||
<View style={styles.modalHandle} />
|
||||
|
||||
{/* Modal Header */}
|
||||
<View style={styles.modalHeader}>
|
||||
<Text style={styles.modalTitle}>Chat History</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.newChatButton}
|
||||
onPress={handleNewChat}
|
||||
>
|
||||
<Ionicons name="add" size={20} color="#fff" />
|
||||
<Text style={styles.newChatText}>New Chat</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* History List */}
|
||||
<FlatList
|
||||
data={chatHistory}
|
||||
renderItem={renderHistoryItem}
|
||||
keyExtractor={item => item.id}
|
||||
style={styles.historyList}
|
||||
ListEmptyComponent={
|
||||
<View style={styles.historyEmpty}>
|
||||
<Ionicons name="chatbubbles-outline" size={48} color={colors.flow.textSecondary} />
|
||||
<Text style={styles.historyEmptyText}>No chat history yet</Text>
|
||||
<View style={styles.modalHandle} />
|
||||
|
||||
{/* Modal Header */}
|
||||
<View style={styles.modalHeader}>
|
||||
<Text style={styles.modalTitle}>Chat History</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.newChatButton}
|
||||
onPress={handleNewChat}
|
||||
>
|
||||
<Ionicons name="add" size={20} color="#fff" />
|
||||
<Text style={styles.newChatText}>New Chat</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Close Button */}
|
||||
<TouchableOpacity
|
||||
style={styles.closeButton}
|
||||
onPress={closeHistoryModal}
|
||||
>
|
||||
<Text style={styles.closeButtonText}>Close</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* History List */}
|
||||
<FlatList
|
||||
data={chatHistory}
|
||||
renderItem={renderHistoryItem}
|
||||
keyExtractor={item => item.id}
|
||||
style={styles.historyList}
|
||||
ListEmptyComponent={
|
||||
<View style={styles.historyEmpty}>
|
||||
<Ionicons name="chatbubbles-outline" size={48} color={colors.flow.textSecondary} />
|
||||
<Text style={styles.historyEmptyText}>No chat history yet</Text>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Close Button */}
|
||||
<TouchableOpacity
|
||||
style={styles.closeButton}
|
||||
onPress={closeHistoryModal}
|
||||
>
|
||||
<Text style={styles.closeButtonText}>Close</Text>
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
</TouchableWithoutFeedback>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</Modal>
|
||||
|
||||
{/* Role Selection Modal */}
|
||||
<Modal
|
||||
visible={showRoleModal}
|
||||
animationType="fade"
|
||||
transparent
|
||||
onRequestClose={() => setShowRoleModal(false)}
|
||||
>
|
||||
<TouchableWithoutFeedback onPress={() => setShowRoleModal(false)}>
|
||||
<View style={styles.modalOverlay}>
|
||||
<TouchableWithoutFeedback onPress={(e) => e.stopPropagation()}>
|
||||
<View style={[styles.modalContent, styles.roleModalContent]}>
|
||||
<View style={styles.modalHandle} />
|
||||
<Text style={styles.modalTitle}>Choose AI Assistant</Text>
|
||||
|
||||
<ScrollView style={styles.roleList} showsVerticalScrollIndicator={false}>
|
||||
{AI_CONFIG.ROLES.map((role) => (
|
||||
<View key={role.id} style={styles.roleItemContainer}>
|
||||
<View
|
||||
style={[
|
||||
styles.roleItem,
|
||||
selectedRole.id === role.id && styles.roleItemActive
|
||||
]}
|
||||
>
|
||||
<TouchableOpacity
|
||||
style={styles.roleSelectionArea}
|
||||
onPress={() => {
|
||||
setSelectedRole(role as any);
|
||||
setShowRoleModal(false);
|
||||
}}
|
||||
>
|
||||
<View style={[
|
||||
styles.roleItemIcon,
|
||||
selectedRole.id === role.id && styles.roleItemIconActive
|
||||
]}>
|
||||
<Ionicons
|
||||
name={role.icon as any}
|
||||
size={20}
|
||||
color={selectedRole.id === role.id ? '#fff' : colors.nautical.teal}
|
||||
/>
|
||||
</View>
|
||||
<Text style={[
|
||||
styles.roleItemName,
|
||||
selectedRole.id === role.id && styles.roleItemNameActive
|
||||
]}>
|
||||
{role.name}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.infoButton}
|
||||
onPress={() => {
|
||||
setExpandedRoleId(expandedRoleId === role.id ? null : role.id);
|
||||
}}
|
||||
>
|
||||
<Ionicons
|
||||
name={expandedRoleId === role.id ? "close-circle-outline" : "information-circle-outline"}
|
||||
size={24}
|
||||
color={expandedRoleId === role.id ? colors.nautical.coral : colors.flow.textSecondary}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{expandedRoleId === role.id && (
|
||||
<View style={styles.roleDescription}>
|
||||
<Text style={styles.roleDescriptionText}>{role.description}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.closeButton}
|
||||
onPress={() => setShowRoleModal(false)}
|
||||
>
|
||||
<Text style={styles.closeButtonText}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</Modal>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -680,16 +853,16 @@ export default function FlowScreen() {
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
// Container styles
|
||||
container: {
|
||||
flex: 1
|
||||
container: {
|
||||
flex: 1
|
||||
},
|
||||
gradient: {
|
||||
flex: 1
|
||||
gradient: {
|
||||
flex: 1
|
||||
},
|
||||
safeArea: {
|
||||
flex: 1
|
||||
safeArea: {
|
||||
flex: 1
|
||||
},
|
||||
|
||||
|
||||
// Header styles
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
@@ -697,12 +870,33 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: spacing.base,
|
||||
paddingTop: spacing.sm,
|
||||
paddingBottom: spacing.md,
|
||||
paddingBottom: spacing.sm,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: 'rgba(0,0,0,0.05)',
|
||||
},
|
||||
headerLeft: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: spacing.sm,
|
||||
flex: 1,
|
||||
},
|
||||
headerRoleButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.flow.cardBackground,
|
||||
paddingHorizontal: spacing.sm,
|
||||
paddingVertical: 6,
|
||||
borderRadius: borderRadius.full,
|
||||
marginHorizontal: spacing.sm,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.flow.cardBorder,
|
||||
maxWidth: '40%',
|
||||
},
|
||||
headerRoleText: {
|
||||
fontSize: typography.fontSize.xs,
|
||||
fontWeight: '600',
|
||||
color: colors.flow.text,
|
||||
marginHorizontal: 4,
|
||||
},
|
||||
iconCircle: {
|
||||
width: 44,
|
||||
@@ -731,20 +925,20 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
...shadows.soft,
|
||||
},
|
||||
|
||||
|
||||
// Messages container styles
|
||||
messagesContainer: {
|
||||
flex: 1
|
||||
messagesContainer: {
|
||||
flex: 1
|
||||
},
|
||||
messagesContent: {
|
||||
padding: spacing.base,
|
||||
paddingTop: 0
|
||||
messagesContent: {
|
||||
padding: spacing.base,
|
||||
paddingTop: 0
|
||||
},
|
||||
emptyContent: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
|
||||
|
||||
// Empty state styles
|
||||
emptyState: {
|
||||
alignItems: 'center',
|
||||
@@ -772,7 +966,97 @@ const styles = StyleSheet.create({
|
||||
color: colors.flow.textSecondary,
|
||||
textAlign: 'center',
|
||||
},
|
||||
|
||||
|
||||
// Role selection styles
|
||||
roleDropdown: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.flow.cardBackground,
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: spacing.sm,
|
||||
borderRadius: borderRadius.lg,
|
||||
marginBottom: spacing.md,
|
||||
...shadows.soft,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.flow.cardBorder,
|
||||
},
|
||||
roleIcon: {
|
||||
marginRight: spacing.sm,
|
||||
},
|
||||
roleDropdownText: {
|
||||
fontSize: typography.fontSize.base,
|
||||
fontWeight: '600',
|
||||
color: colors.flow.text,
|
||||
marginRight: spacing.xs,
|
||||
},
|
||||
roleModalContent: {
|
||||
paddingBottom: spacing.xl,
|
||||
},
|
||||
roleList: {
|
||||
marginTop: spacing.sm,
|
||||
maxHeight: 400,
|
||||
},
|
||||
roleItemContainer: {
|
||||
marginBottom: spacing.sm,
|
||||
},
|
||||
roleItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
borderRadius: borderRadius.lg,
|
||||
backgroundColor: 'transparent',
|
||||
borderWidth: 1,
|
||||
borderColor: 'transparent',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
roleItemActive: {
|
||||
backgroundColor: colors.nautical.paleAqua,
|
||||
borderColor: colors.nautical.lightMint,
|
||||
},
|
||||
roleSelectionArea: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
padding: spacing.md,
|
||||
},
|
||||
roleItemIcon: {
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: 18,
|
||||
backgroundColor: colors.flow.backgroundGradientStart,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginRight: spacing.md,
|
||||
},
|
||||
roleItemIconActive: {
|
||||
backgroundColor: colors.nautical.teal,
|
||||
},
|
||||
roleItemName: {
|
||||
fontSize: typography.fontSize.base,
|
||||
fontWeight: '500',
|
||||
color: colors.flow.text,
|
||||
},
|
||||
roleItemNameActive: {
|
||||
fontWeight: '700',
|
||||
color: colors.nautical.teal,
|
||||
},
|
||||
infoButton: {
|
||||
padding: spacing.md,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
roleDescription: {
|
||||
paddingHorizontal: spacing.md + 36 + spacing.md, // icon width + margins
|
||||
paddingBottom: spacing.sm,
|
||||
paddingTop: 0,
|
||||
},
|
||||
roleDescriptionText: {
|
||||
fontSize: typography.fontSize.sm,
|
||||
color: colors.flow.textSecondary,
|
||||
fontStyle: 'italic',
|
||||
lineHeight: 18,
|
||||
},
|
||||
|
||||
// Message bubble styles
|
||||
messageBubble: {
|
||||
flexDirection: 'row',
|
||||
@@ -837,7 +1121,7 @@ const styles = StyleSheet.create({
|
||||
aiMessageTime: {
|
||||
color: colors.flow.textSecondary,
|
||||
},
|
||||
|
||||
|
||||
// Input bar styles
|
||||
inputBarContainer: {
|
||||
paddingHorizontal: spacing.base,
|
||||
@@ -893,7 +1177,7 @@ const styles = StyleSheet.create({
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
|
||||
// Modal styles
|
||||
modalOverlay: {
|
||||
flex: 1,
|
||||
@@ -941,7 +1225,7 @@ const styles = StyleSheet.create({
|
||||
fontWeight: '600',
|
||||
color: '#fff',
|
||||
},
|
||||
|
||||
|
||||
// History list styles
|
||||
historyList: {
|
||||
flex: 1,
|
||||
|
||||
Reference in New Issue
Block a user