From 302fadb49453ee27b87e316301eb6e03a643e692 Mon Sep 17 00:00:00 2001 From: DaKheera47 Date: Thu, 11 Dec 2025 23:15:20 +0000 Subject: [PATCH] pdf generation fix --- orchestrator/src/client/App.tsx | 28 +++++++++ orchestrator/src/client/api/client.ts | 13 ++++ .../src/client/components/JobCard.tsx | 17 ++++- .../src/client/components/JobList.tsx | 62 ++++++++++++++----- orchestrator/src/server/api/routes.ts | 33 ++++++++++ orchestrator/src/server/services/pdf.ts | 7 +-- orchestrator/vite.config.ts | 4 ++ resume-generator/.gitignore | 8 ++- 8 files changed, 149 insertions(+), 23 deletions(-) diff --git a/orchestrator/src/client/App.tsx b/orchestrator/src/client/App.tsx index 41a6267..aa56344 100644 --- a/orchestrator/src/client/App.tsx +++ b/orchestrator/src/client/App.tsx @@ -21,6 +21,7 @@ export const App: React.FC = () => { const [isLoading, setIsLoading] = useState(true); const [isPipelineRunning, setIsPipelineRunning] = useState(false); const [processingJobId, setProcessingJobId] = useState(null); + const [isProcessingAll, setIsProcessingAll] = useState(false); const [toasts, setToasts] = useState([]); // Toast helpers @@ -147,6 +148,31 @@ export const App: React.FC = () => { } }; + // Process all discovered jobs + const handleProcessAll = async () => { + try { + setIsProcessingAll(true); + const result = await api.processAllDiscovered(); + addToast(`Processing ${result.count} jobs in background...`, 'info'); + + // Poll for completion + const pollInterval = setInterval(async () => { + await loadJobs(); + const currentStats = await api.getJobs(); + const stillDiscovered = currentStats.byStatus.discovered + currentStats.byStatus.processing; + if (stillDiscovered === 0) { + clearInterval(pollInterval); + setIsProcessingAll(false); + addToast('All jobs processed!', 'success'); + } + }, 3000); + } catch (error) { + setIsProcessingAll(false); + const message = error instanceof Error ? error.message : 'Failed to process jobs'; + addToast(message, 'error'); + } + }; + return ( <>
{ onApply={handleApply} onReject={handleReject} onProcess={handleProcess} + onProcessAll={handleProcessAll} processingJobId={processingJobId} + isProcessingAll={isProcessingAll} /> diff --git a/orchestrator/src/client/api/client.ts b/orchestrator/src/client/api/client.ts index b166bc6..e376f9b 100644 --- a/orchestrator/src/client/api/client.ts +++ b/orchestrator/src/client/api/client.ts @@ -104,3 +104,16 @@ export async function clearDatabase(): Promise<{ method: 'DELETE', }); } + +// Bulk operations +export async function processAllDiscovered(): Promise<{ + message: string; + count: number; +}> { + return fetchApi<{ + message: string; + count: number; + }>('/jobs/process-discovered', { + method: 'POST', + }); +} diff --git a/orchestrator/src/client/components/JobCard.tsx b/orchestrator/src/client/components/JobCard.tsx index 1efb4cc..3c6e70d 100644 --- a/orchestrator/src/client/components/JobCard.tsx +++ b/orchestrator/src/client/components/JobCard.tsx @@ -114,15 +114,28 @@ export const JobCard: React.FC = ({ View Job + {/* View PDF in browser */} + {hasPdf && ( + + + View PDF + + )} + {/* Download PDF */} {hasPdf && ( - Download PDF + Download )} diff --git a/orchestrator/src/client/components/JobList.tsx b/orchestrator/src/client/components/JobList.tsx index 2583d19..03aa198 100644 --- a/orchestrator/src/client/components/JobList.tsx +++ b/orchestrator/src/client/components/JobList.tsx @@ -5,13 +5,16 @@ import React, { useState } from 'react'; import type { Job, JobStatus } from '../../shared/types'; import { JobCard } from './JobCard'; +import { RefreshIcon } from './Icons'; interface JobListProps { jobs: Job[]; onApply: (id: string) => void; onReject: (id: string) => void; onProcess: (id: string) => void; + onProcessAll: () => void; processingJobId: string | null; + isProcessingAll: boolean; } type FilterTab = 'ready' | 'discovered' | 'applied' | 'all'; @@ -28,7 +31,9 @@ export const JobList: React.FC = ({ onApply, onReject, onProcess, + onProcessAll, processingJobId, + isProcessingAll, }) => { const [activeTab, setActiveTab] = useState('ready'); @@ -40,24 +45,49 @@ export const JobList: React.FC = ({ return jobs.filter(job => tab.statuses.includes(job.status)); }, [jobs, activeTab]); + const discoveredCount = jobs.filter(j => j.status === 'discovered').length; + return (
-
- {tabs.map(tab => { - const count = tab.statuses.length === 0 - ? jobs.length - : jobs.filter(j => tab.statuses.includes(j.status)).length; - - return ( - - ); - })} +
+
+ {tabs.map(tab => { + const count = tab.statuses.length === 0 + ? jobs.length + : jobs.filter(j => tab.statuses.includes(j.status)).length; + + return ( + + ); + })} +
+ + {activeTab === 'discovered' && discoveredCount > 0 && ( + + )}
{filteredJobs.length === 0 ? ( diff --git a/orchestrator/src/server/api/routes.ts b/orchestrator/src/server/api/routes.ts index 71adc76..57d7212 100644 --- a/orchestrator/src/server/api/routes.ts +++ b/orchestrator/src/server/api/routes.ts @@ -170,6 +170,39 @@ apiRouter.post('/jobs/:id/reject', async (req: Request, res: Response) => { } }); +/** + * POST /api/jobs/process-discovered - Process all discovered jobs (generate PDFs) + */ +apiRouter.post('/jobs/process-discovered', async (req: Request, res: Response) => { + try { + const discoveredJobs = await jobsRepo.getAllJobs(['discovered']); + + // Process each job in background + const processInBackground = async () => { + for (const job of discoveredJobs.filter(j => j.status === 'discovered')) { + try { + await processJob(job.id); + } catch (error) { + console.error(`Failed to process job ${job.id}:`, error); + } + } + }; + + processInBackground().catch(console.error); + + res.json({ + success: true, + data: { + message: `Processing ${discoveredJobs.length} jobs`, + count: discoveredJobs.length, + } + }); + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + res.status(500).json({ success: false, error: message }); + } +}); + // ============================================================================ // Pipeline API // ============================================================================ diff --git a/orchestrator/src/server/services/pdf.ts b/orchestrator/src/server/services/pdf.ts index 5df4a36..8683ce7 100644 --- a/orchestrator/src/server/services/pdf.ts +++ b/orchestrator/src/server/services/pdf.ts @@ -97,11 +97,10 @@ async function runPythonPdfGenerator( outputFilename: string ): Promise { return new Promise((resolve, reject) => { - // Note: This calls the Python script with the JSON path - // The Python script needs to be modified to accept these args - // For now, we'll use environment variables + // Use the virtual environment's Python + const pythonPath = join(RESUME_GEN_DIR, '.venv', 'bin', 'python'); - const child = spawn('python3', ['rxresume_automation.py'], { + const child = spawn(pythonPath, ['rxresume_automation.py'], { cwd: RESUME_GEN_DIR, env: { ...process.env, diff --git a/orchestrator/vite.config.ts b/orchestrator/vite.config.ts index a7efc16..067585f 100644 --- a/orchestrator/vite.config.ts +++ b/orchestrator/vite.config.ts @@ -18,6 +18,10 @@ export default defineConfig({ target: 'http://localhost:3001', changeOrigin: true, }, + '/pdfs': { + target: 'http://localhost:3001', + changeOrigin: true, + }, }, }, build: { diff --git a/resume-generator/.gitignore b/resume-generator/.gitignore index 22137b0..6036520 100644 --- a/resume-generator/.gitignore +++ b/resume-generator/.gitignore @@ -1,2 +1,8 @@ -# any json files that start with "temp_" +# Temp JSON files (used by orchestrator) temp_*.json + +# Python virtual environment +.venv/ + +# Generated resumes +resumes/