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:
Marcos
2026-03-22 15:35:33 -03:00
parent 19ac2b55d4
commit dc1a3f8bad

View File

@@ -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;