feat: consult embeddings UI, vector search FLOAT32 fix, and base64 compartment decode
- Add Consult Embeddings sub-menu with chat-like Q&A interface - Fix VECTOR_DISTANCE FLOAT32/FLOAT64 mismatch (array 'd' → 'f') - Decode base64 compartment_id in _resolve_embed_config - Sidebar sub-item navigation for Embeddings hierarchy - Fallback to raw document display when no GenAI config available
This commit is contained in:
@@ -613,7 +613,7 @@ function rApp(){return`<div class="app">${rSb()}<div class="mc">${rTb()}<div cla
|
||||
const TF_IC='<svg viewBox="0 0 16 16" width="14" height="14" style="vertical-align:-2px;margin-right:2px"><path d="M5.6 1v4.2l3.6 2.1V3.1L5.6 1zm4.4 2.1v4.2l3.6-2.1V1L10 3.1zM1.4 3.5v4.2l3.6 2.1V5.6L1.4 3.5zM5.6 8.1v4.2L9.2 14.4V10.2L5.6 8.1z" fill="#7b42bc"/></svg>';
|
||||
function rSb(){
|
||||
const tabs=[['chat',IC.chat,'Chat Agent'],['terraform',TF_IC,'Terraform'],['explorer',IC.search,'OCI Explorer'],['report',IC.chart,'Reports'],['downloads',IC.folder,'Downloads']];
|
||||
const ctabs=[['oci-config',IC.cloud,'Credenciais OCI'],['genai',IC.brain,'GenAI Config'],['mcp',IC.plug,'MCP Servers'],['adb',IC.db,'ADB Vector'],['embeddings',IC.dna,'Embeddings']];
|
||||
const ctabs=[['oci-config',IC.cloud,'Credenciais OCI'],['genai',IC.brain,'GenAI Config'],['mcp',IC.plug,'MCP Servers'],['adb',IC.db,'ADB Vector'],['embeddings',IC.dna,'Embeddings'],['emb-consult',IC.search,'Consultar Embeddings','sub']];
|
||||
const atabs=[['users',IC.users,'Usuários'],['mfa',IC.lock,'MFA'],['audit',IC.log,'Audit Log']];
|
||||
const i=(S.user?.first_name||S.user?.username||'?')[0].toUpperCase();
|
||||
return`<div class="sb">
|
||||
@@ -621,17 +621,17 @@ function rSb(){
|
||||
<div class="nav"><div class="nl">Principal</div>
|
||||
${tabs.map(t=>`<div class="ni ${S.tab===t[0]?'on':''}" onclick="switchTab('${t[0]}')"><span class="ic">${t[1]}</span>${t[2]}</div>`).join('')}
|
||||
<div class="nl">Configuração</div>
|
||||
${ctabs.map(t=>`<div class="ni ${S.tab===t[0]?'on':''}" onclick="switchTab('${t[0]}')"><span class="ic">${t[1]}</span>${t[2]}</div>`).join('')}
|
||||
${ctabs.map(t=>`<div class="ni ${S.tab===t[0]?'on':''}" onclick="switchTab('${t[0]}')" ${t[3]==='sub'?'style="padding-left:2.2rem;font-size:.78rem;opacity:.9"':''}><span class="ic">${t[1]}</span>${t[2]}</div>`).join('')}
|
||||
${S.user?.role==='admin'?`<div class="nl">Administração</div>
|
||||
${atabs.map(t=>`<div class="ni ${S.tab===t[0]?'on':''}" onclick="switchTab('${t[0]}')"><span class="ic">${t[1]}</span>${t[2]}</div>`).join('')}`:''}</div>
|
||||
<div class="sb-f"><div class="ui"><div class="ua">${i}</div><div><div class="un">${S.user?.first_name?S.user.first_name+' '+S.user.last_name:S.user?.username}</div><div class="ur">${S.user?.role}</div></div>
|
||||
<div class="lo-btn" onclick="toggleTheme()" title="Alternar tema">${document.documentElement.classList.contains('dark')?IC.sun:IC.moon}</div>
|
||||
<div class="lo-btn" onclick="doLogout()" title="Sair">⏻</div></div></div></div>`}
|
||||
|
||||
function rTb(){const t={'chat':IC.chat+' AI Agent Chat','terraform':TF_IC+' Terraform Agent','explorer':IC.search+' OCI Account Explorer','report':IC.chart+' Compliance Reports','downloads':IC.folder+' Downloads','oci-config':IC.cloud+' Credenciais OCI','genai':IC.brain+' OCI Generative AI','mcp':IC.plug+' MCP Servers','adb':IC.db+' Autonomous DB Vector','embeddings':IC.dna+' Embeddings','users':IC.users+' Gerenciar Usuários','mfa':IC.lock+' Autenticação MFA','audit':IC.log+' Audit Log'};
|
||||
function rTb(){const t={'chat':IC.chat+' AI Agent Chat','terraform':TF_IC+' Terraform Agent','explorer':IC.search+' OCI Account Explorer','report':IC.chart+' Compliance Reports','downloads':IC.folder+' Downloads','oci-config':IC.cloud+' Credenciais OCI','genai':IC.brain+' OCI Generative AI','mcp':IC.plug+' MCP Servers','adb':IC.db+' Autonomous DB Vector','embeddings':IC.dna+' Embeddings','emb-consult':IC.search+' Consultar Embeddings','users':IC.users+' Gerenciar Usuários','mfa':IC.lock+' Autenticação MFA','audit':IC.log+' Audit Log'};
|
||||
return`<div class="tb"><div class="tb-t">${t[S.tab]||''}</div><div class="tb-a"><span class="tag">v${V}</span></div></div>`}
|
||||
|
||||
function rPg(){switch(S.tab){case'chat':return rChat();case'terraform':return rTerraform();case'explorer':return rExplorer();case'report':return rReport();case'downloads':return rDl();case'oci-config':return rOci();case'genai':return rGenAI();case'mcp':return rMCP();case'adb':return rADB();case'embeddings':return rEmbeddings();case'users':return rUsers();case'mfa':return rMfa();case'audit':return rAudit();default:return''}}
|
||||
function rPg(){switch(S.tab){case'chat':return rChat();case'terraform':return rTerraform();case'explorer':return rExplorer();case'report':return rReport();case'downloads':return rDl();case'oci-config':return rOci();case'genai':return rGenAI();case'mcp':return rMCP();case'adb':return rADB();case'embeddings':return rEmbeddings();case'emb-consult':return rEmbConsultPage();case'users':return rUsers();case'mfa':return rMfa();case'audit':return rAudit();default:return''}}
|
||||
|
||||
/* ── Chat ── */
|
||||
function rChat(){
|
||||
@@ -2592,6 +2592,8 @@ async function removeTable(vid,tid){if(!confirm('Excluir esta tabela do registro
|
||||
/* ── Embeddings ── */
|
||||
function tblOpts(vid){const cfg=S.adbCfg.find(c=>c.id===vid);if(!cfg||!(cfg.tables||[]).length)return'<option value="">Padrão</option>';
|
||||
return cfg.tables.filter(t=>t.is_active).map(t=>'<option value="'+t.table_name+'">'+t.table_name+'</option>').join('')}
|
||||
function tblOptsAll(vid){const cfg=S.adbCfg.find(c=>c.id===vid);if(!cfg||!(cfg.tables||[]).length)return'<option value="">Todas as tabelas</option>';
|
||||
return'<option value="">Todas as tabelas</option>'+cfg.tables.filter(t=>t.is_active).map(t=>'<option value="'+t.table_name+'">'+t.table_name+'</option>').join('')}
|
||||
function rEmbeddings(){
|
||||
const adbOpts=S.adbCfg.map(c=>'<option value="'+c.id+'">'+c.config_name+'</option>').join('');
|
||||
const rptOpts=S.reports.filter(r=>r.status==='completed').map(r=>'<option value="'+r.id+'">'+r.tenancy_name+' ('+r.created_at?.substring(0,10)+')</option>').join('');
|
||||
@@ -2686,6 +2688,53 @@ async function delEmb(vid,docId){if(!confirm('Excluir este embedding?'))return;
|
||||
const tbl=document.getElementById('eltbl')?.value||'';
|
||||
try{await $api('/embeddings/'+vid+'/'+docId+(tbl?'?table_name='+encodeURIComponent(tbl):''),{method:'DELETE'});loadEmbs()}catch(e){alert('Erro: '+e.message)}}
|
||||
|
||||
/* ── Consult Embeddings ── */
|
||||
if(!S.consultHistory)S.consultHistory=[];
|
||||
function rEmbConsultPage(){
|
||||
if(!S.adbCfg.length)return`<div class="cd"><div class="emp"><div class="eic">${IC.search}</div><p>Nenhuma conexão ADB configurada.</p><p style="font-size:.74rem;margin-top:.35rem">Configure em <strong>ADB Vector</strong>.</p></div></div>`;
|
||||
const firstVid=S.adbCfg.find(c=>c.is_active)?.id||S.adbCfg[0]?.id||'';
|
||||
return rEmbConsult(firstVid)}
|
||||
function rEmbConsult(firstVid){
|
||||
const msgs=S.consultHistory.map(m=>`<div class="cm cm-${m.r}"><div class="cb">${m.r==='bot'?fm(m.c):m.c}</div></div>`).join('');
|
||||
return`<div class="cd" style="display:flex;flex-direction:column;height:calc(100vh - 180px)">
|
||||
<div class="ct">${IC.search} Consultar Embeddings</div>
|
||||
<div class="cdesc">Faça perguntas sobre os dados armazenados nas bases vetoriais. O modelo de IA consulta os embeddings e retorna uma resposta contextualizada.</div>
|
||||
<div style="display:flex;gap:.5rem;margin-bottom:.75rem;flex-wrap:wrap">
|
||||
<div class="ig" style="flex:1;min-width:180px"><label style="font-size:.72rem">Tabela (opcional)</label><select id="ecTbl" style="font-size:.78rem">${tblOptsAll(firstVid)}</select></div>
|
||||
<div class="ig" style="width:80px"><label style="font-size:.72rem">Top-K</label><input type="number" id="ecTopK" value="10" min="1" max="30" style="font-size:.78rem;text-align:center"></div>
|
||||
</div>
|
||||
<div style="flex:1;overflow-y:auto;border:1px solid var(--brd);border-radius:var(--r);padding:.75rem;background:var(--bg2);margin-bottom:.75rem" id="ecMsgs">
|
||||
${msgs||`<div class="emp"><div class="eic">${IC.search}</div><p>Faça uma consulta para pesquisar nos embeddings.</p><p style="font-size:.74rem;margin-top:.35rem">Ex: "findings para CIS 1.1", "recomendações de networking", "como corrigir MFA"</p></div>`}
|
||||
</div>
|
||||
<div style="display:flex;gap:.5rem">
|
||||
<input type="text" id="ecQuery" placeholder="Digite sua consulta..." onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();consultEmb()}" style="flex:1;padding:.55rem .75rem;border:1px solid var(--brd);border-radius:var(--r);background:var(--bg1);color:var(--t1);font-size:.82rem">
|
||||
<button class="btn bp" id="ecBtn" onclick="consultEmb()" style="white-space:nowrap">${IC.search} Consultar</button>
|
||||
</div></div>`}
|
||||
|
||||
async function consultEmb(){
|
||||
const q=document.getElementById('ecQuery').value.trim();if(!q)return;
|
||||
const tbl=document.getElementById('ecTbl')?.value||'';
|
||||
const topK=parseInt(document.getElementById('ecTopK')?.value)||10;
|
||||
S.consultHistory.push({r:'user',c:q});
|
||||
document.getElementById('ecQuery').value='';
|
||||
const msgEl=document.getElementById('ecMsgs');
|
||||
msgEl.innerHTML=S.consultHistory.map(m=>`<div class="cm cm-${m.r}"><div class="cb">${m.r==='bot'?fm(m.c):m.c}</div></div>`).join('')+
|
||||
'<div class="cm cm-bot"><div class="cb"><span class="spinner" style="display:inline-block;width:14px;height:14px;vertical-align:middle"></span> Consultando bases vetoriais...</div></div>';
|
||||
msgEl.scrollTop=msgEl.scrollHeight;
|
||||
const btn=document.getElementById('ecBtn');btn.disabled=true;
|
||||
try{
|
||||
const d=await $api('/embeddings/consult',{method:'POST',body:{query:q,table_name:tbl,top_k:topK}});
|
||||
let resp=d.answer||'Sem resposta do modelo.';
|
||||
if(d.documents?.length){resp+='\n\n---\n**Fontes consultadas ('+d.documents.length+' documentos, '+d.total+' total):**\n';
|
||||
d.documents.forEach((doc,i)=>{resp+='\n'+(i+1)+'. **'+doc.source+'** (distância: '+doc.distance+')'});
|
||||
}
|
||||
S.consultHistory.push({r:'bot',c:resp});
|
||||
}catch(e){S.consultHistory.push({r:'bot',c:'Erro: '+e.message})}
|
||||
btn.disabled=false;
|
||||
msgEl.innerHTML=S.consultHistory.map(m=>`<div class="cm cm-${m.r}"><div class="cb">${m.r==='bot'?fm(m.c):m.c}</div></div>`).join('');
|
||||
msgEl.scrollTop=msgEl.scrollHeight;
|
||||
}
|
||||
|
||||
/* ── Users ── */
|
||||
function rUsers(){return`<div class="cd"><div class="ct">${IC.users} Gerenciamento de Usuários</div><div id="um"></div>
|
||||
<table style="margin-top:.65rem"><thead><tr><th>Nome</th><th>Usuário</th><th>Email</th><th>Role</th><th>MFA</th><th>Status</th><th>Último Login</th><th>Ações</th></tr></thead><tbody>
|
||||
|
||||
Reference in New Issue
Block a user