Initial commit

This commit is contained in:
Jonathan Atta
2026-03-03 10:33:56 +01:00
commit da373199e0
139 changed files with 26421 additions and 0 deletions

View File

@@ -0,0 +1,220 @@
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>
);
}