diff --git a/frontend/src/assets/final-resume.png b/frontend/src/assets/final-resume.png index 3f5cc3e..71c3170 100755 Binary files a/frontend/src/assets/final-resume.png and b/frontend/src/assets/final-resume.png differ diff --git a/frontend/src/assets/select-a-candidate.png b/frontend/src/assets/select-a-candidate.png index d9077c5..b5095b8 100755 Binary files a/frontend/src/assets/select-a-candidate.png and b/frontend/src/assets/select-a-candidate.png differ diff --git a/frontend/src/assets/select-a-job.png b/frontend/src/assets/select-a-job.png index 0394e58..bc3025c 100755 Binary files a/frontend/src/assets/select-a-job.png and b/frontend/src/assets/select-a-job.png differ diff --git a/frontend/src/assets/select-start-analysis.png b/frontend/src/assets/select-start-analysis.png index fd676e4..6a53759 100755 Binary files a/frontend/src/assets/select-start-analysis.png and b/frontend/src/assets/select-start-analysis.png differ diff --git a/frontend/src/assets/wait.png b/frontend/src/assets/wait.png index 11f123b..2adc50d 100755 Binary files a/frontend/src/assets/wait.png and b/frontend/src/assets/wait.png differ diff --git a/frontend/src/components/JobMatchAnalysis.tsx b/frontend/src/components/JobMatchAnalysis.tsx index b3794e6..32cfda0 100644 --- a/frontend/src/components/JobMatchAnalysis.tsx +++ b/frontend/src/components/JobMatchAnalysis.tsx @@ -293,6 +293,9 @@ const JobMatchAnalysis: React.FC = (props: JobAnalysisProps) = domain: requirements[i].domain, }; } else { + if (firstRun) { + continue; + } setSkillMatches(prev => { const updated = [...prev]; updated[i] = { ...updated[i], status: 'pending' }; @@ -353,10 +356,10 @@ const JobMatchAnalysis: React.FC = (props: JobAnalysisProps) = setPercentage(0); fetchMatchData(firstRun).then(() => { - setFirstRun(false); setAnalyzing(false); setStartAnalysis(false); }); + setFirstRun(false); }, [ job, startAnalysis, @@ -507,7 +510,7 @@ const JobMatchAnalysis: React.FC = (props: JobAnalysisProps) = onClick={beginAnalysis} variant="contained" > - {analyzing ? 'Assessment in Progress' : 'Assess Unknown Skills'} + {analyzing ? 'Assessment in Progress' : 'Analyze Waiting Skills'} diff --git a/frontend/src/pages/HowItWorks.tsx b/frontend/src/pages/HowItWorks.tsx index fa69458..cd1e87c 100644 --- a/frontend/src/pages/HowItWorks.tsx +++ b/frontend/src/pages/HowItWorks.tsx @@ -29,6 +29,7 @@ import waitPng from 'assets/wait.png'; import finalResumePng from 'assets/final-resume.png'; import { Beta } from 'components/ui/Beta'; +import { Quote } from '@uiw/react-json-view'; // Styled components matching HomePage patterns const HeroSection = styled(Box)(({ theme }) => ({ @@ -77,8 +78,8 @@ const ImageContainer = styled(Box)(({ theme }) => ({ const steps = [ 'Select Job Analysis', - 'Choose a Job', 'Select a Candidate', + 'Choose a Job', 'Start Assessment', 'Review Results', 'Generate Resume', @@ -367,7 +368,7 @@ const HowItWorks: React.FC = () => { subtitle="Navigate to the main feature" icon={} description={[ - "Select 'Job Analysis' from the menu. This takes you to the interactive Job Analysis page, where you will get to evaluate a candidate for a selected job.", + "Select 'Job Analysis' from the menu. This takes you to the interactive Job Analysis page, where you will get to evaluate the requirements for a job and perform a Backstory assisted evaluation of a candidate, or yourself!", ]} imageSrc={selectJobAnalysisPng} imageAlt="Select Job Analysis from menu" @@ -375,7 +376,27 @@ const HowItWorks: React.FC = () => { - {/* Step 2: Select a Job */} + {/* Step 2: Select a Candidate */} + + + } + description={[ + 'First, select a candidate. If you create an account and are logged in, it will default to selecting you. In addition to myself (James), there are several candidates which AI has generated. Each has a unique skillset and can be used to test out the system.', + 'Once you\'ve selected a candidate, click "Next".', + ]} + imageSrc={selectACandidatePng} + imageAlt="Select a candidate from the available profiles" + note="If you create an account, you can opt-in to have your account show up for others to view as well, or keep it private for just your own resume generation and job research." + reversed={true} + /> + + + + {/* Step 3: Select a Job */} { subtitle="Pick from existing job postings" icon={} description={[ - 'Once on the Job Analysis Page, explore a little bit and then select one of the jobs. The requirements and information provided on Backstory are extracted from job postings that users have pasted as a job description or uploaded from a PDF.', + 'After selecting a candidate, explore a little bit and then select one of the jobs. The requirements and information provided on Backstory are extracted from job postings that users have pasted as a job description or uploaded from a PDF.', + 'Clicking a job will give you more details about it.', + 'When you\'re happy with your selection, click "Next".', ]} imageSrc={selectAJobPng} imageAlt="Select a job from the available options" note="You can create your own job postings once you create an account. Until then, you need to select one that already exists." - reversed={true} - /> - - - - {/* Step 3: Select a Candidate */} - - - } - description={[ - 'Now that you have a Job selected, you need to select a candidate. In addition to myself (James), there are several candidates which AI has generated. Each has a unique skillset and can be used to test out the system.', - ]} - imageSrc={selectACandidatePng} - imageAlt="Select a candidate from the available profiles" - note="If you create an account, you can opt-in to have your account show up for others to view as well, or keep it private for just your own resume generation and job research." /> @@ -423,7 +427,7 @@ const HowItWorks: React.FC = () => { description={[ 'After selecting a candidate, you are ready to have Backstory perform the Job Analysis. During this phase, Backstory will take each of requirements extracted from the Job and match it against information about the selected candidate.', 'This could be as little as a simple resume, or as complete as a full work history. Backstory performs similarity searches to identify key elements from the candidate that pertain to a given skill and provides a graded response.', - 'To see that in action, click the "Start Skill Assessment" button.', + 'To see that in action, click the "Start Skill Assessment" button. Backstory will save the results, only regenerating the information if the candidate updates their uploaded content.', ]} imageSrc={selectStartAnalysisPng} imageAlt="Start the skill assessment process" @@ -443,6 +447,7 @@ const HowItWorks: React.FC = () => { description={[ 'Once you begin that action, the Start Skill Assessment button will grey out and the page will begin updating as it collates information about the candidate. As Backstory performs its magic, you can monitor the progress and explore the different identified skills to see how or why a candidate does or does not have that skill.', 'Once it is done, you can see the final Overall Match. This is a weighted score based on amount of evidence a skill had, whether the skill was required or preferred, and other metrics.', + 'After looking at the results, you can click "Next" to proceed to the final step--resume generation.', ]} imageSrc={waitPng} imageAlt="Wait for the analysis to complete and review results" @@ -461,6 +466,7 @@ const HowItWorks: React.FC = () => { description={[ 'The final step is creating the custom resume for the Candidate tailored to the particular Job. On the bottom right you can click "Next" to have Backstory generate the custom resume.', "Note that the resume focuses on identifying key areas from the Candidate's work history that align with skills which were extracted from the original job posting.", + 'After the initial resume is generated, if you are logged in, you can select "Save Resume and Edit" to take you to the resume editor.', ]} imageSrc={finalResumePng} imageAlt="Generated custom resume tailored to the job" diff --git a/frontend/src/pages/JobAnalysisPage.tsx b/frontend/src/pages/JobAnalysisPage.tsx index 7f7d4ea..9f2a595 100644 --- a/frontend/src/pages/JobAnalysisPage.tsx +++ b/frontend/src/pages/JobAnalysisPage.tsx @@ -78,7 +78,7 @@ const capitalize = (str: string): string => { // Main component const JobAnalysisPage: React.FC = () => { const theme = useTheme(); - const { user, guest } = useAuth(); + const { user, guest, apiClient } = useAuth(); const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate(); const { selectedJob, setSelectedJob } = useSelectedJob(); @@ -95,6 +95,22 @@ const JobAnalysisPage: React.FC = () => { const [activeStep, setActiveStep] = useState(user === null ? 0 : 1); const maxStep = 4; + useEffect(() => { + if (selectedCandidate === null && user !== null) { + apiClient + .getCandidate(user.id || '') + .then((candidate: Candidate | null) => { + if (candidate) { + setSelectedCandidate(candidate); + } + }) + .catch((err: Error) => { + console.error('Error fetching candidate:', err); + setError('Failed to fetch candidate information.'); + }); + } + }, [user, apiClient, selectedCandidate, setSelectedCandidate]); + const getMissingStepRequirement = useCallback( (step: number) => { switch (step) { @@ -393,7 +409,7 @@ const JobAnalysisPage: React.FC = () => { }} > Candidate Selection - {user !== null && ( + {selectedCandidate !== null && ( = () => { width: '100%', }} > + Name - {user?.fullName} + {selectedCandidate?.fullName} )} diff --git a/frontend/src/types/types.ts b/frontend/src/types/types.ts index f2521b2..9e1bf76 100644 --- a/frontend/src/types/types.ts +++ b/frontend/src/types/types.ts @@ -1,6 +1,6 @@ // Generated TypeScript types from Pydantic models // Source: src/backend/models.py -// Generated on: 2025-07-01T21:24:10.743667 +// Generated on: 2025-07-01T22:28:07.615325 // DO NOT EDIT MANUALLY - This file is auto-generated // ============================ @@ -53,7 +53,7 @@ export type SkillLevel = "beginner" | "intermediate" | "advanced" | "expert"; export type SkillStatus = "pending" | "complete" | "waiting" | "error"; -export type SkillStrength = "strong" | "moderate" | "weak" | "none"; +export type SkillStrength = "strong" | "moderate" | "weak" | "none" | "unknown"; export type SocialPlatform = "linkedin" | "twitter" | "github" | "dribbble" | "behance" | "website" | "other"; @@ -1041,7 +1041,7 @@ export interface SkillAssessment { skill: string; skillModified?: string; evidenceFound: boolean; - evidenceStrength: "strong" | "moderate" | "weak" | "none"; + evidenceStrength: "strong" | "moderate" | "weak" | "none" | "unknown"; assessment: string; description: string; evidenceDetails?: Array; diff --git a/src/backend/models.py b/src/backend/models.py index 49b7f58..073ae84 100644 --- a/src/backend/models.py +++ b/src/backend/models.py @@ -97,6 +97,7 @@ class SkillStrength(str, Enum): MODERATE = "moderate" WEAK = "weak" NONE = "none" + UNKNOWN = "unknown" class EvidenceDetail(BaseModel): diff --git a/src/backend/routes/candidates.py b/src/backend/routes/candidates.py index becbdb3..e7edd40 100644 --- a/src/backend/routes/candidates.py +++ b/src/backend/routes/candidates.py @@ -59,6 +59,7 @@ from models import ( RagContentMetadata, RagContentResponse, SkillAssessment, + SkillStrength, UserType, ) from utils.dependencies import ( @@ -1440,7 +1441,7 @@ async def get_candidate_chat_summary( @router.post("/job-analysis") async def post_job_analysis( request: JobAnalysis = Body(...), - current_user=Depends(get_current_user), + current_user=Depends(get_current_user_or_guest), database: RedisDatabase = Depends(get_database), ): """Get chat activity summary for a candidate""" @@ -1467,7 +1468,6 @@ async def post_job_analysis( job = Job.model_validate(job_data) - uninitalized = False requirements = get_requirements_list(job) logger.info( @@ -1486,8 +1486,15 @@ async def post_job_analysis( logger.info(f"💾 No cached skill match data: {cache_key}, {candidate.id}, {skill}") continue else: - logger.info(f"✅ Assessment found for {candidate.username} skill {assessment.skill}: {cache_key}") - matched_skills.append(assessment) + if assessment.evidence_strength != SkillStrength.UNKNOWN: + logger.info( + f"✅ Assessment found for {candidate.username} skill {assessment.skill}: {assessment.evidence_strength}" + ) + matched_skills.append(assessment) + else: + logger.info( + f"❌ Assessment for {candidate.username} skill {assessment.skill} is unknown, skipping." + ) request.skills = matched_skills return create_success_response(request.model_dump(by_alias=True))