- Added support for Google Gemini, OpenAI, Anthropic, and Azure OpenAI - Implemented API key validation with auto model detection - Added Error Boundary for better error handling - Migrated PDF generation to native jsPDF (better quality) - Added PWA support with offline capabilities - Implemented tests with Vitest - Fixed language consistency (PT-BR) - Improved accessibility (ARIA)
86 lines
4.2 KiB
TypeScript
86 lines
4.2 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { PROVIDERS, type AIProvider, type ProviderConfig } from '../types/providers';
|
|
|
|
interface ProviderSelectorProps {
|
|
onProviderChange: (provider: AIProvider) => void;
|
|
}
|
|
|
|
export const ProviderSelector: React.FC<ProviderSelectorProps> = ({ onProviderChange }) => {
|
|
const [selectedProvider, setSelectedProvider] = useState<AIProvider>('gemini');
|
|
const [showDropdown, setShowDropdown] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const saved = localStorage.getItem('ai-provider') as AIProvider;
|
|
if (saved && PROVIDERS.find(p => p.id === saved)) {
|
|
setSelectedProvider(saved);
|
|
onProviderChange(saved);
|
|
}
|
|
}, [onProviderChange]);
|
|
|
|
const handleSelect = (provider: AIProvider) => {
|
|
setSelectedProvider(provider);
|
|
setShowDropdown(false);
|
|
localStorage.setItem('ai-provider', provider);
|
|
onProviderChange(provider);
|
|
};
|
|
|
|
const currentProvider = PROVIDERS.find(p => p.id === selectedProvider)!;
|
|
|
|
return (
|
|
<div className="relative">
|
|
<label className="block text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider mb-2">
|
|
Provedor de IA
|
|
</label>
|
|
<button
|
|
type="button"
|
|
onClick={() => setShowDropdown(!showDropdown)}
|
|
className="w-full flex items-center justify-between gap-3 px-4 py-3 bg-white/50 dark:bg-slate-700/50 border border-slate-300 dark:border-slate-600 rounded-xl shadow-sm hover:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all"
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 flex items-center justify-center text-white font-bold text-sm">
|
|
{currentProvider.name.charAt(0)}
|
|
</div>
|
|
<div className="text-left">
|
|
<span className="block font-medium text-slate-700 dark:text-slate-200">{currentProvider.name}</span>
|
|
<span className="block text-xs text-slate-500 dark:text-slate-400">{currentProvider.defaultModel}</span>
|
|
</div>
|
|
</div>
|
|
<svg className={`w-5 h-5 text-slate-400 transition-transform ${showDropdown ? 'rotate-180' : ''}`} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
</svg>
|
|
</button>
|
|
|
|
{showDropdown && (
|
|
<div className="absolute z-50 w-full mt-2 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl shadow-lg overflow-hidden">
|
|
{PROVIDERS.map((provider) => (
|
|
<button
|
|
key={provider.id}
|
|
type="button"
|
|
onClick={() => handleSelect(provider.id)}
|
|
className={`w-full flex items-center gap-3 px-4 py-3 text-left hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors
|
|
${selectedProvider === provider.id ? 'bg-blue-50 dark:bg-blue-900/20' : ''}`}
|
|
>
|
|
<div className={`w-8 h-8 rounded-lg flex items-center justify-center text-white font-bold text-sm
|
|
${provider.id === 'gemini' ? 'bg-gradient-to-br from-blue-500 to-indigo-600' : ''}
|
|
${provider.id === 'openai' ? 'bg-gradient-to-br from-green-500 to-emerald-600' : ''}
|
|
${provider.id === 'anthropic' ? 'bg-gradient-to-br from-orange-500 to-amber-600' : ''}
|
|
${provider.id === 'azure' ? 'bg-gradient-to-br from-blue-600 to-cyan-600' : ''}`}
|
|
>
|
|
{provider.name.charAt(0)}
|
|
</div>
|
|
<div className="flex-1">
|
|
<span className="block font-medium text-slate-700 dark:text-slate-200">{provider.name}</span>
|
|
<span className="block text-xs text-slate-500 dark:text-slate-400">{provider.description}</span>
|
|
</div>
|
|
{selectedProvider === provider.id && (
|
|
<svg className="w-5 h-5 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
)}
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}; |