Files
MyMobileAgent/app/src/screens/HuggingFaceModelsScreen/index.tsx
Jonathan Atta da373199e0 Initial commit
2026-03-03 10:33:56 +01:00

221 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useEffect, useState } from 'react';
import {
View,
Text,
FlatList,
TouchableOpacity,
ActivityIndicator,
Alert,
KeyboardAvoidingView,
Platform,
Linking,
} from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from '../../store';
import {
searchHuggingFaceModels,
saveHuggingFaceApiKey,
loadHuggingFaceApiKey,
setSearchQuery,
clearHFError,
} from '../../store/modelsSlice';
import HFModelItem from './HFModelItem';
import ApiKeyEditor from './ApiKeyEditor';
import SearchSection from './SearchSection';
import { handleModelDownload } from './downloadHelper';
import { createStyles } from './styles';
import { useTheme } from '../../theme/ThemeProvider';
export default function HuggingFaceModelsScreen() {
const { colors } = useTheme();
const styles = createStyles(colors);
const dispatch = useDispatch<AppDispatch>();
const {
huggingFaceModels,
huggingFaceApiKey,
searchQuery,
isLoadingHF,
hfError,
modelsDirectory,
downloadProgress,
} = useSelector((state: RootState) => state.models);
const [showApiKeyInput, setShowApiKeyInput] = useState(false);
const [tempApiKey, setTempApiKey] = useState('');
const [localSearchQuery, setLocalSearchQuery] = useState(searchQuery);
useEffect(() => {
// Load saved API key on mount
dispatch(loadHuggingFaceApiKey());
}, [dispatch]);
useEffect(() => {
setTempApiKey(huggingFaceApiKey);
}, [huggingFaceApiKey]);
useEffect(() => {
if (hfError) {
Alert.alert('Erreur', hfError, [
{ text: 'OK', onPress: () => dispatch(clearHFError()) },
]);
}
}, [hfError, dispatch]);
const handleSearch = () => {
if (localSearchQuery.trim()) {
dispatch(setSearchQuery(localSearchQuery.trim()));
dispatch(searchHuggingFaceModels({
query: localSearchQuery.trim(),
apiKey: huggingFaceApiKey || undefined,
}));
}
};
const handleSaveApiKey = () => {
dispatch(saveHuggingFaceApiKey(tempApiKey.trim()));
setShowApiKeyInput(false);
Alert.alert('Succès', 'Clé API enregistrée');
};
const handleOpenHuggingFace = () => {
Linking.openURL('https://huggingface.co/settings/tokens');
};
const handleModelPress = (modelId: string) => {
const url = `https://huggingface.co/${modelId}`;
Linking.openURL(url);
};
const handleDownload = (modelId: string) => {
handleModelDownload(modelId, modelsDirectory, dispatch);
};
const renderModelItem = ({ item }: { item: typeof huggingFaceModels[0] }) => {
const modelProgress = downloadProgress[item.id];
const isDownloading = !!modelProgress;
const progressPercent = modelProgress
? (modelProgress.bytesWritten / modelProgress.contentLength) * 100
: 0;
return (
<HFModelItem
model={item}
onPress={handleModelPress}
onDownload={handleDownload}
isDownloading={isDownloading}
downloadProgress={progressPercent}
bytesWritten={modelProgress?.bytesWritten || 0}
contentLength={modelProgress?.contentLength || 0}
/>
);
};
const renderEmptyList = () => {
if (isLoadingHF) {
return null;
}
return (
<View style={styles.emptyContainer}>
{searchQuery ? (
<>
<Text style={styles.emptyText}>Aucun modèle trouvé</Text>
<Text style={styles.emptySubtext}>
Essayez une autre recherche
</Text>
</>
) : (
<>
<Text style={styles.emptyText}>
Recherchez des modèles GGUF
</Text>
<Text style={styles.emptySubtext}>
Entrez un terme de recherche ci-dessus
</Text>
</>
)}
</View>
);
};
return (
<KeyboardAvoidingView
style={styles.container}
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
>
{/* API Key Section */}
<View style={styles.apiKeySection}>
<View style={styles.apiKeyHeader}>
<Text style={styles.sectionTitle}>Clé API HuggingFace</Text>
<TouchableOpacity
style={styles.infoButton}
onPress={handleOpenHuggingFace}
>
<Text style={styles.infoButtonText}> Obtenir</Text>
</TouchableOpacity>
</View>
{showApiKeyInput ? (
<ApiKeyEditor
tempApiKey={tempApiKey}
onChangeText={setTempApiKey}
onCancel={() => {
setTempApiKey(huggingFaceApiKey);
setShowApiKeyInput(false);
}}
onSave={handleSaveApiKey}
/>
) : (
<View style={styles.apiKeyDisplay}>
<Text style={styles.apiKeyText}>
{huggingFaceApiKey ? '•••••••••••••' : 'Non configurée'}
</Text>
<TouchableOpacity
style={styles.changeButton}
onPress={() => setShowApiKeyInput(true)}
>
<Text style={styles.changeButtonText}>
{huggingFaceApiKey ? 'Modifier' : 'Configurer'}
</Text>
</TouchableOpacity>
</View>
)}
<Text style={styles.apiKeyHint}>
Optionnel - Permet d'augmenter les limites de recherche
</Text>
</View>
{/* Search Section */}
<SearchSection
searchQuery={localSearchQuery}
onChangeText={setLocalSearchQuery}
onSearch={handleSearch}
isLoading={isLoadingHF}
/>
{/* Results Section */}
<View style={styles.resultsSection}>
<Text style={styles.sectionTitle}>
Résultats ({huggingFaceModels.length})
</Text>
{isLoadingHF && huggingFaceModels.length === 0 ? (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#007AFF" />
<Text style={styles.loadingText}>Recherche en cours...</Text>
</View>
) : (
<FlatList
data={huggingFaceModels}
renderItem={renderModelItem}
keyExtractor={(item) => item.id}
ListEmptyComponent={renderEmptyList}
contentContainerStyle={styles.listContent}
/>
)}
</View>
</KeyboardAvoidingView>
);
}