Beta seems functional

This commit is contained in:
James Ketr 2025-06-11 15:58:40 -07:00
parent 74201d0a71
commit d2d0bb29ac
6 changed files with 46 additions and 18 deletions

View File

@ -220,7 +220,7 @@ export const navigationConfig: NavigationConfig = {
// },
{
id: "candidate-settings",
label: "Settings",
label: "System Information",
path: "/candidate/settings",
icon: <SettingsIcon />,
component: <Settings />,

View File

@ -307,14 +307,6 @@ const HowItWorks: React.FC = () => {
<Typography variant="h5" sx={{ mb: 3, fontWeight: 400 }}>
Here are your steps from zero-to-hero to see Backstory in action
</Typography>
<Chip
label="Beta Version"
sx={{
backgroundColor: 'action.active',
color: 'background.paper',
fontWeight: 'bold'
}}
/>
</Box>
</Container>
</HeroSection>
@ -395,7 +387,7 @@ const HowItWorks: React.FC = () => {
subtitle="Begin the AI analysis"
icon={<PlayArrowIcon sx={{ color: 'action.active' }} />}
description={[
"After selecting a candidate, you are ready to have Backstory perform the Job Analysis. During this phase, Backstory will take each of requirements that were extracted from the Job and match it against any information available about the selected candidate.",
"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."
]}
@ -415,7 +407,7 @@ const HowItWorks: React.FC = () => {
subtitle="Watch the magic happen"
icon={<AutoAwesomeIcon sx={{ color: 'action.active' }} />}
description={[
"Once you begin that action, the Start Skill Assessment button will grey out and the page will begin updating as it discovers information about the candidate. As it does its thing, 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 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."
]}
imageSrc={waitPng}

View File

@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react';
import CheckIcon from '@mui/icons-material/Check';
import {
Box,
Button,
@ -27,7 +28,9 @@ import {
FormControl,
InputLabel,
Switch,
FormControlLabel
FormControlLabel,
ToggleButton,
Checkbox
} from '@mui/material';
import { styled } from '@mui/material/styles';
import {
@ -100,6 +103,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (props: BackstoryPageProp
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
const { user, updateUserData, apiClient } = useAuth();
const [isPublic, setIsPublic] = React.useState(true);
// Check if user is a candidate
const candidate = user?.userType === 'candidate' ? user as Types.Candidate : null;
@ -120,7 +124,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (props: BackstoryPageProp
// Form data state
const [formData, setFormData] = useState<Partial<Types.Candidate>>({});
const [profileImage, setProfileImage] = useState<string | null>(null);
const [profileImage, setProfileImage] = useState<string>('');
// Dialog states
const [skillDialog, setSkillDialog] = useState(false);
@ -146,7 +150,12 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (props: BackstoryPageProp
useEffect(() => {
if (candidate) {
setFormData(candidate);
setProfileImage(candidate.profileImage || null);
if (candidate.profileImage) {
setProfileImage(`/api/1.0/candidates/profile/${candidate.username}`);
} else {
setProfileImage('');
}
console.log({ isPublic: candidate.isPublic });
}
}, [candidate]);
@ -177,6 +186,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (props: BackstoryPageProp
const handleImageUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) {
if (await apiClient.uploadCandidateProfile(e.target.files[0])) {
setProfileImage(URL.createObjectURL(e.target.files[0]));
candidate.profileImage = 'profile.' + e.target.files[0].name.replace(/^.*\./, '');
console.log(`Set profile image to: ${candidate.profileImage}`);
updateUserData(candidate);
@ -265,7 +275,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (props: BackstoryPageProp
<Box sx={{ textAlign: 'center', mb: { xs: 1, sm: 2 } }}>
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<Avatar
src={profileImage ? `/api/1.0/candidates/profile/${candidate.username}` : ''}
src={profileImage}
sx={{
width: { xs: 80, sm: 120 },
height: { xs: 80, sm: 120 },
@ -298,6 +308,25 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (props: BackstoryPageProp
</Box>
</Box>
<Box className="entry" sx={{ display: 'flex', justifyContent: 'center', alignContent: 'center', gap: 1, "& span": { mb: 0 } }}>
{editMode.basic ? (
<FormControlLabel sx={{ display: 'flex' }} control={
<Switch
checked={formData.isPublic}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => handleInputChange('isPublic', event.target.checked)} />}
label={
formData.isPublic ?
'Your account will appear in candidate lists and searches on Backstory.' :
`Your account will not be listed as a candidate. You can still share your profile by users navigating to '/u/${candidate.username}'.`
} />
) : (<>{
candidate.isPublic ?
'Your account will appear in candidate lists and searches on Backstory.' :
`Your account will not be listed as a candidate. You can still share your profile by users navigating to '/u/${candidate.username}'.`
}</>)}
</Box>
<Box className="entry">
{editMode.basic ? (
<TextField

View File

@ -1,6 +1,6 @@
// Generated TypeScript types from Pydantic models
// Source: src/backend/models.py
// Generated on: 2025-06-10T17:14:56.968033
// Generated on: 2025-06-11T22:14:30.373041
// DO NOT EDIT MANUALLY - This file is auto-generated
// ============================
@ -207,6 +207,7 @@ export interface Candidate {
jobApplications?: Array<JobApplication>;
rags?: Array<RagEntry>;
ragContentSize: number;
isPublic: boolean;
}
export interface CandidateAI {
@ -241,6 +242,7 @@ export interface CandidateAI {
jobApplications?: Array<any>;
rags?: Array<RagEntry>;
ragContentSize: number;
isPublic: boolean;
isAI: boolean;
age?: number;
gender?: "female" | "male";
@ -634,6 +636,7 @@ export interface Guest {
ipAddress?: string;
userAgent?: string;
ragContentSize: number;
isPublic: boolean;
}
export interface GuestCleanupRequest {

View File

@ -3121,6 +3121,7 @@ async def get_candidates(
sortBy: Optional[str] = Query(None, alias="sortBy"),
sortOrder: str = Query("desc", pattern="^(asc|desc)$", alias="sortOrder"),
filters: Optional[str] = Query(None),
current_user = Depends(get_current_user),
database: RedisDatabase = Depends(get_database)
):
"""Get paginated list of candidates"""
@ -3133,6 +3134,7 @@ async def get_candidates(
# Get all candidates from Redis
all_candidates_data = await database.get_all_candidates()
candidates_list = [Candidate.model_validate(data) if not data.get("is_AI") else CandidateAI.model_validate(data) for data in all_candidates_data.values()]
candidates_list = [c for c in candidates_list if c.is_public or c.id == current_user.id]
paginated_candidates, total = filter_and_paginate(
candidates_list, page, limit, sortBy, sortOrder, filter_dict

View File

@ -604,6 +604,7 @@ class Candidate(BaseUser):
job_applications: Optional[List["JobApplication"]] = Field(None, alias="jobApplications")
rags: List[RagEntry] = Field(default_factory=list)
rag_content_size : int = 0
is_public: bool = Field(default=True, alias="isPublic")
class CandidateAI(Candidate):
user_type: UserType = Field(UserType.CANDIDATE, alias="userType")
@ -634,6 +635,7 @@ class Guest(BaseUser):
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC), alias="createdAt")
user_agent: Optional[str] = Field(None, alias="userAgent")
rag_content_size: int = 0
is_public: bool = Field(default=False, alias="isPublic")
model_config = {
"populate_by_name": True, # Allow both field names and aliases
"use_enum_values": True # Use enum values instead of names