|
|
|
|
@@ -112,19 +112,23 @@ const AgentsUI = {
|
|
|
|
|
<i data-lucide="play"></i>
|
|
|
|
|
Executar
|
|
|
|
|
</button>
|
|
|
|
|
<button class="btn btn-ghost btn-sm" data-action="edit" data-id="${agent.id}">
|
|
|
|
|
<i data-lucide="pencil"></i>
|
|
|
|
|
Editar
|
|
|
|
|
</button>
|
|
|
|
|
<button class="btn btn-ghost btn-icon btn-sm" data-action="duplicate" data-id="${agent.id}" title="Duplicar agente">
|
|
|
|
|
<i data-lucide="copy"></i>
|
|
|
|
|
</button>
|
|
|
|
|
<button class="btn btn-ghost btn-icon btn-sm" data-action="export" data-id="${agent.id}" title="Exportar agente">
|
|
|
|
|
<i data-lucide="download"></i>
|
|
|
|
|
</button>
|
|
|
|
|
<button class="btn btn-ghost btn-icon btn-sm btn-danger" data-action="delete" data-id="${agent.id}" title="Excluir agente">
|
|
|
|
|
<i data-lucide="trash-2"></i>
|
|
|
|
|
</button>
|
|
|
|
|
<div class="agent-actions-icons">
|
|
|
|
|
<button class="btn btn-ghost btn-icon btn-sm" data-action="edit" data-id="${agent.id}" title="Editar agente">
|
|
|
|
|
<i data-lucide="pencil"></i>
|
|
|
|
|
</button>
|
|
|
|
|
<button class="btn btn-ghost btn-icon btn-sm" data-action="duplicate" data-id="${agent.id}" title="Duplicar agente">
|
|
|
|
|
<i data-lucide="copy"></i>
|
|
|
|
|
</button>
|
|
|
|
|
<button class="btn btn-ghost btn-icon btn-sm" data-action="export" data-id="${agent.id}" title="Exportar agente">
|
|
|
|
|
<i data-lucide="download"></i>
|
|
|
|
|
</button>
|
|
|
|
|
<button class="btn btn-ghost btn-icon btn-sm" data-action="versions" data-id="${agent.id}" title="Histórico de versões">
|
|
|
|
|
<i data-lucide="history"></i>
|
|
|
|
|
</button>
|
|
|
|
|
<button class="btn btn-ghost btn-icon btn-sm btn-danger" data-action="delete" data-id="${agent.id}" title="Excluir agente">
|
|
|
|
|
<i data-lucide="trash-2"></i>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
@@ -158,7 +162,23 @@ const AgentsUI = {
|
|
|
|
|
const permissionMode = document.getElementById('agent-permission-mode');
|
|
|
|
|
if (permissionMode) permissionMode.value = '';
|
|
|
|
|
|
|
|
|
|
const retryToggle = document.getElementById('agent-retry-toggle');
|
|
|
|
|
if (retryToggle) retryToggle.checked = false;
|
|
|
|
|
|
|
|
|
|
const retryMaxGroup = document.getElementById('agent-retry-max-group');
|
|
|
|
|
if (retryMaxGroup) retryMaxGroup.style.display = 'none';
|
|
|
|
|
|
|
|
|
|
const retryMax = document.getElementById('agent-retry-max');
|
|
|
|
|
if (retryMax) retryMax.value = '3';
|
|
|
|
|
|
|
|
|
|
const secretsSection = document.getElementById('agent-secrets-section');
|
|
|
|
|
if (secretsSection) secretsSection.hidden = true;
|
|
|
|
|
|
|
|
|
|
const secretsList = document.getElementById('agent-secrets-list');
|
|
|
|
|
if (secretsList) secretsList.innerHTML = '';
|
|
|
|
|
|
|
|
|
|
Modal.open('agent-modal-overlay');
|
|
|
|
|
AgentsUI._setupModalListeners();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async openEditModal(agentId) {
|
|
|
|
|
@@ -199,7 +219,23 @@ const AgentsUI = {
|
|
|
|
|
).join('');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const retryToggle = document.getElementById('agent-retry-toggle');
|
|
|
|
|
const retryOnFailure = agent.config && agent.config.retryOnFailure;
|
|
|
|
|
if (retryToggle) retryToggle.checked = !!retryOnFailure;
|
|
|
|
|
|
|
|
|
|
const retryMaxGroup = document.getElementById('agent-retry-max-group');
|
|
|
|
|
if (retryMaxGroup) retryMaxGroup.style.display = retryOnFailure ? '' : 'none';
|
|
|
|
|
|
|
|
|
|
const retryMax = document.getElementById('agent-retry-max');
|
|
|
|
|
if (retryMax) retryMax.value = (agent.config && agent.config.maxRetries) || '3';
|
|
|
|
|
|
|
|
|
|
const secretsSection = document.getElementById('agent-secrets-section');
|
|
|
|
|
if (secretsSection) secretsSection.hidden = false;
|
|
|
|
|
|
|
|
|
|
AgentsUI._loadSecrets(agent.id);
|
|
|
|
|
|
|
|
|
|
Modal.open('agent-modal-overlay');
|
|
|
|
|
AgentsUI._setupModalListeners();
|
|
|
|
|
} catch (err) {
|
|
|
|
|
Toast.error(`Erro ao carregar agente: ${err.message}`);
|
|
|
|
|
}
|
|
|
|
|
@@ -237,6 +273,8 @@ const AgentsUI = {
|
|
|
|
|
allowedTools: document.getElementById('agent-allowed-tools')?.value.trim() || '',
|
|
|
|
|
maxTurns: parseInt(document.getElementById('agent-max-turns')?.value) || 0,
|
|
|
|
|
permissionMode: document.getElementById('agent-permission-mode')?.value || '',
|
|
|
|
|
retryOnFailure: !!document.getElementById('agent-retry-toggle')?.checked,
|
|
|
|
|
maxRetries: parseInt(document.getElementById('agent-retry-max')?.value) || 3,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@@ -406,6 +444,223 @@ const AgentsUI = {
|
|
|
|
|
minute: '2-digit',
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_setupModalListeners() {
|
|
|
|
|
const retryToggle = document.getElementById('agent-retry-toggle');
|
|
|
|
|
const retryMaxGroup = document.getElementById('agent-retry-max-group');
|
|
|
|
|
|
|
|
|
|
if (retryToggle && !retryToggle._listenerAdded) {
|
|
|
|
|
retryToggle._listenerAdded = true;
|
|
|
|
|
retryToggle.addEventListener('change', () => {
|
|
|
|
|
if (retryMaxGroup) retryMaxGroup.style.display = retryToggle.checked ? '' : 'none';
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const addSecretBtn = document.getElementById('agent-secret-add-btn');
|
|
|
|
|
if (addSecretBtn && !addSecretBtn._listenerAdded) {
|
|
|
|
|
addSecretBtn._listenerAdded = true;
|
|
|
|
|
addSecretBtn.addEventListener('click', () => {
|
|
|
|
|
const agentId = document.getElementById('agent-form-id')?.value;
|
|
|
|
|
if (agentId) {
|
|
|
|
|
AgentsUI._addSecret(agentId);
|
|
|
|
|
} else {
|
|
|
|
|
Toast.warning('Salve o agente primeiro para adicionar secrets');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async _loadSecrets(agentId) {
|
|
|
|
|
const list = document.getElementById('agent-secrets-list');
|
|
|
|
|
if (!list) return;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const secrets = await API.secrets.list(agentId);
|
|
|
|
|
const items = Array.isArray(secrets) ? secrets : (secrets?.secrets || []);
|
|
|
|
|
|
|
|
|
|
if (items.length === 0) {
|
|
|
|
|
list.innerHTML = '<p class="text-muted text-sm">Nenhum secret configurado.</p>';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
list.innerHTML = items.map(s => `
|
|
|
|
|
<div class="secret-item">
|
|
|
|
|
<span class="secret-name font-mono">${Utils.escapeHtml(s.name || s)}</span>
|
|
|
|
|
<span class="secret-value-placeholder">••••••••</span>
|
|
|
|
|
<button type="button" class="btn btn-ghost btn-icon btn-sm btn-danger" data-secret-delete="${Utils.escapeHtml(s.name || s)}" data-agent-id="${agentId}" title="Remover secret">
|
|
|
|
|
<i data-lucide="trash-2"></i>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
`).join('');
|
|
|
|
|
|
|
|
|
|
Utils.refreshIcons(list);
|
|
|
|
|
|
|
|
|
|
list.querySelectorAll('[data-secret-delete]').forEach(btn => {
|
|
|
|
|
btn.addEventListener('click', () => {
|
|
|
|
|
AgentsUI._deleteSecret(btn.dataset.agentId, btn.dataset.secretDelete);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
} catch {
|
|
|
|
|
list.innerHTML = '<p class="text-muted text-sm">Erro ao carregar secrets.</p>';
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async _addSecret(agentId) {
|
|
|
|
|
const nameEl = document.getElementById('agent-secret-name');
|
|
|
|
|
const valueEl = document.getElementById('agent-secret-value');
|
|
|
|
|
const name = nameEl?.value.trim();
|
|
|
|
|
const value = valueEl?.value;
|
|
|
|
|
|
|
|
|
|
if (!name) {
|
|
|
|
|
Toast.warning('Nome do secret é obrigatório');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!value) {
|
|
|
|
|
Toast.warning('Valor do secret é obrigatório');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await API.secrets.create(agentId, { name, value });
|
|
|
|
|
Toast.success(`Secret "${name}" salvo`);
|
|
|
|
|
if (nameEl) nameEl.value = '';
|
|
|
|
|
if (valueEl) valueEl.value = '';
|
|
|
|
|
AgentsUI._loadSecrets(agentId);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
Toast.error(`Erro ao salvar secret: ${err.message}`);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async _deleteSecret(agentId, secretName) {
|
|
|
|
|
const confirmed = await Modal.confirm(
|
|
|
|
|
'Remover secret',
|
|
|
|
|
`Tem certeza que deseja remover o secret "${secretName}"?`
|
|
|
|
|
);
|
|
|
|
|
if (!confirmed) return;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await API.secrets.delete(agentId, secretName);
|
|
|
|
|
Toast.success(`Secret "${secretName}" removido`);
|
|
|
|
|
AgentsUI._loadSecrets(agentId);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
Toast.error(`Erro ao remover secret: ${err.message}`);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async openVersionsModal(agentId) {
|
|
|
|
|
const agent = AgentsUI.agents.find(a => a.id === agentId);
|
|
|
|
|
const titleEl = document.getElementById('agent-versions-title');
|
|
|
|
|
const contentEl = document.getElementById('agent-versions-content');
|
|
|
|
|
|
|
|
|
|
if (titleEl) titleEl.textContent = `Versões — ${agent?.agent_name || agent?.name || 'Agente'}`;
|
|
|
|
|
|
|
|
|
|
if (contentEl) {
|
|
|
|
|
contentEl.innerHTML = '<div class="flex flex-center gap-8"><div class="spinner"></div><span class="text-secondary">Carregando versões...</span></div>';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Modal.open('agent-versions-modal-overlay');
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const versions = await API.versions.list(agentId);
|
|
|
|
|
const items = Array.isArray(versions) ? versions : (versions?.versions || []);
|
|
|
|
|
|
|
|
|
|
if (!contentEl) return;
|
|
|
|
|
|
|
|
|
|
if (items.length === 0) {
|
|
|
|
|
contentEl.innerHTML = `
|
|
|
|
|
<div class="empty-state">
|
|
|
|
|
<div class="empty-state-icon"><i data-lucide="history"></i></div>
|
|
|
|
|
<h3 class="empty-state-title">Sem histórico de versões</h3>
|
|
|
|
|
<p class="empty-state-desc">As alterações neste agente serão registradas aqui automaticamente.</p>
|
|
|
|
|
</div>`;
|
|
|
|
|
Utils.refreshIcons(contentEl);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
contentEl.innerHTML = `
|
|
|
|
|
<div class="versions-timeline">
|
|
|
|
|
${items.map((v, i) => {
|
|
|
|
|
const date = v.changedAt ? new Date(v.changedAt).toLocaleString('pt-BR') : '—';
|
|
|
|
|
const changedFields = AgentsUI._getChangedFields(v);
|
|
|
|
|
const isLatest = i === 0;
|
|
|
|
|
|
|
|
|
|
return `
|
|
|
|
|
<div class="version-item ${isLatest ? 'version-item--latest' : ''}">
|
|
|
|
|
<div class="version-node">
|
|
|
|
|
<div class="version-dot ${isLatest ? 'version-dot--active' : ''}"></div>
|
|
|
|
|
${i < items.length - 1 ? '<div class="version-line"></div>' : ''}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="version-content">
|
|
|
|
|
<div class="version-header">
|
|
|
|
|
<span class="version-number">v${v.version || items.length - i}</span>
|
|
|
|
|
<span class="version-date">${date}</span>
|
|
|
|
|
${!isLatest ? `<button class="btn btn-ghost btn-sm" data-restore-version="${v.version || items.length - i}" data-agent-id="${agentId}" type="button">
|
|
|
|
|
<i data-lucide="undo-2"></i> Restaurar
|
|
|
|
|
</button>` : '<span class="badge badge-active">Atual</span>'}
|
|
|
|
|
</div>
|
|
|
|
|
${changedFields ? `<div class="version-changes">${changedFields}</div>` : ''}
|
|
|
|
|
${v.changelog ? `<p class="version-changelog">${Utils.escapeHtml(v.changelog)}</p>` : ''}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
}).join('')}
|
|
|
|
|
</div>`;
|
|
|
|
|
|
|
|
|
|
Utils.refreshIcons(contentEl);
|
|
|
|
|
|
|
|
|
|
contentEl.querySelectorAll('[data-restore-version]').forEach(btn => {
|
|
|
|
|
btn.addEventListener('click', async () => {
|
|
|
|
|
const version = btn.dataset.restoreVersion;
|
|
|
|
|
const aid = btn.dataset.agentId;
|
|
|
|
|
const confirmed = await Modal.confirm(
|
|
|
|
|
'Restaurar versão',
|
|
|
|
|
`Deseja restaurar a versão v${version} deste agente? A configuração atual será substituída.`
|
|
|
|
|
);
|
|
|
|
|
if (!confirmed) return;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await API.versions.restore(aid, version);
|
|
|
|
|
Toast.success(`Versão v${version} restaurada`);
|
|
|
|
|
Modal.close('agent-versions-modal-overlay');
|
|
|
|
|
await AgentsUI.load();
|
|
|
|
|
} catch (err) {
|
|
|
|
|
Toast.error(`Erro ao restaurar versão: ${err.message}`);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
} catch (err) {
|
|
|
|
|
if (contentEl) {
|
|
|
|
|
contentEl.innerHTML = `
|
|
|
|
|
<div class="empty-state">
|
|
|
|
|
<div class="empty-state-icon"><i data-lucide="alert-circle"></i></div>
|
|
|
|
|
<h3 class="empty-state-title">Erro ao carregar versões</h3>
|
|
|
|
|
<p class="empty-state-desc">${Utils.escapeHtml(err.message)}</p>
|
|
|
|
|
</div>`;
|
|
|
|
|
Utils.refreshIcons(contentEl);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_getChangedFields(version) {
|
|
|
|
|
if (!version.config) return '';
|
|
|
|
|
const fieldLabels = {
|
|
|
|
|
systemPrompt: 'System Prompt',
|
|
|
|
|
model: 'Modelo',
|
|
|
|
|
workingDirectory: 'Diretório',
|
|
|
|
|
allowedTools: 'Ferramentas',
|
|
|
|
|
maxTurns: 'Max Turns',
|
|
|
|
|
permissionMode: 'Permission Mode',
|
|
|
|
|
retryOnFailure: 'Retry',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const fields = Object.keys(version.config || {}).filter(k => fieldLabels[k]);
|
|
|
|
|
if (fields.length === 0) return '';
|
|
|
|
|
|
|
|
|
|
return fields.map(f =>
|
|
|
|
|
`<span class="version-field-badge">${fieldLabels[f] || f}</span>`
|
|
|
|
|
).join('');
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
window.AgentsUI = AgentsUI;
|
|
|
|
|
|