import { useCallback, useEffect, useState, type FormEvent } from 'react'; import { AssessorApiClient } from '../../api/assessor-api-client'; import { useEnginePreferences } from '../../app/engine-preferences'; import { useBackendWorkActivity } from './use-backend-work-activity'; import { statusPollApi } from './status-poll-client'; import type { RegulatoryCorpusStatus } from '../regulatory-corpus-management'; import { TriangleIcon } from '../../components/TriangleIcon'; const api = new AssessorApiClient(); const MAXIMUM_PDF_BYTES = 32 * 1024 * 1024; /** Data Hub ISO corpus tile; a switched-off Compliance Engine renders nothing and requests nothing. */ export function IsoCorpusLibrary() { return useEnginePreferences().compliance ? : null; } function IsoCorpusLibraryPanel() { const [status, setStatus] = useState(null); const [iso27001, setIso27001] = useState(null); const [iso27002, setIso27002] = useState(null); const [token, setToken] = useState(''); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); const load = useCallback(async (poll = false) => { try { setStatus(await (poll ? statusPollApi : api).getIsoCorpusStatus()); setError(null); } catch (reason) { setError(reason instanceof Error ? reason.message : 'ISO corpus status could not be loaded.'); } }, []); useEffect(() => { void load(); }, [load]); const job = status?.job?.status; useBackendWorkActivity('Embedding ISO corpus', job === 'running' ? 'running' : job === 'failed' ? 'failed' : job === 'succeeded' ? 'finished' : 'idle'); useEffect(() => { if (status?.job?.status !== 'running') return; const interval = setInterval(() => { void load(true); }, 2_000); return () => clearInterval(interval); }, [load, status?.job?.status]); const submit = async (event: FormEvent) => { event.preventDefault(); const form = event.currentTarget as HTMLFormElement; if (!iso27001 || !iso27002 || !token) return; if (iso27001.size > MAXIMUM_PDF_BYTES || iso27002.size > MAXIMUM_PDF_BYTES) { setError('Each PDF must be no larger than 32 MiB.'); return; } setSubmitting(true); setError(null); try { setStatus(await api.submitIsoCorpus(iso27001, iso27002, token)); setIso27001(null); setIso27002(null); setToken(''); form.reset(); } catch (reason) { setError(reason instanceof Error ? reason.message : 'ISO corpus upload failed.'); } finally { setSubmitting(false); } }; const running = status?.job?.status === 'running'; return {status?.active ? '1' : '0'}ISO Control Corpus {running ? status.job?.phase === 'embedding' && status.job.totalChunks ? `Embedding ${status.job.completedChunks ?? 0} / ${status.job.totalChunks}` : `Processing: ${status.job?.phase}` : status?.active ? status.indexStatus === 'incomplete' ? 'Rebuild required' : status.indexStatus === 'unavailable' ? 'Index unavailable' : 'Ready' : 'Not indexed'} {status?.active && Active version {status.active.corpusVersion} Controls and sections {status.active.documentCount.toLocaleString('en-US')} · {status.active.chunkCount.toLocaleString('en-US')} indexed chunks Embedding model {status.active.embeddingModel} {status.active.sources.map(source => {source.standard} {source.pageCount} pages · SHA-256 {source.sha256.slice(0, 12)}…)} } {status?.indexStatus === 'incomplete' && The index is incomplete. Upload the same PDFs to rebuild it.} {status?.job?.status === 'failed' && {status.job.error}} {status?.job?.status === 'succeeded' && ISO corpus indexed and active.} {!status?.configured && status?.disabledReason && {status.disabledReason}} void submit(event)}> DIN EN ISO/IEC 27001:2024-01 PDF setIso27001(event.target.files?.[0] ?? null)}/> DIN EN ISO/IEC 27002:2024-01 PDF setIso27002(event.target.files?.[0] ?? null)}/> ISO upload token setToken(event.target.value)}/> {submitting ? 'Uploading …' : running ? 'Embedding …' : 'Upload and embed PDFs'} The server keeps the generated corpus privately for rebuilding the index. Original PDFs are removed after parsing. {error && {error}} ; }
The server keeps the generated corpus privately for rebuilding the index. Original PDFs are removed after parsing.