Feature: Recherche et modèle par défaut dans l'assistant IA

Améliorations du chatbox IA:

Système de recherche:
- Ajout d'un champ de recherche pour filtrer les modèles par nom ou ID
- Filtre en temps réel pendant la saisie
- Interface avec liste scrollable des modèles (max-h-32)

Système de modèle par défaut:
- Possibilité de définir un modèle comme favori (étoile ★)
- Sauvegarde du modèle par défaut dans localStorage
- Chargement automatique du modèle par défaut à l'ouverture du chatbox
- Indication visuelle: étoile jaune pleine (★) pour le modèle par défaut, étoile vide (☆) pour les autres

Interface améliorée:
- Remplacement du select natif par une liste interactive
- Highlighting du modèle sélectionné (bg-accent)
- Hover effect sur les modèles
- Bouton étoile cliquable sans sélectionner le modèle (stopPropagation)

L'utilisateur peut maintenant facilement trouver et définir son modèle préféré qui sera automatiquement sélectionné à chaque ouverture.
This commit is contained in:
Claude committed 2025-11-12 21:35:55 +00:00
1 parent 2bbb75766c
commit b4e2c254ed
1 file changed
+75 -17
+75 -17
View File
@@ -96,6 +96,8 @@ const Index = () => {
const [chatInput, setChatInput] = useState(""); const [chatInput, setChatInput] = useState("");
const [chatLoading, setChatLoading] = useState(false); const [chatLoading, setChatLoading] = useState(false);
const [selectedModel, setSelectedModel] = useState<string>(""); const [selectedModel, setSelectedModel] = useState<string>("");
const [defaultModel, setDefaultModel] = useState<string>("");
const [modelSearchQuery, setModelSearchQuery] = useState<string>("");
// Debounce timer for auto-save // Debounce timer for auto-save
const saveTimerRef = useRef<NodeJS.Timeout | null>(null); const saveTimerRef = useRef<NodeJS.Timeout | null>(null);
@@ -527,6 +529,14 @@ const Index = () => {
chatEndRef.current?.scrollIntoView({ behavior: 'smooth' }); chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [chatMessages]); }, [chatMessages]);
// Load default model from localStorage on mount
useEffect(() => {
const savedDefaultModel = localStorage.getItem('defaultAiModel');
if (savedDefaultModel) {
setDefaultModel(savedDefaultModel);
}
}, []);
// Load models when chatbox opens // Load models when chatbox opens
useEffect(() => { useEffect(() => {
if (chatOpen && openRouterModels.length === 0) { if (chatOpen && openRouterModels.length === 0) {
@@ -537,9 +547,22 @@ const Index = () => {
// Set default model when models are loaded // Set default model when models are loaded
useEffect(() => { useEffect(() => {
if (openRouterModels.length > 0 && !selectedModel) { if (openRouterModels.length > 0 && !selectedModel) {
setSelectedModel(openRouterModels[0].id); // If there's a default model saved, use it
if (defaultModel && openRouterModels.find(m => m.id === defaultModel)) {
setSelectedModel(defaultModel);
} else {
// Otherwise use the first model
setSelectedModel(openRouterModels[0].id);
}
} }
}, [openRouterModels, selectedModel]); }, [openRouterModels, selectedModel, defaultModel]);
// Save default model to localStorage
const handleSetDefaultModel = (modelId: string) => {
setDefaultModel(modelId);
localStorage.setItem('defaultAiModel', modelId);
showSuccess("Modèle par défaut défini");
};
const confirmAddUser = async (username: string, password: string, isAdmin: boolean) => { const confirmAddUser = async (username: string, password: string, isAdmin: boolean) => {
try { try {
@@ -2266,25 +2289,60 @@ const Index = () => {
</Button> </Button>
</div> </div>
</div> </div>
<div className="mt-2"> <div className="mt-3 space-y-2">
<select {/* Search field */}
value={selectedModel} <Input
onChange={(e) => setSelectedModel(e.target.value)} placeholder="Rechercher un modèle..."
className="w-full text-xs px-2 py-1.5 border border-input rounded-md bg-background" value={modelSearchQuery}
disabled={chatLoading || loadingModels} onChange={(e) => setModelSearchQuery(e.target.value)}
> className="text-xs h-8"
disabled={loadingModels}
/>
{/* Models list */}
<div className="max-h-32 overflow-y-auto border rounded-md">
{loadingModels ? ( {loadingModels ? (
<option value="">Chargement des modèles...</option> <div className="text-xs text-center py-2 text-muted-foreground">
Chargement des modèles...
</div>
) : openRouterModels.length === 0 ? ( ) : openRouterModels.length === 0 ? (
<option value="">Aucun modèle disponible</option> <div className="text-xs text-center py-2 text-muted-foreground">
Aucun modèle disponible
</div>
) : ( ) : (
openRouterModels.map(model => ( openRouterModels
<option key={model.id} value={model.id}> .filter(model =>
{model.name} model.name.toLowerCase().includes(modelSearchQuery.toLowerCase()) ||
</option> model.id.toLowerCase().includes(modelSearchQuery.toLowerCase())
)) )
.map(model => (
<div
key={model.id}
className={`flex items-center justify-between px-2 py-1.5 hover:bg-accent cursor-pointer text-xs ${
selectedModel === model.id ? 'bg-accent' : ''
}`}
onClick={() => setSelectedModel(model.id)}
>
<span className="flex-1 truncate">{model.name}</span>
<Button
variant="ghost"
size="icon"
className="h-5 w-5"
onClick={(e) => {
e.stopPropagation();
handleSetDefaultModel(model.id);
}}
>
{defaultModel === model.id ? (
<span className="text-yellow-500">★</span>
) : (
<span className="text-muted-foreground">☆</span>
)}
</Button>
</div>
))
)} )}
</select> </div>
</div> </div>
</CardHeader> </CardHeader>