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>
|
</button>
|
||||||
</div>
|
</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="section-title">Terminal & Insights da IA</div>
|
||||||
<div class="chat-layout">
|
<div class="chat-layout">
|
||||||
<!-- Coluna 1: Chat Técnico -->
|
<!-- Coluna 1: Chat Técnico -->
|
||||||
@@ -897,6 +966,10 @@
|
|||||||
function initDashboard() {
|
function initDashboard() {
|
||||||
fetchStats();
|
fetchStats();
|
||||||
loadConfig();
|
loadConfig();
|
||||||
|
loadOrchestratorStatus();
|
||||||
|
loadLLMModels().then(function() {
|
||||||
|
loadLLMConfig();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Theme management
|
// 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() {
|
async function testLLMSpeed() {
|
||||||
const btn = document.getElementById('btn-test-llm');
|
const btn = document.getElementById('btn-test-llm');
|
||||||
const originalContent = btn.innerHTML;
|
const originalContent = btn.innerHTML;
|
||||||
|
|||||||
Reference in New Issue
Block a user