diff --git a/frontend/index.html b/frontend/index.html
index 749e6b1..98fff9e 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -347,13 +347,14 @@ ${!S.ociCfg.length?'
-`}
+`}
async function sOci(){const fd=new FormData();fd.append('tenancy_name',document.getElementById('otn').value);fd.append('tenancy_ocid',document.getElementById('oto').value);
fd.append('user_ocid',document.getElementById('ouo').value);fd.append('fingerprint',document.getElementById('ofp').value);
fd.append('region',document.getElementById('org').value);fd.append('compartment_id',document.getElementById('ocp').value);
const sk=document.getElementById('osk').files[0];if(!sk)return sm('om','Selecione a chave privada','e');fd.append('private_key',sk);
const pk=document.getElementById('opk').files[0];if(pk)fd.append('public_key',pk);
- try{await $api('/oci/config',{method:'POST',body:fd,headers:{}});S.ociCfg=await $api('/oci/configs');sm('om','✅ Credencial salva com sucesso!','s');R()}catch(e){sm('om',e.message,'e')}}
+ const btn=document.getElementById('obtn');btn.disabled=true;btn.textContent='Salvando...';sm('om',' Salvando credencial...','i');
+ try{await $api('/oci/config',{method:'POST',body:fd,headers:{}});S.ociCfg=await $api('/oci/configs');sm('om','✅ Credencial salva com sucesso!','s');R()}catch(e){sm('om',e.message,'e');btn.disabled=false;btn.textContent='Salvar Credencial'}}
async function tOci(id){try{const d=await $api('/oci/test/'+id,{method:'POST'});alert(d.status==='success'?'✅ Conexão OK!':'❌ '+d.message)}catch(e){alert(e.message)}}
async function dOci(id){if(!confirm('Excluir credencial?'))return;await $api('/oci/configs/'+id,{method:'DELETE'});S.ociCfg=await $api('/oci/configs');R()}
@@ -384,8 +385,8 @@ ${S.ociCfg.map(c=>``).join('')
-`}
-async function sGenai(){try{await $api('/genai/config',{method:'POST',body:{
+`}
+async function sGenai(){const btn=document.getElementById('gbtn');btn.disabled=true;btn.textContent='Salvando...';sm('gm',' Salvando modelo...','i');try{await $api('/genai/config',{method:'POST',body:{
name:document.getElementById('gname').value||'default',
oci_config_id:document.getElementById('goci').value,model_id:document.getElementById('gmod').value,
model_ocid:document.getElementById('gocid').value||null,
@@ -395,12 +396,12 @@ async function sGenai(){try{await $api('/genai/config',{method:'POST',body:{
top_p:parseFloat(document.getElementById('gtp').value),top_k:parseInt(document.getElementById('gtk').value),
frequency_penalty:parseFloat(document.getElementById('gfp').value),presence_penalty:parseFloat(document.getElementById('gpp').value),
is_default:S.genaiCfg.length===0}});
- S.genaiCfg=await $api('/genai/configs');sm('gm','✅ Modelo salvo com sucesso!','s');R()}catch(e){sm('gm',e.message,'e')}}
+ S.genaiCfg=await $api('/genai/configs');sm('gm','✅ Modelo salvo com sucesso!','s');R()}catch(e){sm('gm',e.message,'e');btn.disabled=false;btn.textContent='Salvar Modelo'}}
async function tGenai(id){try{const d=await $api('/genai/test/'+id,{method:'POST'});alert(d.status==='success'?'✅ '+d.message+'\n'+d.response:'❌ '+d.message)}catch(e){alert(e.message)}}
async function dGenai(id){if(!confirm('Excluir modelo?'))return;await $api('/genai/configs/'+id,{method:'DELETE'});S.genaiCfg=await $api('/genai/configs');R()}
/* ── MCP Servers ── */
-function mcpTypeFields(){const t=document.getElementById('mt')?.value;
+function mcpTypeFields(){const t=document.querySelector('input[name="mt"]:checked')?.value;
document.querySelectorAll('.mcp-f').forEach(el=>{el.style.display='none'});
if(t==='stdio'){document.getElementById('mf-cmd').style.display='';document.getElementById('mf-args').style.display=''}
if(t==='sse')document.getElementById('mf-url').style.display='';
@@ -421,12 +422,13 @@ ${m.command?`Comando:
${m.url?`
URL: ${m.url}
`:''}
${m.module_path?`
Módulo: ${m.module_path}
`:''}
ADB: ${adb?`${adb.config_name}`:'Nenhum'}
+
-
+
-
`}).join('')
+`}).join('')
:'Nenhum MCP server registrado.
'}
➕ Registrar Novo Server
Configure um novo servidor MCP. Os campos se ajustam ao tipo selecionado.
@@ -448,19 +450,21 @@ ${m.module_path?`
Módulo:
Opcional — o MCP server poderá usar este banco como ferramenta.
-`}
-async function sMcp(){try{let args=null;const a=document.getElementById('margs')?.value;if(a)try{args=JSON.parse(a)}catch(e){}
+`}
+async function sMcp(){const btn=document.getElementById('mcbtn');btn.disabled=true;btn.textContent='Registrando...';sm('mcm',' Registrando servidor...','i');try{let args=null;const a=document.getElementById('margs')?.value;if(a)try{args=JSON.parse(a)}catch(e){}
const mt=document.querySelector('input[name="mt"]:checked')?.value||'stdio';
await $api('/mcp/servers',{method:'POST',body:{name:document.getElementById('mn').value,description:document.getElementById('md').value,
server_type:mt,command:document.getElementById('mcmd')?.value||null,url:document.getElementById('murl')?.value||null,args,
linked_adb_id:document.getElementById('madb').value||null}});
- S.mcpSvr=await $api('/mcp/servers');sm('mcm','✅ Registrado!','s');R()}catch(e){sm('mcm',e.message,'e')}}
+ S.mcpSvr=await $api('/mcp/servers');sm('mcm','✅ Registrado!','s');R()}catch(e){sm('mcm',e.message,'e');btn.disabled=false;btn.textContent='Registrar Server'}}
async function tgMcp(id){await $api('/mcp/servers/'+id+'/toggle',{method:'PUT'});S.mcpSvr=await $api('/mcp/servers');R()}
async function dMcp(id){if(!confirm('Excluir server?'))return;await $api('/mcp/servers/'+id,{method:'DELETE'});S.mcpSvr=await $api('/mcp/servers');R()}
-async function uMcp(id){const fd=new FormData();const f=document.getElementById('muf_'+id)?.files[0];if(!f)return alert('Selecione um arquivo .py');
- fd.append('file',f);try{await $api('/mcp/servers/'+id+'/upload',{method:'POST',body:fd,headers:{}});alert('Upload realizado!')}catch(e){alert(e.message)}}
+async function uMcp(id){const fd=new FormData();const f=document.getElementById('muf_'+id)?.files[0];if(!f)return sm('mcpm_'+id,'Selecione um arquivo .py','e');
+ fd.append('file',f);const btn=document.getElementById('mub_'+id);btn.disabled=true;btn.textContent='Enviando...';sm('mcpm_'+id,' Enviando arquivo...','i');
+ try{await $api('/mcp/servers/'+id+'/upload',{method:'POST',body:fd,headers:{}});S.mcpSvr=await $api('/mcp/servers');sm('mcpm_'+id,'✅ Upload realizado!','s');R()}catch(e){sm('mcpm_'+id,e.message,'e');btn.disabled=false;btn.textContent='Upload .py'}}
async function lnkMcp(id){const aid=document.getElementById('mlnk_'+id)?.value||'';
- try{await $api('/mcp/servers/'+id+'/link-adb?adb_id='+aid,{method:'PUT'});S.mcpSvr=await $api('/mcp/servers');alert('Vinculado!');R()}catch(e){alert(e.message)}}
+ const btn=document.getElementById('mlb_'+id);btn.disabled=true;btn.textContent='Vinculando...';
+ try{await $api('/mcp/servers/'+id+'/link-adb?adb_id='+aid,{method:'PUT'});S.mcpSvr=await $api('/mcp/servers');sm('mcpm_'+id,'✅ '+(aid?'Vinculado ao ADB!':'ADB desvinculado!'),'s');R()}catch(e){sm('mcpm_'+id,e.message,'e');btn.disabled=false;btn.textContent='Vincular ADB'}}
/* ── ADB Vector ── */
function rADB(){return`🗄️ Conexões Registradas
@@ -483,22 +487,23 @@ ${!S.adbCfg.length?'
Nenhuma conexão
Modelo OCI GenAI para gerar vetores.
-
-📤 Upload Wallet
+
+📤 Upload Wallet
-
`}
-async function sAdb(){try{await $api('/adb/config',{method:'POST',body:{config_name:document.getElementById('an').value,dsn:document.getElementById('adsn').value,
+`}
+async function sAdb(){const btn=document.getElementById('abtn');btn.disabled=true;btn.textContent='Salvando...';sm('am',' Salvando conexão...','i');try{await $api('/adb/config',{method:'POST',body:{config_name:document.getElementById('an').value,dsn:document.getElementById('adsn').value,
username:document.getElementById('auser').value,password:document.getElementById('apw').value,
wallet_password:document.getElementById('awpw').value||null,table_name:document.getElementById('atbl').value,
genai_config_id:document.getElementById('agcfg').value||null,
embedding_model_id:document.getElementById('aemb').value||'cohere.embed-multilingual-v3.0'}});
- S.adbCfg=await $api('/adb/configs');sm('am','✅ Conexão salva!','s');R()}catch(e){sm('am',e.message,'e')}}
+ S.adbCfg=await $api('/adb/configs');sm('am','✅ Conexão salva!','s');R()}catch(e){sm('am',e.message,'e');btn.disabled=false;btn.textContent='Salvar Conexão'}}
async function tAdb(id){try{const d=await $api('/adb/test/'+id,{method:'POST'});alert(d.status==='success'?'✅ '+d.message:'❌ '+d.message)}catch(e){alert(e.message)}}
async function dAdb(id){if(!confirm('Excluir conexão?'))return;await $api('/adb/configs/'+id,{method:'DELETE'});S.adbCfg=await $api('/adb/configs');R()}
-async function uWallet(){const fd=new FormData();const f=document.getElementById('awf').files[0];if(!f)return alert('Selecione wallet ZIP');
- fd.append('wallet',f);const id=document.getElementById('awsel').value;if(!id)return alert('Selecione conexão');
- try{const d=await $api('/adb/'+id+'/upload-wallet',{method:'POST',body:fd,headers:{}});alert('Wallet enviada! Arquivos: '+d.files.join(', '))}catch(e){alert(e.message)}}
+async function uWallet(){const fd=new FormData();const f=document.getElementById('awf').files[0];if(!f)return sm('wm','Selecione um arquivo wallet ZIP.','e');
+ fd.append('wallet',f);const id=document.getElementById('awsel').value;if(!id)return sm('wm','Selecione uma conexão ADB.','e');
+ const btn=document.getElementById('wbtn');btn.disabled=true;btn.textContent='Enviando...';sm('wm',' Enviando wallet...','i');
+ try{const d=await $api('/adb/'+id+'/upload-wallet',{method:'POST',body:fd,headers:{}});S.adbCfg=await $api('/adb/configs');sm('wm','✅ Wallet enviada! Arquivos: '+d.files.join(', '),'s');R()}catch(e){sm('wm',e.message,'e');btn.disabled=false;btn.textContent='Upload Wallet'}}
async function ensureTable(id){try{const d=await $api('/adb/'+id+'/ensure-table',{method:'POST'});alert('Tabela criada/verificada: '+d.table)}catch(e){alert('Erro: '+e.message)}}
/* ── Embeddings ── */
@@ -515,12 +520,12 @@ function rEmbeddings(){
Gere embeddings a partir de relatórios CIS. Cada seção (IAM, Networking, etc.) gera um embedding separado.
-
+
📄 Upload de Arquivo
Faça upload de um arquivo .txt para gerar embeddings automaticamente (chunking por parágrafos).
-
`}
+`}
async function loadEmbs(){const vid=document.getElementById('elsel').value;if(!vid)return;
try{const d=await $api('/embeddings/'+vid+'/list');
@@ -533,13 +538,15 @@ async function loadEmbs(){const vid=document.getElementById('elsel').value;if(!v
catch(e){document.getElementById('emb-list').innerHTML='Erro: '+e.message+'
'}}
async function embedReport(){const vid=document.getElementById('ersel').value;const rid=document.getElementById('errpt').value;
- if(!vid||!rid)return alert('Selecione conexão ADB e relatório');
- try{const d=await $api('/embeddings/report/'+rid,{method:'POST',body:{adb_config_id:vid}});sm('erm','✅ '+d.message,'s')}catch(e){sm('erm',e.message,'e')}}
+ if(!vid||!rid)return sm('erm','Selecione conexão ADB e relatório.','e');
+ const btn=document.getElementById('erbtn');btn.disabled=true;btn.textContent='Gerando...';sm('erm',' Gerando embeddings do relatório...','i');
+ try{const d=await $api('/embeddings/report/'+rid,{method:'POST',body:{adb_config_id:vid}});sm('erm','✅ '+d.message,'s');btn.disabled=false;btn.textContent='Gerar Embeddings do Relatório'}catch(e){sm('erm',e.message,'e');btn.disabled=false;btn.textContent='Gerar Embeddings do Relatório'}}
async function embedFile(){const vid=document.getElementById('efsel').value;const f=document.getElementById('eff').files[0];
- if(!vid||!f)return alert('Selecione conexão ADB e arquivo');
+ if(!vid||!f)return sm('efm','Selecione conexão ADB e arquivo.','e');
const fd=new FormData();fd.append('file',f);fd.append('adb_config_id',vid);
- try{const d=await $api('/embeddings/upload',{method:'POST',body:fd,headers:{}});sm('efm','✅ '+d.message,'s')}catch(e){sm('efm',e.message,'e')}}
+ const btn=document.getElementById('efbtn');btn.disabled=true;btn.textContent='Enviando...';sm('efm',' Enviando e gerando embeddings...','i');
+ try{const d=await $api('/embeddings/upload',{method:'POST',body:fd,headers:{}});sm('efm','✅ '+d.message,'s');btn.disabled=false;btn.textContent='Upload e Gerar Embeddings'}catch(e){sm('efm',e.message,'e');btn.disabled=false;btn.textContent='Upload e Gerar Embeddings'}}
async function delEmb(vid,docId){if(!confirm('Excluir este embedding?'))return;
try{await $api('/embeddings/'+vid+'/'+docId,{method:'DELETE'});loadEmbs()}catch(e){alert('Erro: '+e.message)}}