feat: add dedicated Embeddings tab with report chunking and file upload
Add a new Embeddings menu in the sidebar for managing vector embeddings
separately from ADB config. Supports embedding CIS reports (chunked by
section) and uploading .txt files (chunked by paragraphs). Includes
listing and deleting individual embeddings from the ADB vector store.
- Add _chunk_report_by_section() for CIS report sectional chunking
- Add _chunk_text_file() for paragraph-based text chunking
- Add POST /api/embeddings/report/{rid} endpoint
- Add POST /api/embeddings/upload endpoint (multipart)
- Add GET /api/embeddings/{vid}/list with pagination
- Add DELETE /api/embeddings/{vid}/{doc_id}
- Add Embeddings tab in frontend with 3 cards (list, report, upload)
- Remove ingestion section from ADB Vector tab
This commit is contained in:
@@ -233,7 +233,7 @@ function rApp(){return`<div class="app">${rSb()}<div class="mc">${rTb()}<div cla
|
||||
|
||||
function rSb(){
|
||||
const tabs=[['chat','💬','Chat Agent'],['explorer','🔍','OCI Explorer'],['report','📊','Reports'],['downloads','📁','Downloads']];
|
||||
const ctabs=[['oci-config','☁️','Credenciais OCI'],['genai','🧠','GenAI Config'],['mcp','🔌','MCP Servers'],['adb','🗄️','ADB Vector']];
|
||||
const ctabs=[['oci-config','☁️','Credenciais OCI'],['genai','🧠','GenAI Config'],['mcp','🔌','MCP Servers'],['adb','🗄️','ADB Vector'],['embeddings','🧬','Embeddings']];
|
||||
const atabs=[['users','👥','Usuários'],['mfa','🔐','MFA'],['audit','📋','Audit Log']];
|
||||
const i=(S.user?.first_name||S.user?.username||'?')[0].toUpperCase();
|
||||
return`<div class="sb">
|
||||
@@ -247,10 +247,10 @@ ${atabs.map(t=>`<div class="ni ${S.tab===t[0]?'on':''}" onclick="switchTab('${t[
|
||||
<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="doLogout()" title="Sair">⏻</div></div></div></div>`}
|
||||
|
||||
function rTb(){const t={'chat':'💬 AI Agent Chat','explorer':'🔍 OCI Account Explorer','report':'📊 Compliance Reports','downloads':'📁 Downloads','oci-config':'☁️ Credenciais OCI','genai':'🧠 OCI Generative AI','mcp':'🔌 MCP Servers','adb':'🗄️ Autonomous DB Vector','users':'👥 Gerenciar Usuários','mfa':'🔐 Autenticação MFA','audit':'📋 Audit Log'};
|
||||
function rTb(){const t={'chat':'💬 AI Agent Chat','explorer':'🔍 OCI Account Explorer','report':'📊 Compliance Reports','downloads':'📁 Downloads','oci-config':'☁️ Credenciais OCI','genai':'🧠 OCI Generative AI','mcp':'🔌 MCP Servers','adb':'🗄️ Autonomous DB Vector','embeddings':'🧬 Embeddings','users':'👥 Gerenciar Usuários','mfa':'🔐 Autenticação MFA','audit':'📋 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'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'users':return rUsers();case'mfa':return rMfa();case'audit':return rAudit();default:return''}}
|
||||
function rPg(){switch(S.tab){case'chat':return rChat();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''}}
|
||||
|
||||
/* ── Chat ── */
|
||||
function rChat(){
|
||||
@@ -466,15 +466,7 @@ ${!S.adbCfg.length?'<div class="emp" style="padding:.85rem"><p>Nenhuma conexão
|
||||
<div class="cd"><div class="ct">📤 Upload Wallet</div>
|
||||
<div class="g2"><div class="ig"><label>Conexão ADB</label><select id="awsel">${S.adbCfg.map(c=>`<option value="${c.id}">${c.config_name}</option>`).join('')}</select></div>
|
||||
<div class="ig"><label>Wallet ZIP</label><input type="file" id="awf" accept=".zip"></div></div>
|
||||
<button class="btn bs" onclick="uWallet()">Upload Wallet</button></div>
|
||||
<div class="cd"><div class="ct">📄 Ingerir Documentos</div>
|
||||
<div class="cdesc">Gere embeddings e insira documentos na tabela ADB para uso com RAG.</div>
|
||||
<div class="g2"><div class="ig"><label>Conexão ADB</label>
|
||||
<select id="aisel">${S.adbCfg.filter(c=>c.genai_config_id).map(c=>'<option value="'+c.id+'">'+c.config_name+'</option>').join('')}</select></div>
|
||||
<div class="ig"><label>Documentos (JSON)</label>
|
||||
<div class="ht">Array de objetos: [{"content":"texto","source":"fonte","metadata":"info"}]</div>
|
||||
<textarea id="adocs" rows="6" style="font-family:var(--fm);font-size:.72rem;width:100%;padding:.5rem;border:1px solid var(--bd);border-radius:var(--r)" placeholder='[{"content":"...","source":"doc.pdf","metadata":"page 1"}]'></textarea></div></div>
|
||||
<button class="btn bp" onclick="ingestDocs()">Ingerir Documentos</button></div>`}
|
||||
<button class="btn bs" onclick="uWallet()">Upload Wallet</button></div>`}
|
||||
async function sAdb(){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,
|
||||
@@ -487,11 +479,49 @@ async function uWallet(){const fd=new FormData();const f=document.getElementById
|
||||
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 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)}}
|
||||
async function ingestDocs(){const id=document.getElementById('aisel').value;
|
||||
if(!id)return alert('Selecione conexão ADB com GenAI configurado');
|
||||
let docs;try{docs=JSON.parse(document.getElementById('adocs').value)}catch(e){return alert('JSON inválido')}
|
||||
if(!Array.isArray(docs)||!docs.length)return alert('Forneça array de documentos');
|
||||
try{const d=await $api('/adb/'+id+'/ingest',{method:'POST',body:{adb_config_id:id,documents:docs}});alert(d.message)}catch(e){alert('Erro: '+e.message)}}
|
||||
|
||||
/* ── Embeddings ── */
|
||||
function rEmbeddings(){
|
||||
const adbOpts=S.adbCfg.filter(c=>c.genai_config_id).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('');
|
||||
if(!S.adbCfg.filter(c=>c.genai_config_id).length)return`<div class="cd"><div class="emp"><div class="eic">🧬</div><p>Nenhuma conexão ADB com GenAI configurada.</p><p style="font-size:.74rem;margin-top:.35rem">Configure em <strong>ADB Vector</strong> vinculando uma Config GenAI.</p></div></div>`;
|
||||
return`<div class="cd"><div class="ct">📋 Embeddings Existentes</div>
|
||||
<div class="cdesc">Consulte os documentos armazenados na tabela de embeddings do ADB.</div>
|
||||
<div class="g2"><div class="ig"><label>Conexão ADB</label><select id="elsel">${adbOpts}</select></div>
|
||||
<div class="ig" style="align-self:end"><button class="btn bs" onclick="loadEmbs()">Carregar</button></div></div>
|
||||
<div id="emb-list"></div></div>
|
||||
<div class="cd"><div class="ct">📊 Embed Relatório CIS</div>
|
||||
<div class="cdesc">Gere embeddings a partir de relatórios CIS. Cada seção (IAM, Networking, etc.) gera um embedding separado.</div><div id="erm"></div>
|
||||
<div class="g2"><div class="ig"><label>Conexão ADB</label><select id="ersel">${adbOpts}</select></div>
|
||||
<div class="ig"><label>Relatório</label><select id="errpt">${rptOpts||'<option value="">Nenhum relatório disponível</option>'}</select></div></div>
|
||||
<button class="btn bp" onclick="embedReport()">Gerar Embeddings do Relatório</button></div>
|
||||
<div class="cd"><div class="ct">📄 Upload de Arquivo</div>
|
||||
<div class="cdesc">Faça upload de um arquivo .txt para gerar embeddings automaticamente (chunking por parágrafos).</div><div id="efm"></div>
|
||||
<div class="g2"><div class="ig"><label>Conexão ADB</label><select id="efsel">${adbOpts}</select></div>
|
||||
<div class="ig"><label>Arquivo (.txt)</label><input type="file" id="eff" accept=".txt"></div></div>
|
||||
<button class="btn bp" onclick="embedFile()">Upload e Gerar Embeddings</button></div>`}
|
||||
|
||||
async function loadEmbs(){const vid=document.getElementById('elsel').value;if(!vid)return;
|
||||
try{const d=await $api('/embeddings/'+vid+'/list');
|
||||
if(!d.documents.length){document.getElementById('emb-list').innerHTML='<div class="emp" style="padding:.65rem"><p>Nenhum embedding encontrado.</p></div>';return}
|
||||
document.getElementById('emb-list').innerHTML='<table style="margin-top:.65rem"><thead><tr><th>ID</th><th>Source</th><th>Metadata</th><th>Data</th><th>Ações</th></tr></thead><tbody>'+
|
||||
d.documents.map(e=>'<tr><td style="font-family:var(--fm);font-size:.62rem">'+e.id.substring(0,8)+'...</td><td>'+
|
||||
(e.source||'—')+'</td><td style="font-size:.72rem;color:var(--t3)">'+(e.metadata||'—')+'</td><td style="font-size:.72rem;color:var(--t4)">'+
|
||||
(e.created_at||'—')+'</td><td><button class="btn bd bsm" onclick="delEmb(\''+vid+'\',\''+e.id+'\')">Excluir</button></td></tr>').join('')+
|
||||
'</tbody></table><div style="padding:.5rem;font-size:.72rem;color:var(--t4)">Total: '+d.total+' documentos</div>'}
|
||||
catch(e){document.getElementById('emb-list').innerHTML='<div style="color:var(--rd);padding:.5rem">Erro: '+e.message+'</div>'}}
|
||||
|
||||
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')}}
|
||||
|
||||
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');
|
||||
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')}}
|
||||
|
||||
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)}}
|
||||
|
||||
/* ── Users ── */
|
||||
function rUsers(){return`<div class="cd"><div class="ct">👥 Gerenciamento de Usuários</div><div id="um"></div>
|
||||
|
||||
Reference in New Issue
Block a user