import { useState, useRef, useCallback, useEffect } from 'react';
import { useAppStore, type AdbConfig, type AdbTable } from '@/stores/app';
import { embeddingsApi, type EmbeddingDoc } from '@/api/endpoints/embeddings';
import { useI18n } from '@/i18n';
import {
Dna, Shield, ScrollText, Upload, Loader2, Check, X,
Trash2, RefreshCw, Link, FileUp, Database, Search, AlertTriangle,
} from 'lucide-react';
/* ── Helpers ── */
function Msg({ type, text }: { type: 's' | 'e' | 'i'; text: string }) {
const bg = type === 's' ? 'var(--gnl)' : type === 'e' ? 'var(--rdl)' : 'var(--bg2)';
const fg = type === 's' ? 'var(--gn)' : type === 'e' ? 'var(--rd)' : 'var(--t2)';
return (
{type === 's' && }
{type === 'e' && }
{type === 'i' && }
{text}
);
}
type MsgState = { type: 's' | 'e' | 'i'; text: string } | null;
function getActiveTables(cfg: AdbConfig): AdbTable[] {
return (cfg.tables || []).filter((t) => t.is_active);
}
function getAllTables(cfg: AdbConfig): AdbTable[] {
return cfg.tables || [];
}
/* ── Main ── */
export default function EmbeddingsPage() {
const { t } = useI18n();
const { adbCfg, ociCfg, genaiCfg } = useAppStore();
// ── Existing Embeddings section ──
const [selVid, setSelVid] = useState(() => adbCfg.find((c) => c.is_active)?.id || adbCfg[0]?.id || '');
const [selTable, setSelTable] = useState('');
const [docs, setDocs] = useState([]);
const [total, setTotal] = useState(0);
const [listLoading, setListLoading] = useState(false);
const [listMsg, setListMsg] = useState(null);
// ── CIS Recommendations section ──
const [cisVid, setCisVid] = useState(() => adbCfg.find((c) => c.is_active)?.id || adbCfg[0]?.id || '');
const cisPdfRef = useRef(null);
const [cisUploading, setCisUploading] = useState(false);
const [cisMsg, setCisMsg] = useState(null);
// ── Knowledge Base section ──
const [kbVid, setKbVid] = useState(() => adbCfg.find((c) => c.is_active)?.id || adbCfg[0]?.id || '');
const kbFileRef = useRef(null);
const [kbUrl, setKbUrl] = useState('');
const [kbFileUploading, setKbFileUploading] = useState(false);
const [kbUrlUploading, setKbUrlUploading] = useState(false);
const [kbMsg, setKbMsg] = useState(null);
// ── Purge section ──
const [purgeVid, setPurgeVid] = useState(() => adbCfg.find((c) => c.is_active)?.id || adbCfg[0]?.id || '');
const [purgeTable, setPurgeTable] = useState('');
const [purgeTenancy, setPurgeTenancy] = useState('');
const [purging, setPurging] = useState(false);
const [purgeMsg, setPurgeMsg] = useState(null);
const [purgeConfirm, setPurgeConfirm] = useState(false);
// Update selTable when selVid changes
useEffect(() => {
const cfg = adbCfg.find((c) => c.id === selVid);
if (cfg) {
const tables = getActiveTables(cfg);
setSelTable(tables[0]?.table_name || '');
}
}, [selVid, adbCfg]);
// Preconditions
if (!adbCfg.length) {
return (
{t('emb.noAdb')}
{t('emb.noAdbHint')}
);
}
if (!genaiCfg.length && !ociCfg.length) {
return (
{t('emb.noOci')}
{t('emb.noOciHint')}
);
}
/* ── Handlers ── */
const loadEmbs = async () => {
if (!selVid) return;
setListLoading(true);
setListMsg(null);
setDocs([]);
try {
const d = await embeddingsApi.list(selVid, selTable || undefined);
setDocs(d.documents);
setTotal(d.total);
if (!d.documents.length) setListMsg({ type: 'i', text: t('emb.noEmbeddings') });
} catch (err) {
setListMsg({ type: 'e', text: err instanceof Error ? err.message : t('emb.errorLoadEmb') });
} finally {
setListLoading(false);
}
};
const deleteEmb = async (docId: string) => {
if (!confirm(t('emb.confirmDeleteEmb'))) return;
try {
await embeddingsApi.remove(selVid, docId, selTable || undefined);
loadEmbs();
} catch (err) {
alert(err instanceof Error ? err.message : t('common.errorDelete'));
}
};
const uploadCis = async () => {
const f = cisPdfRef.current?.files?.[0];
if (!cisVid || !f) { setCisMsg({ type: 'e', text: t('emb.selectAdbAndFile') }); return; }
setCisUploading(true);
setCisMsg({ type: 'i', text: t('emb.processing') });
try {
const d = await embeddingsApi.uploadFile(cisVid, 'cisrecom', f);
setCisMsg({ type: 's', text: `${d.message} -> tabela CISRECOM` });
if (cisPdfRef.current) cisPdfRef.current.value = '';
} catch (err) {
setCisMsg({ type: 'e', text: err instanceof Error ? err.message : 'Erro' });
} finally {
setCisUploading(false);
}
};
const uploadKbFile = async () => {
const f = kbFileRef.current?.files?.[0];
if (!kbVid || !f) { setKbMsg({ type: 'e', text: t('emb.selectAdbAndFile') }); return; }
setKbFileUploading(true);
setKbMsg({ type: 'i', text: t('emb.uploading') });
try {
const d = await embeddingsApi.uploadFile(kbVid, 'engineerknowledgebase', f);
setKbMsg({ type: 's', text: d.message });
if (kbFileRef.current) kbFileRef.current.value = '';
} catch (err) {
setKbMsg({ type: 'e', text: err instanceof Error ? err.message : 'Erro' });
} finally {
setKbFileUploading(false);
}
};
const uploadKbUrl = async () => {
if (!kbVid || !kbUrl.trim()) { setKbMsg({ type: 'e', text: t('emb.selectAdbAndUrl') }); return; }
setKbUrlUploading(true);
setKbMsg({ type: 'i', text: t('emb.fetchingUrl') });
try {
const d = await embeddingsApi.uploadUrl(kbVid, 'engineerknowledgebase', kbUrl.trim());
setKbMsg({ type: 's', text: d.message });
setKbUrl('');
} catch (err) {
setKbMsg({ type: 'e', text: err instanceof Error ? err.message : 'Erro' });
} finally {
setKbUrlUploading(false);
}
};
const executePurge = async () => {
if (!purgeVid || !purgeTable) { setPurgeMsg({ type: 'e', text: t('emb.selectTable') }); return; }
setPurging(true);
setPurgeMsg({ type: 'i', text: t('emb.purging') });
try {
const d = await embeddingsApi.purge(purgeVid, purgeTable, purgeTenancy || undefined);
setPurgeMsg({ type: 's', text: `${d.deleted} embeddings removidos da tabela ${d.table} (tenancy: ${d.tenancy})` });
setPurgeConfirm(false);
} catch (err) {
setPurgeMsg({ type: 'e', text: err instanceof Error ? err.message : 'Erro' });
} finally {
setPurging(false);
}
};
/* ── Shared styles ── */
const inputCls = 'w-full px-3 py-2 rounded-lg text-[.78rem] outline-none transition-colors focus:ring-1';
const inputStyle = { background: 'var(--bg2)', border: '1.5px solid var(--bd)', color: 'var(--t1)' };
const labelCls = 'block text-[.72rem] font-semibold mb-1';
const labelStyle = { color: 'var(--t3)' };
const cardCls = 'card';
const cardStyle = { padding: 0 };
const cardHeaderCls = 'card-header';
const cardHeaderStyle = { margin: 0, padding: '14px 20px' };
const adbOptions = adbCfg.map((c) => (
{c.config_name}
));
const tableOptions = (vid: string) => {
const cfg = adbCfg.find((c) => c.id === vid);
if (!cfg) return null;
return getActiveTables(cfg).map((t) => (
{t.table_name}
));
};
const allTableOptions = (vid: string) => {
const cfg = adbCfg.find((c) => c.id === vid);
if (!cfg) return null;
return getAllTables(cfg).map((t) => (
{t.table_name}
));
};
return (
{/* Header */}
{t('emb.title')}
{t('emb.subtitle')}
{/* ────── Existing Embeddings ────── */}
{t('emb.existing')}
{t('emb.existingDesc')}
{t('emb.adbConnection')}
setSelVid(e.target.value)} className={inputCls} style={inputStyle}>
{adbOptions}
{t('emb.table')}
setSelTable(e.target.value)} className={inputCls} style={inputStyle}>
{tableOptions(selVid)}
{listLoading ? : }
{t('emb.load')}
{listMsg &&
}
{docs.length > 0 && (
<>
ID
Metadata
{docs.map((doc) => {
let meta = doc.metadata || '\u2014';
if (typeof meta === 'object') meta = JSON.stringify(meta);
if (meta.length > 200) meta = meta.substring(0, 200) + '\u2026';
return (
{doc.id.substring(0, 12)}
{meta}
deleteEmb(doc.id)} className="btn btn-sm" style={{ color: 'var(--rd)', background: 'none', border: 'none', padding: 4 }}>
);
})}
{t('emb.totalDocs').replace('{0}', String(total))}
>
)}
{/* ────── CIS Recommendations ────── */}
{t('emb.cisTitle')}
{t('emb.cisDesc')}
{cisMsg &&
}
{t('emb.adbConnection')}
setCisVid(e.target.value)} className={inputCls} style={inputStyle}>
{adbOptions}
{t('emb.pdfFile')}
{cisUploading ? : }
{t('emb.uploadCis')}
{/* ────── Knowledge Base ────── */}
{t('emb.kbTitle')}
{t('emb.kbDesc')}
{kbMsg &&
}
{t('emb.adbConnection')}
setKbVid(e.target.value)} className={inputCls} style={inputStyle}>
{adbOptions}
{/* File upload */}
{t('emb.fileLabel')}
{kbFileUploading ? : }
{t('emb.uploadFile')}
{/* Divider */}
{/* URL import */}
{t('emb.urlLabel')}
setKbUrl(e.target.value)}
placeholder="https://docs.oracle.com/..."
className={inputCls}
style={inputStyle}
/>
{kbUrlUploading ? : }
{t('emb.importUrl')}
{/* ────── Purge ────── */}
{t('emb.purgeDesc')}
{purgeMsg &&
}
{t('emb.adbConnection')}
setPurgeVid(e.target.value)} className={inputCls} style={inputStyle}>
{adbOptions}
{t('emb.table')}
setPurgeTable(e.target.value)} className={inputCls} style={inputStyle}>
{t('common.select')}
{allTableOptions(purgeVid)}
{t('emb.tenancyOptional')}
setPurgeTenancy(e.target.value)}
placeholder="Filtrar por tenancy"
className={inputCls}
style={inputStyle}
/>
{!purgeConfirm ? (
{ if (!purgeTable) { setPurgeMsg({ type: 'e', text: t('emb.selectTable') }); return; } setPurgeConfirm(true); }}
className="btn btn-danger btn-sm w-full justify-center"
>
{t('emb.purge')}
) : (
{purging ? : }
{t('emb.confirm')}
setPurgeConfirm(false)}
className="px-3 py-2 rounded-lg text-[.72rem] font-medium"
style={{ background: 'var(--bg2)', color: 'var(--t3)', border: '1px solid var(--bd)' }}
>
{t('common.no')}
)}
);
}