Origem: D:/Documents/Github/LocalAiApp Arquivos omitidos (segredos, bancos de dados, logs, >50 MB): 0
203 lines
6.4 KiB
TypeScript
203 lines
6.4 KiB
TypeScript
import { useThemeColor } from '@/hooks/useThemeColor';
|
|
import { useAppStore } from '@/store/useAppStore';
|
|
import * as Haptics from 'expo-haptics';
|
|
import { Globe, Send, Square, X } from 'lucide-react-native';
|
|
import React, { useEffect, useState } from 'react';
|
|
import {
|
|
Platform,
|
|
StyleSheet,
|
|
Text,
|
|
TextInput,
|
|
TouchableOpacity,
|
|
View
|
|
} from 'react-native';
|
|
|
|
interface ChatInputProps {
|
|
onSend: (text: string) => void;
|
|
onStop?: () => void;
|
|
isLoading?: boolean;
|
|
editingText?: string | null;
|
|
onCancelEdit?: () => void;
|
|
}
|
|
|
|
export function ChatInput({ onSend, onStop, isLoading, editingText, onCancelEdit }: ChatInputProps) {
|
|
const [text, setText] = useState('');
|
|
const colors = useThemeColor();
|
|
const isWebSearchEnabled = useAppStore(state => state.isWebSearchEnabled);
|
|
const setIsWebSearchEnabled = useAppStore(state => state.setIsWebSearchEnabled);
|
|
|
|
// When editingText changes, fill the input
|
|
useEffect(() => {
|
|
if (editingText !== null && editingText !== undefined) {
|
|
setText(editingText);
|
|
}
|
|
}, [editingText]);
|
|
|
|
const handleSend = () => {
|
|
if (text.trim() && !isLoading) {
|
|
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
|
onSend(text.trim());
|
|
setText('');
|
|
}
|
|
};
|
|
|
|
const handleStop = () => {
|
|
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
|
|
onStop?.();
|
|
};
|
|
|
|
const handleCancelEdit = () => {
|
|
setText('');
|
|
onCancelEdit?.();
|
|
};
|
|
|
|
const handleKeyPress = (e: any) => {
|
|
if (Platform.OS === 'web') {
|
|
if (e.nativeEvent.key === 'Enter' && !e.nativeEvent.shiftKey) {
|
|
e.preventDefault();
|
|
handleSend();
|
|
}
|
|
}
|
|
};
|
|
|
|
const isEditing = editingText !== null && editingText !== undefined;
|
|
|
|
return (
|
|
<View style={[styles.outerContainer, { backgroundColor: colors.backgroundColor, borderTopColor: colors.border }]}>
|
|
{/* Edit mode banner */}
|
|
{isEditing && (
|
|
<View style={[styles.editBanner, { backgroundColor: colors.bannerBg, borderBottomColor: colors.border }]}>
|
|
<Text style={[styles.editBannerText, { color: colors.bannerText }]}>Editing message</Text>
|
|
<TouchableOpacity onPress={handleCancelEdit} style={styles.editCancelBtn}>
|
|
<X size={16} color={colors.textMuted} />
|
|
</TouchableOpacity>
|
|
</View>
|
|
)}
|
|
|
|
<View style={styles.container}>
|
|
<View style={[
|
|
styles.inputWrapper,
|
|
{ backgroundColor: colors.inputBg, borderColor: colors.border, borderWidth: 1 },
|
|
isEditing && { borderColor: colors.tint, borderWidth: 1.5 }
|
|
]}>
|
|
<TouchableOpacity
|
|
style={styles.webSearchButton}
|
|
onPress={() => {
|
|
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
|
setIsWebSearchEnabled(!isWebSearchEnabled);
|
|
}}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Globe
|
|
size={20}
|
|
color={isWebSearchEnabled ? colors.tint : colors.textMuted}
|
|
/>
|
|
</TouchableOpacity>
|
|
|
|
<TextInput
|
|
style={[styles.input, { color: colors.text }]}
|
|
placeholder="Send a message..."
|
|
placeholderTextColor={colors.textMuted}
|
|
value={text}
|
|
onChangeText={setText}
|
|
multiline
|
|
onKeyPress={handleKeyPress}
|
|
editable={!isLoading}
|
|
/>
|
|
|
|
{/* Send or Stop button */}
|
|
{isLoading ? (
|
|
<TouchableOpacity
|
|
style={[styles.stopButton, { backgroundColor: colors.errorText }]}
|
|
onPress={handleStop}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Square size={16} color={colors.backgroundColor} />
|
|
</TouchableOpacity>
|
|
) : (
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.sendButton,
|
|
{ backgroundColor: text.trim() ? colors.tint : colors.border }
|
|
]}
|
|
onPress={handleSend}
|
|
disabled={!text.trim()}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Send size={18} color={text.trim() ? colors.backgroundColor : colors.textMuted} />
|
|
</TouchableOpacity>
|
|
)}
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
outerContainer: {
|
|
borderTopWidth: 1,
|
|
},
|
|
editBanner: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 8,
|
|
borderBottomWidth: 1,
|
|
},
|
|
editBannerText: {
|
|
fontSize: 13,
|
|
fontWeight: '500',
|
|
},
|
|
editCancelBtn: {
|
|
padding: 4,
|
|
},
|
|
container: {
|
|
padding: 12,
|
|
paddingBottom: Platform.OS === 'ios' ? 24 : 12,
|
|
},
|
|
inputWrapper: {
|
|
flexDirection: 'row',
|
|
alignItems: 'flex-end',
|
|
borderRadius: 24,
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 6,
|
|
minHeight: 44,
|
|
maxHeight: 120,
|
|
},
|
|
input: {
|
|
flex: 1,
|
|
fontSize: 15,
|
|
lineHeight: 22,
|
|
paddingTop: 8,
|
|
paddingBottom: 8,
|
|
},
|
|
sendButton: {
|
|
width: 34,
|
|
height: 34,
|
|
borderRadius: 17,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
marginLeft: 8,
|
|
marginBottom: 2,
|
|
},
|
|
webSearchButton: {
|
|
width: 34,
|
|
height: 34,
|
|
borderRadius: 17,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
marginRight: 4,
|
|
marginBottom: 2,
|
|
},
|
|
stopButton: {
|
|
width: 34,
|
|
height: 34,
|
|
borderRadius: 17,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
marginLeft: 8,
|
|
marginBottom: 2,
|
|
},
|
|
});
|