From 8a236c0be9ef9b69f6ce36d12a85871799f75a8e Mon Sep 17 00:00:00 2001 From: DaKheera47 Date: Wed, 17 Dec 2025 17:48:03 +0000 Subject: [PATCH] can regenerate pdfs, because sometimes the ai gets it wrong, and we need to help it out --- orchestrator/src/client/App.tsx | 6 +++-- orchestrator/src/client/api/client.ts | 5 ++-- .../src/client/components/JobCard.tsx | 4 +-- .../src/client/components/JobTable.tsx | 14 ++++++---- orchestrator/src/server/api/routes.ts | 5 +++- .../src/server/pipeline/orchestrator.ts | 26 ++++++++++++++++--- orchestrator/src/server/services/pdf.ts | 9 ++++++- 7 files changed, 53 insertions(+), 16 deletions(-) diff --git a/orchestrator/src/client/App.tsx b/orchestrator/src/client/App.tsx index 59a643c..5f4fbb0 100644 --- a/orchestrator/src/client/App.tsx +++ b/orchestrator/src/client/App.tsx @@ -116,8 +116,10 @@ export const App: React.FC = () => { const handleProcess = async (jobId: string) => { try { setProcessingJobId(jobId); - await api.processJob(jobId); - toast.success("Resume generated successfully"); + const job = jobs.find((item) => item.id === jobId); + const force = job?.status === "ready"; + await api.processJob(jobId, { force }); + toast.success(force ? "Resume regenerated successfully" : "Resume generated successfully"); await loadJobs(); } catch (error) { const message = error instanceof Error ? error.message : "Failed to process job"; diff --git a/orchestrator/src/client/api/client.ts b/orchestrator/src/client/api/client.ts index 66d0a1c..09dea72 100644 --- a/orchestrator/src/client/api/client.ts +++ b/orchestrator/src/client/api/client.ts @@ -56,8 +56,9 @@ export async function updateJob( }); } -export async function processJob(id: string): Promise { - return fetchApi(`/jobs/${id}/process`, { +export async function processJob(id: string, options?: { force?: boolean }): Promise { + const query = options?.force ? '?force=1' : ''; + return fetchApi(`/jobs/${id}/process${query}`, { method: 'POST', }); } diff --git a/orchestrator/src/client/components/JobCard.tsx b/orchestrator/src/client/components/JobCard.tsx index a3e01ea..a262371 100644 --- a/orchestrator/src/client/components/JobCard.tsx +++ b/orchestrator/src/client/components/JobCard.tsx @@ -68,7 +68,7 @@ export const JobCard: React.FC = ({ const hasPdf = !!job.pdfPath; const canApply = job.status === "ready"; - const canProcess = job.status === "discovered"; + const canProcess = ["discovered", "ready"].includes(job.status); const canReject = ["discovered", "ready"].includes(job.status); const jobLink = job.applicationLink || job.jobUrl; @@ -200,7 +200,7 @@ export const JobCard: React.FC = ({ ) : ( <> - Generate Resume + {job.status === "ready" ? "Regenerate PDF" : "Generate Resume"} )} diff --git a/orchestrator/src/client/components/JobTable.tsx b/orchestrator/src/client/components/JobTable.tsx index b9bdd52..759c70b 100644 --- a/orchestrator/src/client/components/JobTable.tsx +++ b/orchestrator/src/client/components/JobTable.tsx @@ -215,7 +215,7 @@ export const JobTable: React.FC = ({ const pdfHref = `/pdfs/resume_${job.id}.pdf`; const canApply = job.status === "ready"; - const canProcess = job.status === "discovered"; + const canProcess = ["discovered", "ready"].includes(job.status); const canReject = ["discovered", "ready"].includes(job.status); const isProcessing = processingJobId === job.id; const isSelected = selectedJobIds.has(job.id); @@ -240,7 +240,7 @@ export const JobTable: React.FC = ({ asChild variant="link" size="sm" - className="h-auto justify-start p-0 text-left leading-snug whitespace-normal break-words" + className="h-auto justify-start p-0 text-left leading-snug whitespace-normal wrap-break-word" > {job.title} @@ -248,7 +248,7 @@ export const JobTable: React.FC = ({ - + {job.employer} @@ -258,7 +258,7 @@ export const JobTable: React.FC = ({ - + {job.location || "—"} @@ -331,7 +331,11 @@ export const JobTable: React.FC = ({ disabled={isProcessing} > - {isProcessing ? "Processing..." : "Generate Resume"} + {isProcessing + ? "Processing..." + : job.status === "ready" + ? "Regenerate PDF" + : "Generate Resume"} )} diff --git a/orchestrator/src/server/api/routes.ts b/orchestrator/src/server/api/routes.ts index 124230a..0a8911f 100644 --- a/orchestrator/src/server/api/routes.ts +++ b/orchestrator/src/server/api/routes.ts @@ -133,7 +133,10 @@ apiRouter.patch('/jobs/:id', async (req: Request, res: Response) => { */ apiRouter.post('/jobs/:id/process', async (req: Request, res: Response) => { try { - const result = await processJob(req.params.id); + const forceRaw = req.query.force as string | undefined; + const force = forceRaw === '1' || forceRaw === 'true'; + + const result = await processJob(req.params.id, { force }); if (!result.success) { return res.status(400).json({ success: false, error: result.error }); diff --git a/orchestrator/src/server/pipeline/orchestrator.ts b/orchestrator/src/server/pipeline/orchestrator.ts index 0181dbb..b5e52cc 100644 --- a/orchestrator/src/server/pipeline/orchestrator.ts +++ b/orchestrator/src/server/pipeline/orchestrator.ts @@ -279,7 +279,10 @@ export async function runPipeline(config: Partial = {}): Promise /** * Process a single job (for manual processing). */ -export async function processJob(jobId: string): Promise<{ +export async function processJob( + jobId: string, + options?: { force?: boolean } +): Promise<{ success: boolean; error?: string; }> { @@ -290,14 +293,31 @@ export async function processJob(jobId: string): Promise<{ if (!job) { return { success: false, error: 'Job not found' }; } + + if (job.status !== 'discovered' && job.status !== 'ready') { + return { success: false, error: `Job cannot be processed from status: ${job.status}` }; + } const profile = await loadProfile(DEFAULT_PROFILE_PATH); // Mark as processing await jobsRepo.updateJob(job.id, { status: 'processing' }); + + // Re-score job suitability (AI) + // If forcing, always recompute; otherwise compute if missing. + if (options?.force || job.suitabilityScore == null || !job.suitabilityReason) { + const suitability = await scoreJobSuitability(job, profile); + await jobsRepo.updateJob(job.id, { + suitabilityScore: suitability.score, + suitabilityReason: suitability.reason, + }); + job.suitabilityScore = suitability.score; + job.suitabilityReason = suitability.reason; + } - // Generate summary if not already done - if (!job.tailoredSummary) { + // Generate summary (AI) + // If forcing, always recompute; otherwise compute if missing. + if (options?.force || !job.tailoredSummary) { console.log(' Generating summary...'); const summaryResult = await generateSummary( job.jobDescription || '', diff --git a/orchestrator/src/server/services/pdf.ts b/orchestrator/src/server/services/pdf.ts index 06b923d..77ca097 100644 --- a/orchestrator/src/server/services/pdf.ts +++ b/orchestrator/src/server/services/pdf.ts @@ -6,7 +6,7 @@ import { spawn } from 'child_process'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; -import { readFile, writeFile, mkdir, access } from 'fs/promises'; +import { readFile, writeFile, mkdir, access, unlink } from 'fs/promises'; import { existsSync } from 'fs'; import { getSetting } from '../repositories/settings.js'; @@ -101,6 +101,13 @@ export async function generatePdf( // Generate PDF using Python script - output directly to our data folder const outputFilename = `resume_${jobId}.pdf`; const outputPath = join(OUTPUT_DIR, outputFilename); + + // Ensure regeneration overwrites the old file if it exists. + try { + await unlink(outputPath); + } catch { + // Ignore if it doesn't exist or cannot be removed. + } await runPythonPdfGenerator(tempResumePath, outputFilename, OUTPUT_DIR);