/** * Chat Switchboard - API Module * Handles communication with LLM backends */ const API = { /** * Fetch available models from endpoint */ async fetchModels(endpoint, apiKey) { const response = await fetch(endpoint.replace(/\/$/, '') + '/models', { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`API Error: ${response.status}`); } const data = await response.json(); const models = data.data || data.models || data || []; return Array.isArray(models) ? models.map(m => ({ id: m.id || m.name || m, owned_by: m.owned_by || null })).sort((a, b) => a.id.localeCompare(b.id)) : []; }, /** * Send chat completion request */ async sendMessage(settings, messages, onChunk = null) { const endpoint = settings.apiEndpoint.replace(/\/$/, '') + '/chat/completions'; const controller = new AbortController(); const response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${settings.apiKey}` }, body: JSON.stringify({ model: settings.model, messages: messages.map(m => ({ role: m.role, content: m.content })), max_tokens: settings.maxTokens, temperature: settings.temperature, top_p: settings.topP, presence_penalty: settings.presencePenalty, stream: settings.stream }), signal: controller.signal }); if (!response.ok) { const error = await response.text(); throw new Error(`API Error: ${response.status} - ${error}`); } let content = ''; if (settings.stream && onChunk) { const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { const data = line.slice(6); if (data === '[DONE]') continue; try { const parsed = JSON.parse(data); const delta = parsed.choices?.[0]?.delta?.content || ''; content += delta; onChunk(content, delta); } catch (e) {} } } } } else { const data = await response.json(); content = data.choices?.[0]?.message?.content || 'No response'; } return { content, controller }; } };