import { ConnectionBadge } from '@/components/ui/ConnectionBadge'; import { useLMStudio } from '@/hooks/useLMStudio'; import { useAppStore } from '@/store/useAppStore'; import { Check, ChevronDown, RefreshCw } from 'lucide-react-native'; import React, { useState } from 'react'; import { ActivityIndicator, FlatList, Modal, Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; export function ModelSelector() { const [modalVisible, setModalVisible] = useState(false); const activeModelId = useAppStore(state => state.activeModelId); const setActiveModelId = useAppStore(state => state.setActiveModelId); const { availableModels, isConnected, fetchModels } = useLMStudio(); const [isRefreshing, setIsRefreshing] = useState(false); const handleSelect = (modelId: string) => { setActiveModelId(modelId); setModalVisible(false); }; const handleRefresh = async () => { setIsRefreshing(true); await fetchModels(); setIsRefreshing(false); }; const activeModelName = activeModelId || 'Select Model'; return ( <> setModalVisible(true)} activeOpacity={0.7} > {activeModelName} setModalVisible(false)} > setModalVisible(false)} > true}> Select Model {isRefreshing ? ( ) : ( )} {!isConnected && ( Not connected to LM Studio. Configure server in Settings. )} {availableModels.length === 0 && isConnected && ( No models loaded. Load a model in LM Studio first. )} item.id} renderItem={({ item }) => { const isSelected = item.id === activeModelId; return ( handleSelect(item.id)} > {item.id} API {item.owned_by && ( {item.owned_by} )} {isSelected && } ); }} /> ); } const styles = StyleSheet.create({ headerButton: { flexDirection: 'row', alignItems: 'center', paddingVertical: 8, paddingHorizontal: 12, borderRadius: 8, backgroundColor: '#F3F4F6', gap: 8, maxWidth: 280, }, headerText: { fontSize: 14, fontWeight: '600', color: '#111827', flexShrink: 1, }, iconOffset: { marginTop: 2, }, modalOverlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.4)', justifyContent: 'flex-end', }, bottomSheet: { backgroundColor: '#FFFFFF', borderTopLeftRadius: 24, borderTopRightRadius: 24, padding: 24, paddingTop: 12, maxHeight: '80%', ...Platform.select({ web: { maxWidth: 480, marginHorizontal: 'auto', borderBottomLeftRadius: 24, borderBottomRightRadius: 24, marginBottom: 24, maxHeight: '60%', alignSelf: 'center', width: '100%', shadowColor: '#000', shadowOpacity: 0.1, shadowRadius: 24, } }) }, dragHandle: { width: 40, height: 4, backgroundColor: '#E5E7EB', borderRadius: 2, alignSelf: 'center', marginBottom: 16, ...Platform.select({ web: { display: 'none' } }) }, sheetHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, }, sheetTitle: { fontSize: 20, fontWeight: '700', color: '#111827', }, refreshButton: { padding: 8, borderRadius: 8, backgroundColor: '#F3F4F6', }, warningBox: { backgroundColor: '#FEF3C7', borderRadius: 8, padding: 12, marginBottom: 16, }, warningText: { fontSize: 13, color: '#92400E', textAlign: 'center', }, emptyBox: { backgroundColor: '#F3F4F6', borderRadius: 8, padding: 16, marginBottom: 16, }, emptyText: { fontSize: 14, color: '#6B7280', textAlign: 'center', }, itemRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingVertical: 14, paddingHorizontal: 16, borderRadius: 12, marginBottom: 8, backgroundColor: '#FFFFFF', borderWidth: 1, borderColor: '#E5E7EB', }, itemRowSelected: { borderColor: '#000000', backgroundColor: '#F9FAFB', }, itemContent: { flex: 1, paddingRight: 16, }, itemName: { fontSize: 15, fontWeight: '600', color: '#111827', marginBottom: 6, }, itemNameSelected: { color: '#000000', }, badgeRow: { flexDirection: 'row', gap: 6, }, badge: { alignSelf: 'flex-start', backgroundColor: '#DBEAFE', paddingHorizontal: 8, paddingVertical: 3, borderRadius: 6, }, badgeSecondary: { backgroundColor: '#F3F4F6', }, badgeText: { fontSize: 11, fontWeight: '500', color: '#4B5563', }, });