feat: Add Orchestrator LLM config to web interface
- Add section to configure Planner (Gemini, OpenAI, Anthropic, Ollama) - Add section to configure Executor (Ollama, Gemini) - Auto-discover Ollama models - Add Orchestrator status display - Add Sync Credentials button - Load models dynamically based on provider selection
This commit is contained in:
@@ -795,6 +795,75 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- ORCHESTRATOR CONFIG -->
|
||||
<div class="section-title">Orquestrador AI (Planner + Executor)</div>
|
||||
<div class="card">
|
||||
<div style="margin-bottom: 1rem; padding: 0.75rem; background: var(--bg-input); border-radius: 8px; font-size: 0.85rem;">
|
||||
<strong>Status:</strong> <span id="orchestrator-status">Carregando...</span>
|
||||
<button type="button" class="btn" style="margin-left: 1rem; padding: 0.4rem 0.75rem; font-size: 0.75rem;" onclick="syncCredentials()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="14" height="14"><path d="M23 4v6h-6M1 20v-6h6"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>
|
||||
Sync Credenciais
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="config-grid">
|
||||
<!-- PLANNER CONFIG -->
|
||||
<div style="padding: 1rem; background: var(--bg-input); border-radius: 8px; border: 1px solid var(--border);">
|
||||
<h4 style="margin-bottom: 0.75rem; color: var(--accent); font-size: 0.9rem;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" style="vertical-align: middle; margin-right: 0.25rem;"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg>
|
||||
Planner (Planejador)
|
||||
</h4>
|
||||
<div class="form-group">
|
||||
<label for="planner_provider">Provider</label>
|
||||
<select id="planner_provider" aria-label="Provider do Planner" class="form-input" onchange="loadPlannerModels()">
|
||||
<option value="gemini">Gemini (Google)</option>
|
||||
<option value="openai">OpenAI (GPT-4)</option>
|
||||
<option value="anthropic">Anthropic (Claude)</option>
|
||||
<option value="ollama">Ollama (Local)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="planner_model">Modelo</label>
|
||||
<select id="planner_model" aria-label="Modelo do Planner" class="form-input">
|
||||
<option value="">Carregando...</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EXECUTOR CONFIG -->
|
||||
<div style="padding: 1rem; background: var(--bg-input); border-radius: 8px; border: 1px solid var(--border);">
|
||||
<h4 style="margin-bottom: 0.75rem; color: var(--success); font-size: 0.9rem;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" style="vertical-align: middle; margin-right: 0.25rem;"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
|
||||
Executor (Executor)
|
||||
</h4>
|
||||
<div class="form-group">
|
||||
<label for="executor_provider">Provider</label>
|
||||
<select id="executor_provider" aria-label="Provider do Executor" class="form-input" onchange="loadExecutorModels()">
|
||||
<option value="ollama" selected>Ollama (Local)</option>
|
||||
<option value="gemini">Gemini (Google)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="executor_model">Modelo</label>
|
||||
<select id="executor_model" aria-label="Modelo do Executor" class="form-input">
|
||||
<option value="">Carregando...</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 1rem;">
|
||||
<button type="button" class="btn btn-primary" onclick="saveLLMConfig()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
|
||||
<polyline points="17 21 17 13 7 13 7 21" />
|
||||
<polyline points="7 3 7 8 15 8" />
|
||||
</svg>
|
||||
Salvar Config LLM
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-title">Terminal & Insights da IA</div>
|
||||
<div class="chat-layout">
|
||||
<!-- Coluna 1: Chat Técnico -->
|
||||
@@ -897,6 +966,10 @@
|
||||
function initDashboard() {
|
||||
fetchStats();
|
||||
loadConfig();
|
||||
loadOrchestratorStatus();
|
||||
loadLLMModels().then(function() {
|
||||
loadLLMConfig();
|
||||
});
|
||||
}
|
||||
|
||||
// Theme management
|
||||
@@ -1111,6 +1184,156 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ================== ORCHESTRATOR LLM CONFIG ==================
|
||||
|
||||
let availableModels = {};
|
||||
|
||||
async function loadOrchestratorStatus() {
|
||||
try {
|
||||
const res = await apiFetch('/api/orchestrator-status');
|
||||
const data = await res.json();
|
||||
|
||||
const statusEl = document.getElementById('orchestrator-status');
|
||||
if (statusEl) {
|
||||
statusEl.innerHTML = '<strong>Planner:</strong> ' + data.planner.name + ' | <strong>Executor:</strong> ' + data.executor.name + ' | <strong>Ferramentas:</strong> ' + data.available_tools;
|
||||
}
|
||||
} catch (e) {
|
||||
const statusEl = document.getElementById('orchestrator-status');
|
||||
if (statusEl) statusEl.textContent = 'Erro ao carregar';
|
||||
}
|
||||
}
|
||||
|
||||
async function loadLLMModels() {
|
||||
try {
|
||||
const res = await apiFetch('/api/llm-models');
|
||||
availableModels = await res.json();
|
||||
} catch (e) {
|
||||
console.error('Erro ao carregar modelos:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadLLMConfig() {
|
||||
try {
|
||||
const res = await apiFetch('/api/llm-config');
|
||||
const data = await res.json();
|
||||
|
||||
// Planner
|
||||
const plannerProvider = document.getElementById('planner_provider');
|
||||
const plannerModel = document.getElementById('planner_model');
|
||||
if (plannerProvider) plannerProvider.value = data.planner.provider;
|
||||
await loadPlannerModels();
|
||||
if (plannerModel && data.planner.model) {
|
||||
plannerModel.value = data.planner.model;
|
||||
}
|
||||
|
||||
// Executor
|
||||
const executorProvider = document.getElementById('executor_provider');
|
||||
const executorModel = document.getElementById('executor_model');
|
||||
if (executorProvider) executorProvider.value = data.executor.provider;
|
||||
await loadExecutorModels();
|
||||
if (executorModel && data.executor.model) {
|
||||
executorModel.value = data.executor.model;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Erro ao carregar config LLM:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function populateModelSelect(selectEl, models) {
|
||||
if (!selectEl) return;
|
||||
selectEl.innerHTML = '';
|
||||
|
||||
if (!models || models.length === 0) {
|
||||
selectEl.innerHTML = '<option value="">Nenhum modelo</option>';
|
||||
return;
|
||||
}
|
||||
|
||||
models.forEach(function(model) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = model;
|
||||
opt.textContent = model;
|
||||
selectEl.appendChild(opt);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadPlannerModels() {
|
||||
const provider = document.getElementById('planner_provider')?.value;
|
||||
const modelSelect = document.getElementById('planner_model');
|
||||
if (!modelSelect) return;
|
||||
|
||||
modelSelect.innerHTML = '<option value="">Carregando...</option>';
|
||||
|
||||
if (provider === 'ollama') {
|
||||
const models = availableModels.models?.find(function(p) { return p.provider === 'ollama'; })?.models || [];
|
||||
populateModelSelect(modelSelect, models.length > 0 ? models : ['qwen2.5-coder:1.5b', 'llama3.1:8b', 'codellama:13b']);
|
||||
} else {
|
||||
const fixedModels = {
|
||||
gemini: ['gemini-2.5-flash', 'gemini-2.0-pro', 'gemini-1.5-flash'],
|
||||
openai: ['gpt-4o', 'gpt-4-turbo', 'gpt-3.5-turbo'],
|
||||
anthropic: ['claude-3-5-sonnet-20241022', 'claude-3-5-haiku-20241022', 'claude-3-opus-20240229']
|
||||
};
|
||||
populateModelSelect(modelSelect, fixedModels[provider] || []);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadExecutorModels() {
|
||||
const provider = document.getElementById('executor_provider')?.value;
|
||||
const modelSelect = document.getElementById('executor_model');
|
||||
if (!modelSelect) return;
|
||||
|
||||
modelSelect.innerHTML = '<option value="">Carregando...</option>';
|
||||
|
||||
if (provider === 'ollama') {
|
||||
const models = availableModels.models?.find(function(p) { return p.provider === 'ollama'; })?.models || [];
|
||||
populateModelSelect(modelSelect, models.length > 0 ? models : ['qwen2.5-coder:1.5b', 'llama3.1:8b', 'codellama:13b']);
|
||||
} else if (provider === 'gemini') {
|
||||
populateModelSelect(modelSelect, ['gemini-2.5-flash', 'gemini-2.0-pro']);
|
||||
} else {
|
||||
populateModelSelect(modelSelect, []);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveLLMConfig() {
|
||||
const plannerProvider = document.getElementById('planner_provider')?.value;
|
||||
const plannerModel = document.getElementById('planner_model')?.value;
|
||||
const executorProvider = document.getElementById('executor_provider')?.value;
|
||||
const executorModel = document.getElementById('executor_model')?.value;
|
||||
|
||||
try {
|
||||
const res = await apiFetch('/api/llm-config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
planner_provider: plannerProvider,
|
||||
planner_model: plannerModel,
|
||||
executor_provider: executorProvider,
|
||||
executor_model: executorModel
|
||||
})
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
showToast('Config LLM salva! Planner: ' + plannerProvider + ', Executor: ' + executorProvider);
|
||||
loadOrchestratorStatus();
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
} catch (e) {
|
||||
showToast('Erro ao salvar config LLM.', true);
|
||||
}
|
||||
}
|
||||
|
||||
async function syncCredentials() {
|
||||
try {
|
||||
const res = await apiFetch('/api/sync-credentials', {
|
||||
method: 'POST'
|
||||
});
|
||||
const data = await res.json();
|
||||
showToast('Credenciais sincronizadas! Services: ' + Object.keys(data.services || {}).length);
|
||||
} catch (e) {
|
||||
showToast('Erro ao sincronizar.', true);
|
||||
}
|
||||
}
|
||||
|
||||
async function testLLMSpeed() {
|
||||
const btn = document.getElementById('btn-test-llm');
|
||||
const originalContent = btn.innerHTML;
|
||||
|
||||
Reference in New Issue
Block a user