Files
SteelCheck/components/ProviderSelector.tsx
admtracksteel 97eb42c243 feat: multi-provider AI support with auto-detection
- 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)
2026-04-04 19:32:00 +00:00

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>
);
};