diff --git a/App.tsx b/App.tsx index 697b5bc..beb5e76 100644 --- a/App.tsx +++ b/App.tsx @@ -14,6 +14,7 @@ import { ApiKeySetup } from './components/ApiKeySetup'; const App: React.FC = () => { const [file, setFile] = useState(null); const [apiKey, setApiKey] = useState(''); + const [endpoint, setEndpoint] = useState(''); const [provider, setProvider] = useState('gemini'); const [model, setModel] = useState('gemini-2.5-flash'); const [hasKey, setHasKey] = useState(false); @@ -25,11 +26,13 @@ const App: React.FC = () => { const savedApiKey = localStorage.getItem('api-key'); const savedProvider = localStorage.getItem('ai-provider') as AIProvider; const savedModel = localStorage.getItem('model-' + savedProvider); + const savedEndpoint = localStorage.getItem('ollama-endpoint'); - if (savedApiKey) { - setApiKey(savedApiKey); + if (savedApiKey || savedEndpoint) { + setApiKey(savedApiKey || ''); if (savedProvider) setProvider(savedProvider); if (savedModel) setModel(savedModel); + if (savedEndpoint) setEndpoint(savedEndpoint); setHasKey(true); } }, []); @@ -42,24 +45,33 @@ const App: React.FC = () => { } }, []); - const handleKeySave = useCallback((key: string, newProvider: AIProvider, newModel: string) => { - if (key) { - setApiKey(key); - setProvider(newProvider); - setModel(newModel); - localStorage.setItem('api-key', key); - localStorage.setItem('ai-provider', newProvider); - localStorage.setItem('model-' + newProvider, newModel); - setHasKey(true); + const handleKeySave = useCallback((key: string, newProvider: AIProvider, newModel: string, newEndpoint?: string) => { + setApiKey(key); + setProvider(newProvider); + setModel(newModel); + if (newEndpoint) { + setEndpoint(newEndpoint); + localStorage.setItem('ollama-endpoint', newEndpoint); } + if (key) { + localStorage.setItem('api-key', key); + } + localStorage.setItem('ai-provider', newProvider); + localStorage.setItem('model-' + newProvider, newModel); + setHasKey(true); }, []); const handleAnalyzeClick = async () => { - if (!apiKey) { + if (provider !== 'ollama' && !apiKey) { setError("A chave de API não foi encontrada. Por favor, configure-a novamente."); setHasKey(false); return; } + if (provider === 'ollama' && !endpoint) { + setError("O endereço do Ollama não foi configurado. Por favor, configure-o."); + setHasKey(false); + return; + } if (!file) { setError("Por favor, selecione um arquivo primeiro."); return; @@ -72,7 +84,8 @@ const App: React.FC = () => { provider, apiKey, model, - file + file, + endpoint: provider === 'ollama' ? endpoint : undefined }); setReportData(data); } catch (err) { diff --git a/components/ApiKeySetup.tsx b/components/ApiKeySetup.tsx index 41d8e2f..f46ee62 100644 --- a/components/ApiKeySetup.tsx +++ b/components/ApiKeySetup.tsx @@ -4,7 +4,7 @@ import { PROVIDERS, type AIProvider } from '../types/providers'; import { testApiKey, type ModelInfo } from '../services/apiTestService'; interface ApiKeySetupProps { - onKeySave: (key: string, provider: AIProvider, model: string) => void; + onKeySave: (key: string, provider: AIProvider, model: string, endpoint?: string) => void; } const isValidApiKey = (key: string): boolean => { @@ -63,6 +63,7 @@ export const ApiKeySetup: React.FC = ({ onKeySave }) => { case 'openai': return 'https://platform.openai.com/api-keys'; case 'anthropic': return 'https://console.anthropic.com/keys'; case 'azure': return 'https://portal.azure.com/#view/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps'; + case 'ollama': return 'https://ollama.com/download'; default: return '#'; } }; @@ -73,6 +74,7 @@ export const ApiKeySetup: React.FC = ({ onKeySave }) => { case 'openai': return 'OpenAI'; case 'anthropic': return 'Anthropic (Claude)'; case 'azure': return 'Azure OpenAI'; + case 'ollama': return 'Ollama (Local)'; default: return 'API'; } }; @@ -115,7 +117,11 @@ export const ApiKeySetup: React.FC = ({ onKeySave }) => { }; const handleSave = () => { - if (localApiKey.trim() && isValidApiKey(localApiKey)) { + if (provider === 'ollama') { + if (endpoint.trim()) { + onKeySave('', provider, model, endpoint.trim()); + } + } else if (localApiKey.trim() && isValidApiKey(localApiKey)) { onKeySave(localApiKey.trim(), provider, model); } }; @@ -157,79 +163,86 @@ export const ApiKeySetup: React.FC = ({ onKeySave }) => { - {/* Azure Endpoint (only show if Azure is selected) */} - {provider === 'azure' && ( + {/* Azure/Ollama Endpoint */} + {(provider === 'azure' || provider === 'ollama') && (
-
)} - {/* API Key Input */} -
- -
-
- + {/* API Key Input (not needed for Ollama) */} + {provider !== 'ollama' && ( +
+ +
+
+ +
+ e.key === 'Enter' && handleTestApi()} + autoComplete="off" + /> +
+ +
- e.key === 'Enter' && handleTestApi()} - autoComplete="off" - /> -
- -
-
- {error && ( -

- {error} + {error && ( +

+ {error} +

+ )} +

+ Não tem uma chave? Obtenha aqui.

- )} -

- Não tem uma chave? Obtenha aqui. -

-
+
+ )} {/* Model Selector */}
@@ -268,7 +281,7 @@ export const ApiKeySetup: React.FC = ({ onKeySave }) => {