From 075f6ae0bca67d970f5098552b9fa4113a8f0378 Mon Sep 17 00:00:00 2001 From: admtracksteel Date: Sat, 4 Apr 2026 19:51:34 +0000 Subject: [PATCH] feat: Ollama auto-detect without manual input - Auto-detect Ollama endpoint from predefined URLs - Try multiple common addresses (localhost, VPS IPs, cloud domain) - One-click connect to Ollama without manual endpoint entry - Visual feedback during detection - Support for https://llm.reifonas.cloud --- components/ApiKeySetup.tsx | 99 ++++++++++++++++++++++++++++++-------- services/apiTestService.ts | 37 ++++++++++++-- types/providers.ts | 10 ++++ 3 files changed, 121 insertions(+), 25 deletions(-) diff --git a/components/ApiKeySetup.tsx b/components/ApiKeySetup.tsx index f46ee62..356cbf6 100644 --- a/components/ApiKeySetup.tsx +++ b/components/ApiKeySetup.tsx @@ -80,6 +80,33 @@ export const ApiKeySetup: React.FC = ({ onKeySave }) => { }; const handleTestApi = async () => { + if (provider === 'ollama') { + setIsTesting(true); + setError(null); + try { + const result = await testApiKey(provider, '', endpoint); + + if (result.success && result.models && result.models.length > 0) { + setAvailableModels(result.models); + setModel(result.models[0].id); + if (result.endpoint) { + setEndpoint(result.endpoint); + } + setTestStatus('success'); + setError(null); + } else { + setTestStatus('error'); + setError(result.error || 'Falha ao conectar com Ollama'); + } + } catch (err) { + setTestStatus('error'); + setError(err instanceof Error ? err.message : 'Erro ao testar API'); + } finally { + setIsTesting(false); + } + return; + } + if (!localApiKey.trim() || !isValidApiKey(localApiKey)) { setError('Insira uma chave de API válida para testar.'); return; @@ -114,11 +141,12 @@ export const ApiKeySetup: React.FC = ({ onKeySave }) => { setModel(providerConfig?.defaultModel || ''); setAvailableModels([]); setTestStatus('idle'); + setEndpoint(''); }; const handleSave = () => { if (provider === 'ollama') { - if (endpoint.trim()) { + if (endpoint.trim() || testStatus === 'success') { onKeySave('', provider, model, endpoint.trim()); } } else if (localApiKey.trim() && isValidApiKey(localApiKey)) { @@ -163,30 +191,61 @@ export const ApiKeySetup: React.FC = ({ onKeySave }) => { - {/* Azure/Ollama Endpoint */} - {(provider === 'azure' || provider === 'ollama') && ( + {/* Ollama Endpoint - Auto Detect */} + {provider === 'ollama' && (
-
)} - {/* API Key Input (not needed for Ollama) */} - {provider !== 'ollama' && ( + {/* Azure Endpoint */} + {provider === 'azure' && (
diff --git a/services/apiTestService.ts b/services/apiTestService.ts index ed11668..5717eb3 100644 --- a/services/apiTestService.ts +++ b/services/apiTestService.ts @@ -1,4 +1,5 @@ import { PROVIDERS, type AIProvider } from '../types/providers'; +import { OLLAMA_AUTO_DETECT_URLS } from '../types/providers'; export interface ModelInfo { id: string; @@ -9,6 +10,7 @@ interface TestResult { success: boolean; models?: ModelInfo[]; error?: string; + endpoint?: string; } export const testApiKey = async (provider: AIProvider, apiKey: string, endpoint?: string): Promise => { @@ -35,13 +37,37 @@ export const testApiKey = async (provider: AIProvider, apiKey: string, endpoint? } }; +const findOllamaEndpoint = async (): Promise => { + for (const url of OLLAMA_AUTO_DETECT_URLS) { + try { + const response = await fetch(`${url}/api/tags`, { + method: 'GET', + signal: AbortSignal.timeout(3000) + }); + if (response.ok) { + return url; + } + } catch { + continue; + } + } + return null; +}; + const testOllama = async (endpoint?: string): Promise => { - if (!endpoint) { - return { success: false, error: 'Endereço do Ollama é obrigatório (ex: http://192.168.1.100:11434)' }; + let ollamaEndpoint = endpoint; + + if (!ollamaEndpoint) { + const foundEndpoint = await findOllamaEndpoint(); + if (foundEndpoint) { + ollamaEndpoint = foundEndpoint; + } else { + return { success: false, error: 'Ollama não encontrado. Configure o endereço manualmente.' }; + } } try { - const response = await fetch(`${endpoint}/api/tags`); + const response = await fetch(`${ollamaEndpoint}/api/tags`); if (!response.ok) { return { success: false, error: 'Não foi possível conectar ao Ollama. Verifique o endereço.' }; @@ -61,12 +87,13 @@ const testOllama = async (endpoint?: string): Promise => { ); if (visionModels.length > 0) { - return { success: true, models: visionModels }; + return { success: true, models: visionModels, endpoint: ollamaEndpoint }; } return { success: true, - models: models.length > 0 ? models : [{ id: 'llama3.2', name: 'Llama 3.2 (Padrão)' }] + models: models.length > 0 ? models : [{ id: 'llama3.2', name: 'Llama 3.2 (Padrão)' }], + endpoint: ollamaEndpoint }; } catch (error: any) { return { success: false, error: 'Não foi possível conectar ao Ollama. Verifique o endereço e certifique-se que o Ollama está rodando.' }; diff --git a/types/providers.ts b/types/providers.ts index c546930..4173c09 100644 --- a/types/providers.ts +++ b/types/providers.ts @@ -2,6 +2,16 @@ import { AIProvider } from './providers'; export type AIProvider = 'gemini' | 'openai' | 'anthropic' | 'azure' | 'ollama'; +export const OLLAMA_AUTO_DETECT_URLS = [ + 'http://localhost:11434', + 'http://127.0.0.1:11434', + 'http://192.168.1.100:11434', + 'http://10.0.0.1:11434', + 'https://llm.reifonas.cloud', + 'http://ollama:11434', + 'http://host.docker.internal:11434', +]; + export interface ProviderConfig { id: AIProvider; name: string;