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)
This commit is contained in:
161
services/apiTestService.ts
Normal file
161
services/apiTestService.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { PROVIDERS, type AIProvider } from '../types/providers';
|
||||
|
||||
export interface ModelInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface TestResult {
|
||||
success: boolean;
|
||||
models?: ModelInfo[];
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export const testApiKey = async (provider: AIProvider, apiKey: string, endpoint?: string): Promise<TestResult> => {
|
||||
try {
|
||||
switch (provider) {
|
||||
case 'gemini':
|
||||
return await testGemini(apiKey);
|
||||
case 'openai':
|
||||
return await testOpenAI(apiKey);
|
||||
case 'anthropic':
|
||||
return await testAnthropic(apiKey);
|
||||
case 'azure':
|
||||
return await testAzure(apiKey, endpoint);
|
||||
default:
|
||||
return { success: false, error: 'Provedor não suportado' };
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Erro desconhecido ao testar API'
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const testGemini = async (apiKey: string): Promise<TestResult> => {
|
||||
const { GoogleGenAI } = await import('@google/genai');
|
||||
|
||||
try {
|
||||
const ai = new GoogleGenAI({ apiKey });
|
||||
const response = await ai.models.generateContent({
|
||||
model: 'gemini-2.5-flash',
|
||||
contents: 'Respond with exactly: {"status": "ok"}',
|
||||
config: { temperature: 0 }
|
||||
});
|
||||
|
||||
if (response.text && response.text.includes('ok')) {
|
||||
return {
|
||||
success: true,
|
||||
models: [
|
||||
{ id: 'gemini-2.0-flash', name: 'Gemini 2.0 Flash (Rápido)' },
|
||||
{ id: 'gemini-2.5-flash', name: 'Gemini 2.5 Flash (Equilibrado)' },
|
||||
{ id: 'gemini-2.5-pro', name: 'Gemini 2.5 Pro (Mais potente)' }
|
||||
]
|
||||
};
|
||||
}
|
||||
return { success: false, error: 'Resposta inválida do Gemini' };
|
||||
} catch (error: any) {
|
||||
if (error.message?.includes('API key')) {
|
||||
return { success: false, error: 'Chave de API inválida' };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const testOpenAI = async (apiKey: string): Promise<TestResult> => {
|
||||
try {
|
||||
const response = await fetch('https://api.openai.com/v1/models', {
|
||||
headers: { 'Authorization': `Bearer ${apiKey}` }
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
return { success: false, error: error.error?.message || 'Chave de API inválida' };
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const visionModels = data.data
|
||||
.filter((m: any) => m.id.includes('gpt-4o') || m.id.includes('gpt-4') || m.id.includes('gpt-4-turbo'))
|
||||
.map((m: any) => ({ id: m.id, name: m.id }));
|
||||
|
||||
if (visionModels.length === 0) {
|
||||
return {
|
||||
success: true,
|
||||
models: [{ id: 'gpt-4o', name: 'GPT-4o (Padrão)' }]
|
||||
};
|
||||
}
|
||||
|
||||
return { success: true, models: visionModels };
|
||||
} catch (error: any) {
|
||||
return { success: false, error: error.message || 'Erro ao conectar com OpenAI' };
|
||||
}
|
||||
};
|
||||
|
||||
const testAnthropic = async (apiKey: string): Promise<TestResult> => {
|
||||
try {
|
||||
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'x-api-key': apiKey,
|
||||
'anthropic-version': '2023-06-01',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'claude-3-haiku-20240307',
|
||||
max_tokens: 10,
|
||||
messages: [{ role: 'user', content: 'Hi' }]
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
return { success: false, error: error.error?.message || 'Chave de API inválida' };
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
models: [
|
||||
{ id: 'claude-3-opus-20240229', name: 'Claude 3 Opus (Mais potente)' },
|
||||
{ id: 'claude-3-sonnet-20240229', name: 'Claude 3 Sonnet (Equilibrado)' },
|
||||
{ id: 'claude-3-haiku-20240307', name: 'Claude 3 Haiku (Rápido)' }
|
||||
]
|
||||
};
|
||||
} catch (error: any) {
|
||||
return { success: false, error: error.message || 'Erro ao conectar com Anthropic' };
|
||||
}
|
||||
};
|
||||
|
||||
const testAzure = async (apiKey: string, endpoint: string): Promise<TestResult> => {
|
||||
if (!endpoint) {
|
||||
return { success: false, error: 'Endpoint do Azure é obrigatório' };
|
||||
}
|
||||
|
||||
try {
|
||||
const url = `${endpoint}/openai/deployments?api-version=2024-02-15-preview`;
|
||||
const response = await fetch(url, {
|
||||
headers: { 'api-key': apiKey }
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return { success: false, error: 'Endpoint ou chave inválidos' };
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const deployments = data.data?.map((d: any) => ({
|
||||
id: d.id,
|
||||
name: d.id
|
||||
})) || [];
|
||||
|
||||
if (deployments.length === 0) {
|
||||
return {
|
||||
success: true,
|
||||
models: [{ id: 'gpt-4', name: 'GPT-4 (Padrão)' }]
|
||||
};
|
||||
}
|
||||
|
||||
return { success: true, models: deployments };
|
||||
} catch (error: any) {
|
||||
return { success: false, error: error.message || 'Erro ao conectar com Azure' };
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user