feat: v3.0 — CIS PDF chunker, DOCX professional, RAG complete, progress bar, code cleanup

- CIS PDF chunker: segments by recommendation (54/54 complete with desc+rationale+rem), 7000 chars target, 500 overlap
- RAG: 60s timeout + retry, direct SQL fetch by recommendationNumber, concatenate all chunks
- Auto-detect embedding dimension from DDL (empty tables), auto-detect model per dimension
- DOCX: camo strip (Pillow), A4 centered cover, code blocks (Consolas), lists with hanging indent
- Compliance report: progress bar (RAG X/35 percentage), Audit/Verification bold
- Code cleanup: removed redundant imports (io, base64, time, Response, HTMLResponse), duplicate DB query
- EmbeddingsPage: auto-select ADB config fix
- ReportsPage: removed DOCX preview (docx-preview lib)
- README updated to v3.0 with all new features
This commit is contained in:
nogueiraguh
2026-03-31 10:44:13 -03:00
parent c81909d0a1
commit 1b3c02d10b
4 changed files with 852 additions and 284 deletions

View File

@@ -598,6 +598,7 @@ export default function ReportsPage() {
const setShowCompliance = store.setRptShowCompliance;
const [complianceReady, setComplianceReady] = useState(false);
const [complianceGenerating, setComplianceGenerating] = useState(false);
const [complianceProgress, setComplianceProgress] = useState<{ current: number; total: number; step: string; rec: string }>({ current: 0, total: 0, step: '', rec: '' });
/* ── Error ── */
const [error, setError] = useState('');
@@ -725,12 +726,16 @@ export default function ReportsPage() {
async () => {
if (!complianceGenerating || !selectedRid) return false;
try {
const s = await reportsApi.complianceReportStatus(selectedRid);
const s = await reportsApi.complianceReportStatus(selectedRid) as { ready: boolean; generating: boolean; current?: number; total?: number; step?: string; rec?: string };
if (s.ready) {
setComplianceReady(true);
setComplianceGenerating(false);
setShowCompliance(true);
return true; // stop polling
setComplianceProgress({ current: 0, total: 0, step: '', rec: '' });
return true;
}
if (s.generating && s.total) {
setComplianceProgress({ current: s.current || 0, total: s.total || 0, step: s.step || '', rec: s.rec || '' });
}
} catch { /* keep polling */ }
return false;
@@ -756,6 +761,8 @@ export default function ReportsPage() {
// polling is already running via usePolling above
}, [selectedRid, complianceGenerating]);
/* ── Handlers ── */
const handleRun = async () => {
if (!ociVal) {
@@ -1431,8 +1438,10 @@ export default function ReportsPage() {
src={reportsApi.complianceReportUrl(selectedRid)}
className="w-full border-0"
style={{ height: 700, background: '#fff' }}
title="LAD A-Team CIS Compliance Report"
title="CIS Compliance Report"
/>
{/* Action buttons */}
<div className="px-5 py-2 flex justify-end gap-2" style={{ borderTop: '1px solid var(--bd)' }}>
<a
href={reportsApi.complianceReportZipUrl(selectedRid)}
@@ -1458,10 +1467,30 @@ export default function ReportsPage() {
</div>
</div>
) : complianceGenerating ? (
<div className="flex flex-col items-center justify-center py-16 gap-3">
<Loader2 size={32} className="animate-spin" style={{ color: 'var(--gn)' }} />
<div className="flex flex-col items-center justify-center py-12 gap-4">
<Loader2 size={28} className="animate-spin" style={{ color: 'var(--gn)' }} />
<p className="text-[.82rem] font-medium" style={{ color: 'var(--t2)' }}>{t('rpt.complianceGenerating')}</p>
<p className="text-[.7rem]" style={{ color: 'var(--t4)' }}>{t('rpt.complianceFetchingKB')}</p>
{complianceProgress.total > 0 && (
<div className="w-full max-w-md space-y-2">
<div className="flex justify-between text-[.68rem]" style={{ color: 'var(--t3)' }}>
<span>{complianceProgress.step} {complianceProgress.rec}</span>
<span>{complianceProgress.current}/{complianceProgress.total}</span>
</div>
<div className="w-full rounded-full overflow-hidden" style={{ height: 6, background: 'var(--bg3)' }}>
<div className="rounded-full transition-all" style={{
width: `${Math.round((complianceProgress.current / complianceProgress.total) * 100)}%`,
height: '100%',
background: 'var(--gn)',
}} />
</div>
<p className="text-[.65rem] text-center" style={{ color: 'var(--t4)' }}>
{Math.round((complianceProgress.current / complianceProgress.total) * 100)}% Buscando remediações na base de conhecimento
</p>
</div>
)}
{!complianceProgress.total && (
<p className="text-[.7rem]" style={{ color: 'var(--t4)' }}>{t('rpt.complianceFetchingKB')}</p>
)}
</div>
) : (
<div className="flex flex-col items-center justify-center py-12 gap-3">

View File

@@ -58,6 +58,17 @@ export default function EmbeddingsPage() {
const [kbUrlUploading, setKbUrlUploading] = useState(false);
const [kbMsg, setKbMsg] = useState<MsgState>(null);
// Auto-select ADB config when data loads (fixes empty dropdown after navigation)
useEffect(() => {
const defaultId = adbCfg.find((c) => c.is_active)?.id || adbCfg[0]?.id || '';
if (defaultId) {
if (!selVid) setSelVid(defaultId);
if (!cisVid) setCisVid(defaultId);
if (!kbVid) setKbVid(defaultId);
if (!purgeVid) setPurgeVid(defaultId);
}
}, [adbCfg]); // eslint-disable-line react-hooks/exhaustive-deps
// ── Purge section ──
const [purgeVid, setPurgeVid] = useState(() => adbCfg.find((c) => c.is_active)?.id || adbCfg[0]?.id || '');
const [purgeTable, setPurgeTable] = useState('');