can regenerate pdfs, because sometimes the ai gets it wrong, and we need to help it out

This commit is contained in:
DaKheera47 2025-12-17 17:48:03 +00:00
parent deb30efa44
commit 8a236c0be9
7 changed files with 53 additions and 16 deletions

View File

@ -116,8 +116,10 @@ export const App: React.FC = () => {
const handleProcess = async (jobId: string) => { const handleProcess = async (jobId: string) => {
try { try {
setProcessingJobId(jobId); setProcessingJobId(jobId);
await api.processJob(jobId); const job = jobs.find((item) => item.id === jobId);
toast.success("Resume generated successfully"); const force = job?.status === "ready";
await api.processJob(jobId, { force });
toast.success(force ? "Resume regenerated successfully" : "Resume generated successfully");
await loadJobs(); await loadJobs();
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : "Failed to process job"; const message = error instanceof Error ? error.message : "Failed to process job";

View File

@ -56,8 +56,9 @@ export async function updateJob(
}); });
} }
export async function processJob(id: string): Promise<Job> { export async function processJob(id: string, options?: { force?: boolean }): Promise<Job> {
return fetchApi<Job>(`/jobs/${id}/process`, { const query = options?.force ? '?force=1' : '';
return fetchApi<Job>(`/jobs/${id}/process${query}`, {
method: 'POST', method: 'POST',
}); });
} }

View File

@ -68,7 +68,7 @@ export const JobCard: React.FC<JobCardProps> = ({
const hasPdf = !!job.pdfPath; const hasPdf = !!job.pdfPath;
const canApply = job.status === "ready"; 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 canReject = ["discovered", "ready"].includes(job.status);
const jobLink = job.applicationLink || job.jobUrl; const jobLink = job.applicationLink || job.jobUrl;
@ -200,7 +200,7 @@ export const JobCard: React.FC<JobCardProps> = ({
) : ( ) : (
<> <>
<RefreshCcw className="mr-2 h-4 w-4" /> <RefreshCcw className="mr-2 h-4 w-4" />
Generate Resume {job.status === "ready" ? "Regenerate PDF" : "Generate Resume"}
</> </>
)} )}
</Button> </Button>

View File

@ -215,7 +215,7 @@ export const JobTable: React.FC<JobTableProps> = ({
const pdfHref = `/pdfs/resume_${job.id}.pdf`; const pdfHref = `/pdfs/resume_${job.id}.pdf`;
const canApply = job.status === "ready"; 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 canReject = ["discovered", "ready"].includes(job.status);
const isProcessing = processingJobId === job.id; const isProcessing = processingJobId === job.id;
const isSelected = selectedJobIds.has(job.id); const isSelected = selectedJobIds.has(job.id);
@ -240,7 +240,7 @@ export const JobTable: React.FC<JobTableProps> = ({
asChild asChild
variant="link" variant="link"
size="sm" 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"
> >
<a href={jobLink} target="_blank" rel="noopener noreferrer"> <a href={jobLink} target="_blank" rel="noopener noreferrer">
{job.title} {job.title}
@ -248,7 +248,7 @@ export const JobTable: React.FC<JobTableProps> = ({
</Button> </Button>
</TableCell> </TableCell>
<TableCell className="align-middle whitespace-normal break-words"> <TableCell className="align-middle whitespace-normal wrap-break-word">
{job.employer} {job.employer}
</TableCell> </TableCell>
@ -258,7 +258,7 @@ export const JobTable: React.FC<JobTableProps> = ({
</Badge> </Badge>
</TableCell> </TableCell>
<TableCell className="align-middle whitespace-normal break-words text-muted-foreground"> <TableCell className="align-middle whitespace-normal wrap-break-word text-muted-foreground">
{job.location || "—"} {job.location || "—"}
</TableCell> </TableCell>
@ -331,7 +331,11 @@ export const JobTable: React.FC<JobTableProps> = ({
disabled={isProcessing} disabled={isProcessing}
> >
<RefreshCcw className="mr-2 h-4 w-4" /> <RefreshCcw className="mr-2 h-4 w-4" />
{isProcessing ? "Processing..." : "Generate Resume"} {isProcessing
? "Processing..."
: job.status === "ready"
? "Regenerate PDF"
: "Generate Resume"}
</DropdownMenuItem> </DropdownMenuItem>
)} )}

View File

@ -133,7 +133,10 @@ apiRouter.patch('/jobs/:id', async (req: Request, res: Response) => {
*/ */
apiRouter.post('/jobs/:id/process', async (req: Request, res: Response) => { apiRouter.post('/jobs/:id/process', async (req: Request, res: Response) => {
try { 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) { if (!result.success) {
return res.status(400).json({ success: false, error: result.error }); return res.status(400).json({ success: false, error: result.error });

View File

@ -279,7 +279,10 @@ export async function runPipeline(config: Partial<PipelineConfig> = {}): Promise
/** /**
* Process a single job (for manual processing). * 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; success: boolean;
error?: string; error?: string;
}> { }> {
@ -290,14 +293,31 @@ export async function processJob(jobId: string): Promise<{
if (!job) { if (!job) {
return { success: false, error: 'Job not found' }; 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); const profile = await loadProfile(DEFAULT_PROFILE_PATH);
// Mark as processing // Mark as processing
await jobsRepo.updateJob(job.id, { status: '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 // Generate summary (AI)
if (!job.tailoredSummary) { // If forcing, always recompute; otherwise compute if missing.
if (options?.force || !job.tailoredSummary) {
console.log(' Generating summary...'); console.log(' Generating summary...');
const summaryResult = await generateSummary( const summaryResult = await generateSummary(
job.jobDescription || '', job.jobDescription || '',

View File

@ -6,7 +6,7 @@
import { spawn } from 'child_process'; import { spawn } from 'child_process';
import { join, dirname } from 'path'; import { join, dirname } from 'path';
import { fileURLToPath } from 'url'; 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 { existsSync } from 'fs';
import { getSetting } from '../repositories/settings.js'; 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 // Generate PDF using Python script - output directly to our data folder
const outputFilename = `resume_${jobId}.pdf`; const outputFilename = `resume_${jobId}.pdf`;
const outputPath = join(OUTPUT_DIR, outputFilename); 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); await runPythonPdfGenerator(tempResumePath, outputFilename, OUTPUT_DIR);