Integrating new resume generator

This commit is contained in:
James Ketr 2025-06-09 11:57:12 -07:00
parent 1fbc5317d3
commit dd0ab5eda6
5 changed files with 103 additions and 40 deletions

View File

@ -34,6 +34,7 @@ import * as Types from 'types/types';
interface JobAnalysisProps extends BackstoryPageProps {
job: Job;
candidate: Candidate;
onAnalysisComplete: (skills: SkillAssessment[]) => void;
}
const defaultMessage: ChatMessage = {
@ -50,6 +51,7 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
const {
job,
candidate,
onAnalysisComplete,
} = props
const { apiClient } = useAuth();
const { setSnack } = useAppState();
@ -138,7 +140,7 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
return;
}
const fetchMatchData = async () => {
const fetchMatchData = async (skills: SkillAssessment[]) => {
if (requirements.length === 0) return;
// Process requirements one by one
@ -153,6 +155,7 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
const request: any = await apiClient.candidateMatchForRequirement(candidate.id || '', requirements[i].requirement, skillMatchHandlers);
const result = await request.promise;
const skillMatch = result.skillAssessment;
skills.push(skillMatch);
setMatchStatus('');
let matchScore: number = 0;
switch (skillMatch.evidenceStrength.toUpperCase()) {
@ -201,8 +204,13 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
};
setAnalyzing(true);
fetchMatchData().then(() => { setAnalyzing(false); setStartAnalysis(false) });
}, [job, startAnalysis, analyzing, requirements, loadingRequirements]);
const skills: SkillAssessment[] = [];
fetchMatchData(skills).then(() => {
setAnalyzing(false);
setStartAnalysis(false);
onAnalysisComplete && onAnalysisComplete(skills);
});
}, [job, onAnalysisComplete, startAnalysis, analyzing, requirements, loadingRequirements]);
// Get color based on match score
const getMatchColor = (score: number): string => {
@ -270,7 +278,7 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
<Grid size={{ xs: 12 }} sx={{ mt: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2, gap: 1 }}>
{<Button disabled={analyzing || startAnalysis} onClick={beginAnalysis} variant="contained">Start Analysis</Button>}
{<Button disabled={analyzing || startAnalysis} onClick={beginAnalysis} variant="contained">Start Skill Assessment</Button>}
{overallScore !== 0 && <>
<Typography variant="h5" component="h2" sx={{ mr: 2 }}>
Overall Match:

View File

@ -0,0 +1,51 @@
import React, { useState, useCallback, useRef } from 'react';
import {
Tabs,
Tab,
Box,
Button,
} from '@mui/material';
import { Job, Candidate, SkillAssessment } from "types/types";
import JsonView from '@uiw/react-json-view';
interface ResumeGeneratorProps {
job: Job;
candidate: Candidate;
skills: SkillAssessment[];
onComplete?: (resume: string) => void;
}
const ResumeGenerator: React.FC<ResumeGeneratorProps> = (props: ResumeGeneratorProps) => {
const { job, candidate, skills, onComplete } = props;
const [resume, setResume] = useState<string>('Generated resume goes here...');
const [generating, setGenerating] = useState<boolean>(false);
// State for editing job description
const generateResume = () => {
setResume('Generation begins...');
setGenerating(true);
setTimeout(() => {
setGenerating(false);
setResume('Generation complete');
onComplete && onComplete(resume);
}, 3000);
};
return (
<Box
className="ResumeGenerator"
sx={{display: "flex", flexDirection: "row", width: "100%"}}>
<JsonView value={skills}/>
<Box sx={{display: "flex", flexDirection: "column"}}>
<Box>{resume}</Box>
<Button disabled={generating} onClick={generateResume} variant="contained">Generate Resume</Button>
</Box>
</Box>
)
};
export {
ResumeGenerator
};

View File

@ -50,17 +50,6 @@ const CandidatePicker = (props: CandidatePickerProps) => {
return (
<Box sx={{display: "flex", flexDirection: "column"}}>
{user?.isAdmin &&
<Box sx={{ p: 1, textAlign: "center" }}>
Not seeing a candidate you like?
<Button
variant="contained"
sx={{ m: 1 }}
onClick={() => { navigate('/generate-candidate') }}>
Generate your own perfect AI candidate!
</Button>
</Box>
}
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap", justifyContent: "center" }}>
{candidates?.map((u, i) =>
<Box key={`${u.username}`}

View File

@ -72,20 +72,23 @@ const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
<strong>Title:</strong> {job.title}
</Typography>
}
{/* {job.datePosted &&
<Typography variant="body2" sx={{ mb: 1 }}>
<strong>Posted:</strong> {job.datePosted.toISOString()}
</Typography>
} */}
{job.company &&
<Typography variant="body2" sx={{ mb: 1 }}>
<strong>Company:</strong> {job.company}
</Typography>
}
{job.summary && <Typography variant="body2">
<strong>Summary:</strong> {job.summary}
</Typography>
}
{job.company &&
<Typography variant="body2" sx={{ mb: 1 }}>
<strong>Company:</strong> {job.company}
</Typography>
}
{job.summary && <Typography variant="body2">
<strong>Summary:</strong> {job.summary}
</Typography>
}
{job.createdAt && <Typography variant="body2">
<strong>Created:</strong> {job.createdAt.toISOString()}
</Typography>
}
{ job.owner && <Typography variant="body2">
<strong>Created by:</strong> {job.owner.fullName}
</Typography>
}
</>}
</CardContent>
<CardActions>

View File

@ -22,7 +22,7 @@ import PersonIcon from '@mui/icons-material/Person';
import WorkIcon from '@mui/icons-material/Work';
import AssessmentIcon from '@mui/icons-material/Assessment';
import { JobMatchAnalysis } from 'components/JobMatchAnalysis';
import { Candidate, Job } from "types/types";
import { Candidate, Job, SkillAssessment } from "types/types";
import { useNavigate } from 'react-router-dom';
import { BackstoryPageProps } from 'components/BackstoryTab';
import { useAuth } from 'hooks/AuthContext';
@ -35,6 +35,8 @@ import { CandidatePicker } from 'components/ui/CandidatePicker';
import { JobPicker } from 'components/ui/JobPicker';
import { JobCreator } from 'components/JobCreator';
import { LoginRestricted } from 'components/ui/LoginRestricted';
import JsonView from '@uiw/react-json-view';
import { ResumeGenerator } from 'components/ResumeGenerator';
function WorkAddIcon() {
return (
@ -65,19 +67,14 @@ function WorkAddIcon() {
const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps) => {
const theme = useTheme();
const { user, guest } = useAuth();
const navigate = useNavigate();
const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate()
const { selectedJob, setSelectedJob } = useSelectedJob();
const { setSnack } = useAppState();
// State management
const [activeStep, setActiveStep] = useState(0);
const [analysisStarted, setAnalysisStarted] = useState(false);
const [error, setError] = useState<string | null>(null);
const [jobTab, setJobTab] = useState<string>('load');
const [skills, setSkills] = useState<SkillAssessment[] | null>(null)
useEffect(() => {
console.log({ activeStep, selectedCandidate, selectedJob });
if (!selectedCandidate) {
if (activeStep !== 0) {
setActiveStep(0);
@ -109,8 +106,9 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
return;
}
if (activeStep === 2) {
setAnalysisStarted(true);
if (activeStep === 2 && !skills) {
setError('Skill assessment must be complete before continuing.');
return;
}
setActiveStep((prevActiveStep) => prevActiveStep + 1);
@ -128,15 +126,19 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
};
const moveToStep = (step: number) => {
console.log(`Move to ${step}`)
switch (step) {
case 0: /* Select candidate */
setSelectedCandidate(null);
setSelectedJob(null);
setSkills(null);
break;
case 1: /* Select Job */
setSelectedJob(null);
setSkills(null);
break;
case 2: /* Job Analysis */
setSkills(null);
break;
case 3: /* Generate Resume */
break;
@ -198,6 +200,10 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
);
}
const onAnalysisComplete = (skills: SkillAssessment[]) => {
setSkills(skills);
};
// Render function for the analysis step
const renderAnalysis = () => (
<Box sx={{ mt: 3 }}>
@ -205,6 +211,7 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
<JobMatchAnalysis
job={selectedJob}
candidate={selectedCandidate}
onAnalysisComplete={onAnalysisComplete}
/>
)}
</Box>
@ -212,7 +219,12 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
const renderResume = () => (
<Box sx={{ mt: 3 }}>
{selectedCandidate && <ComingSoon>Resume Builder</ComingSoon>}
{skills && selectedCandidate && selectedJob &&
<ResumeGenerator
job={selectedJob}
candidate={selectedCandidate}
skills={skills}
/>}
</Box>
);
@ -298,7 +310,7 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
</Button>
) : (
<Button onClick={handleNext} variant="contained">
{activeStep === steps[steps.length - 1].index - 1 ? 'Done' : 'Next'}
{activeStep === steps.length - 1 ? 'Done' : 'Next'}
</Button>
)}
</Box>