'use strict'; import logger from '../logger.js'; import * as sourceCorpusStore from '../stores/sourceCorpusStore.js'; import * as documentCacheStore from '../stores/documentCacheStore.js'; import { generateStudyGuide, generateFaq, generateExecutiveBrief, type SourceGroup, type StudyGuide, type Faq, type ExecutiveBrief, } from './documentService.js'; const MIN_SOURCES = 2; type DocumentType = 'study-guide' | 'faq' | 'executive-brief'; type GeneratorFn = (sourceGroups: SourceGroup[]) => Promise; const GENERATORS: Record = { 'study-guide': generateStudyGuide, 'faq': generateFaq, 'executive-brief': generateExecutiveBrief, }; const DEBOUNCE_MS = 5000; const pendingReGen = new Set(); const debounceTimers = new Map>(); export function triggerPreGeneration(sourceCorpusId: string): void { const sourceCorpus = sourceCorpusStore.getSourceCorpus(sourceCorpusId); if (!sourceCorpus) return; const sources = sourceCorpusStore.getSources(sourceCorpusId); if (sources.length < MIN_SOURCES) return; if (documentCacheStore.isGenerating(sourceCorpusId)) { pendingReGen.add(sourceCorpusId); documentCacheStore.invalidate(sourceCorpusId); logger.debug({ sourceCorpusId }, 'pre-generation in progress, queued re-generation'); return; } const existingTimer = debounceTimers.get(sourceCorpusId); if (existingTimer !== undefined) { clearTimeout(existingTimer); } debounceTimers.set( sourceCorpusId, setTimeout(() => { debounceTimers.delete(sourceCorpusId); runPreGeneration(sourceCorpusId); }, DEBOUNCE_MS) ); logger.debug({ sourceCorpusId, debounceMs: DEBOUNCE_MS }, 'pre-generation debounced'); } function runPreGeneration(sourceCorpusId: string): void { try { const sources = sourceCorpusStore.getSources(sourceCorpusId); const chunks = sourceCorpusStore.getChunksForSourceCorpus(sourceCorpusId); const sourceGroups = sourceCorpusStore.buildSourceGroups(sourceCorpusId, chunks) as SourceGroup[]; documentCacheStore.invalidate(sourceCorpusId); documentCacheStore.markGenerating(sourceCorpusId); logger.info( { sourceCorpusId, sourceCount: sources.length, chunkCount: chunks.length }, 'background pre-generation started' ); const jobs = (Object.entries(GENERATORS) as Array<[DocumentType, GeneratorFn]>).map( async ([type, generator]) => { try { const document = await generator(sourceGroups); documentCacheStore.setCachedDocument(sourceCorpusId, type, document, sources); logger.info({ sourceCorpusId, type }, 'background pre-generation complete for type'); } catch (err) { const error = err as Error; logger.error({ sourceCorpusId, type, err: error.message }, 'background pre-generation failed for type'); } } ); Promise.all(jobs) .then(() => { logger.info({ sourceCorpusId }, 'all background pre-generation complete'); }) .finally(() => { documentCacheStore.clearGenerating(sourceCorpusId); if (pendingReGen.has(sourceCorpusId)) { pendingReGen.delete(sourceCorpusId); logger.info({ sourceCorpusId }, 're-triggering pre-generation for updated sources'); runPreGeneration(sourceCorpusId); } }); } catch (err) { const error = err as Error; logger.error({ sourceCorpusId, err: error.message }, 'pre-generation setup failed'); documentCacheStore.clearGenerating(sourceCorpusId); } }