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:
@@ -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">
|
||||
|
||||
@@ -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('');
|
||||
|
||||
Reference in New Issue
Block a user