Added resume styles

This commit is contained in:
James Ketr 2025-07-08 12:46:59 -07:00
parent cacbb0fd0f
commit d66e1ee1e4
9 changed files with 596 additions and 82 deletions

View File

@ -29,6 +29,7 @@
"@uiw/react-markdown-editor": "^6.1.4",
"country-state-city": "^3.2.1",
"jsonrepair": "^3.12.0",
"libphonenumber-js": "^1.12.9",
"lodash": "^4.17.21",
"lucide-react": "^0.511.0",
"luxon": "^3.6.1",

View File

@ -24,6 +24,7 @@
"@uiw/react-markdown-editor": "^6.1.4",
"country-state-city": "^3.2.1",
"jsonrepair": "^3.12.0",
"libphonenumber-js": "^1.12.9",
"lodash": "^4.17.21",
"lucide-react": "^0.511.0",
"luxon": "^3.6.1",

View File

@ -14,7 +14,7 @@ interface CandidatePickerProps extends BackstoryElementProps {
}
const CandidatePicker = (props: CandidatePickerProps): JSX.Element => {
const { onSelect, sx } = props;
const { onSelect } = props;
const { apiClient } = useAuth();
const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate();
const { setSnack } = useAppState();

View File

@ -73,7 +73,7 @@ interface JobsViewProps {
onJobSelect?: (selectedJobs: Types.Job[]) => void;
onJobView?: (job: Types.Job) => void;
onJobEdit?: (job: Types.Job) => void;
onJobDelete?: (job: Types.Job) => void;
onJobDelete?: (job: Types.Job) => Promise<void>;
selectable?: boolean;
showActions?: boolean;
showDetailsPanel?: boolean;
@ -609,7 +609,13 @@ const JobsView: React.FC<JobsViewProps> = ({
)}
{onJobDelete && (
<Tooltip title="Delete Job">
<IconButton size="small" onClick={(): void => onJobDelete(job)}>
<IconButton
size="small"
onClick={async (): Promise<void> => {
await onJobDelete(job);
fetchJobs(0, searchQuery);
}}
>
<DeleteIcon fontSize="small" />
</IconButton>
</Tooltip>

View File

@ -136,4 +136,17 @@
border-radius: 5px;
overflow-x: auto;
margin: 1em 0;
}
}
.BackstoryResumeHeader {
gap: 1rem;
display: flex;
flex-direction: column;
/* border: 3px solid orange; */
}
.BackstoryResumeHeader p {
/* border: 3px solid purple; */
margin: 0;
}

View File

@ -22,6 +22,12 @@ import {
Tabs,
Tab,
Paper,
FormControl,
Select,
MenuItem,
InputLabel,
Chip,
Theme,
} from '@mui/material';
import PrintIcon from '@mui/icons-material/Print';
import {
@ -34,11 +40,17 @@ import {
Person as PersonIcon,
Schedule as ScheduleIcon,
ModelTraining,
Style as StyleIcon,
Email as EmailIcon,
Phone as PhoneIcon,
LocationOn as LocationIcon,
// Language as WebsiteIcon,
} from '@mui/icons-material';
import InputIcon from '@mui/icons-material/Input';
import TuneIcon from '@mui/icons-material/Tune';
import PreviewIcon from '@mui/icons-material/Preview';
import EditDocumentIcon from '@mui/icons-material/EditDocument';
import { parsePhoneNumberFromString } from 'libphonenumber-js';
import { useReactToPrint } from 'react-to-print';
@ -62,6 +74,450 @@ interface ResumeInfoProps {
variant?: 'minimal' | 'small' | 'normal' | 'all' | null;
}
// Resume Style Definitions
interface ResumeStyle {
name: string;
description: string;
headerStyle: SxProps<Theme>;
footerStyle: SxProps<Theme>;
contentStyle: SxProps<Theme>;
markdownStyle: SxProps<Theme>;
color: {
primary: string;
secondary: string;
accent: string;
text: string;
background: string;
};
}
const resumeStyles: Record<string, ResumeStyle> = {
classic: {
name: 'Classic',
description: 'Traditional, professional serif design',
headerStyle: {
fontFamily: '"Times New Roman", Times, serif',
borderBottom: '2px solid #2c3e50',
paddingBottom: 2,
marginBottom: 3,
},
footerStyle: {
fontFamily: '"Times New Roman", Times, serif',
borderTop: '2px solid #2c3e50',
paddingTop: 2,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
textTransform: 'uppercase',
alignContent: 'center',
fontSize: '0.8rem',
pb: 2,
mb: 2,
},
contentStyle: {
fontFamily: '"Times New Roman", Times, serif',
lineHeight: 1.6,
color: '#2c3e50',
},
markdownStyle: {
fontFamily: '"Times New Roman", Times, serif',
'& h1, & h2, & h3': {
fontFamily: '"Times New Roman", Times, serif',
color: '#2c3e50',
borderBottom: '1px solid #bdc3c7',
paddingBottom: 1,
marginBottom: 2,
},
'& p, & li': {
lineHeight: 1.6,
marginBottom: 1,
},
'& ul': {
paddingLeft: 3,
},
},
color: {
primary: '#2c3e50',
secondary: '#34495e',
accent: '#3498db',
text: '#2c3e50',
background: '#ffffff',
},
},
modern: {
name: 'Modern',
description: 'Clean, minimalist sans-serif layout',
headerStyle: {
fontFamily: '"Helvetica Neue", Helvetica, Arial, sans-serif',
borderLeft: '4px solid #3498db',
paddingLeft: 2,
marginBottom: 3,
backgroundColor: '#f8f9fa',
padding: 2,
borderRadius: 1,
},
footerStyle: {
fontFamily: '"Helvetica Neue", Helvetica, Arial, sans-serif',
borderLeft: '4px solid #3498db',
backgroundColor: '#f8f9fa',
paddingTop: 2,
borderRadius: 1,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
textTransform: 'uppercase',
alignContent: 'center',
fontSize: '0.8rem',
pb: 2,
mb: 2,
},
contentStyle: {
fontFamily: '"Helvetica Neue", Helvetica, Arial, sans-serif',
lineHeight: 1.5,
color: '#2c3e50',
},
markdownStyle: {
fontFamily: '"Helvetica Neue", Helvetica, Arial, sans-serif',
'& h1, & h2, & h3': {
fontFamily: '"Helvetica Neue", Helvetica, Arial, sans-serif',
color: '#3498db',
fontWeight: 300,
marginBottom: 1.5,
},
'& h1': {
fontSize: '1.75rem',
},
'& h2': {
fontSize: '1.5rem',
},
'& h3': {
fontSize: '1.25rem',
},
'& p, & li': {
lineHeight: 1.5,
marginBottom: 0.75,
},
'& ul': {
paddingLeft: 2.5,
},
},
color: {
primary: '#3498db',
secondary: '#2c3e50',
accent: '#e74c3c',
text: '#2c3e50',
background: '#ffffff',
},
},
creative: {
name: 'Creative',
description: 'Colorful, unique design with personality',
headerStyle: {
fontFamily: '"Montserrat", "Helvetica Neue", Arial, sans-serif',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: '#ffffff',
padding: 2.5,
borderRadius: 1.5,
marginBottom: 3,
},
footerStyle: {
fontFamily: '"Montserrat", "Helvetica Neue", Arial, sans-serif',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: '#ffffff',
paddingTop: 2,
borderRadius: 1.5,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
textTransform: 'uppercase',
alignContent: 'center',
fontSize: '0.8rem',
pb: 2,
mb: 2,
},
contentStyle: {
fontFamily: '"Open Sans", Arial, sans-serif',
lineHeight: 1.6,
color: '#444444',
},
markdownStyle: {
fontFamily: '"Open Sans", Arial, sans-serif',
'& h1, & h2, & h3': {
fontFamily: '"Montserrat", "Helvetica Neue", Arial, sans-serif',
color: '#667eea',
fontWeight: 600,
marginBottom: 2,
},
'& h1': {
fontSize: '1.5rem',
},
'& h2': {
fontSize: '1.25rem',
},
'& h3': {
fontSize: '1.1rem',
},
'& p, & li': {
lineHeight: 1.6,
marginBottom: 1,
color: '#444444',
},
'& strong': {
color: '#764ba2',
fontWeight: 600,
},
'& ul': {
paddingLeft: 3,
},
},
color: {
primary: '#667eea',
secondary: '#764ba2',
accent: '#f093fb',
text: '#444444',
background: '#ffffff',
},
},
corporate: {
name: 'Corporate',
description: 'Formal, structured business format',
headerStyle: {
fontFamily: '"Arial", sans-serif',
border: '2px solid #34495e',
padding: 2.5,
marginBottom: 3,
backgroundColor: '#ecf0f1',
},
footerStyle: {
fontFamily: '"Arial", sans-serif',
border: '2px solid #34495e',
backgroundColor: '#ecf0f1',
paddingTop: 2,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
textTransform: 'uppercase',
alignContent: 'center',
fontSize: '0.8rem',
pb: 2,
mb: 2,
},
contentStyle: {
fontFamily: '"Arial", sans-serif',
lineHeight: 1.4,
color: '#2c3e50',
},
markdownStyle: {
fontFamily: '"Arial", sans-serif',
'& h1, & h2, & h3': {
fontFamily: '"Arial", sans-serif',
color: '#34495e',
fontWeight: 'bold',
textTransform: 'uppercase',
fontSize: '0.875rem',
letterSpacing: '1px',
marginBottom: 1.5,
borderBottom: '1px solid #bdc3c7',
paddingBottom: 0.5,
},
'& h1': {
fontSize: '1rem',
},
'& h2': {
fontSize: '0.875rem',
},
'& h3': {
fontSize: '0.75rem',
},
'& p, & li': {
lineHeight: 1.4,
marginBottom: 0.75,
fontSize: '0.75rem',
},
'& ul': {
paddingLeft: 2,
},
},
color: {
primary: '#34495e',
secondary: '#2c3e50',
accent: '#95a5a6',
text: '#2c3e50',
background: '#ffffff',
},
},
};
// Styled Header Component
interface BackstoryStyledResumeProps {
candidate: Types.Candidate;
style: ResumeStyle;
}
const StyledFooter: React.FC<BackstoryStyledResumeProps> = ({ candidate, style }) => {
return (
<>
<Box
className="BackstoryResumeFooter"
sx={{
...style.footerStyle,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
textTransform: 'uppercase',
alignContent: 'center',
fontSize: '0.8rem',
pb: 2,
mb: 2,
// pt: 2,
color: style.color.secondary,
}}
>
Ask any questions you may have at my Backstory...
<Box
component="img"
src={`/api/1.0/candidates/qr-code/${candidate.id || ''}`}
alt="QR Code"
className="qr-code"
sx={{ display: 'flex', mt: 1, mb: 1 }}
/>
{candidate?.username
? `https://backstory.ketrenos.com/u/${candidate?.username}`
: 'backstory'}
</Box>
<Box sx={{ pb: 2 }}>&nbsp;</Box>
</>
);
};
const StyledHeader: React.FC<BackstoryStyledResumeProps> = ({ candidate, style }) => {
const phone = parsePhoneNumberFromString(candidate.phone || '', 'US');
return (
<Box className="BackstoryResumeHeader" sx={style.headerStyle}>
<Typography
variant="h4"
sx={{
fontWeight: 'bold',
mb: 1,
color: style.name === 'creative' ? '#ffffff' : style.color.primary,
fontFamily: 'inherit',
}}
>
{candidate.fullName}
</Typography>
{/* {candidate.title && (
<Typography
variant="h6"
sx={{
mb: 2,
fontWeight: 300,
color: style.name === 'creative' ? '#ffffff' : style.color.secondary,
fontFamily: 'inherit',
}}
>
{candidate.title}
</Typography>
)} */}
<Box
sx={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
flexWrap: 'wrap',
alignContent: 'center',
}}
>
{candidate.email && (
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<EmailIcon
fontSize="small"
sx={{ color: style.name === 'creative' ? '#ffffff' : style.color.accent }}
/>
<Typography
variant="body2"
sx={{
color: style.name === 'creative' ? '#ffffff' : style.color.text,
fontFamily: 'inherit',
}}
>
{candidate.email}
</Typography>
</Box>
</Grid>
)}
{phone?.isValid() && (
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<PhoneIcon
fontSize="small"
sx={{ color: style.name === 'creative' ? '#ffffff' : style.color.accent }}
/>
<Typography
variant="body2"
sx={{
color: style.name === 'creative' ? '#ffffff' : style.color.text,
fontFamily: 'inherit',
}}
>
{phone.formatInternational()}
</Typography>
</Box>
</Grid>
)}
{candidate.location && (
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<LocationIcon
fontSize="small"
sx={{ color: style.name === 'creative' ? '#ffffff' : style.color.accent }}
/>
<Typography
variant="body2"
sx={{
color: style.name === 'creative' ? '#ffffff' : style.color.text,
fontFamily: 'inherit',
}}
>
{candidate.location.city}, {candidate.location.state}
</Typography>
</Box>
</Grid>
)}
{/* {(candidate.website || candidate.linkedin) && (
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<WebsiteIcon
fontSize="small"
sx={{ color: style.name === 'creative' ? '#ffffff' : style.color.accent }}
/>
<Typography
variant="body2"
sx={{
color: style.name === 'creative' ? '#ffffff' : style.color.text,
fontFamily: 'inherit',
}}
>
{candidate.website || candidate.linkedin}
</Typography>
</Box>
</Grid>
)} */}
</Box>
</Box>
);
};
const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
const { setSnack } = useAppState();
const { resume } = props;
@ -81,6 +537,7 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
const [status, setStatus] = useState<string>('');
const [statusType, setStatusType] = useState<Types.ApiActivityType | null>(null);
const [error, setError] = useState<Types.ChatMessageError | null>(null);
const [selectedStyle, setSelectedStyle] = useState<string>('modern');
const printContentRef = useRef<HTMLDivElement>(null);
const reactToPrintFn = useReactToPrint({
@ -94,7 +551,9 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
}
}, [resume, activeResume]);
// Check if content needs truncation
const currentStyle = resumeStyles[selectedStyle];
// Rest of the component remains the same...
const deleteResume = async (id: string | undefined): Promise<void> => {
if (id) {
try {
@ -195,7 +654,7 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
const generateResume = async (): Promise<void> => {
setStatusType('thinking');
setStatus('Starting resume generation...');
setActiveResume({ ...activeResume, resume: '' }); // Reset resume content
setActiveResume({ ...activeResume, resume: '' });
const request = await apiClient.generateResume(
activeResume.candidateId || '',
activeResume.jobId || '',
@ -210,7 +669,6 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
return;
}
if (newValue === 'regenerate') {
// Handle resume regeneration logic here
setSnack('Regenerating resume...');
generateResume();
return;
@ -487,18 +945,65 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
overflow: 'hidden',
}}
>
<Tabs value={tabValue} onChange={handleTabChange} centered>
<Tab value="markdown" icon={<EditDocumentIcon />} label="Markdown" />
{activeResume.systemPrompt && (
<Tab value="systemPrompt" icon={<TuneIcon />} label="System Prompt" />
)}
{activeResume.systemPrompt && (
<Tab value="prompt" icon={<InputIcon />} label="Prompt" />
)}
<Tab value="preview" icon={<PreviewIcon />} label="Preview" />
<Tab value="print" icon={<PrintIcon />} label="Print" />
<Tab value="regenerate" icon={<ModelTraining />} label="Regenerate" />
</Tabs>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 2 }}>
<Tabs value={tabValue} onChange={handleTabChange}>
<Tab value="markdown" icon={<EditDocumentIcon />} label="Markdown" />
{activeResume.systemPrompt && (
<Tab value="systemPrompt" icon={<TuneIcon />} label="System Prompt" />
)}
{activeResume.systemPrompt && (
<Tab value="prompt" icon={<InputIcon />} label="Prompt" />
)}
<Tab value="preview" icon={<PreviewIcon />} label="Preview" />
<Tab value="print" icon={<PrintIcon />} label="Print" />
<Tab value="regenerate" icon={<ModelTraining />} label="Regenerate" />
</Tabs>
{/* Style Selector */}
<FormControl size="small" sx={{ minWidth: 180 }}>
<InputLabel id="resume-style-label">
<StyleIcon sx={{ mr: 1, fontSize: 16 }} />
Resume Style
</InputLabel>
<Select
labelId="resume-style-label"
value={selectedStyle}
onChange={e => setSelectedStyle(e.target.value)}
label="Resume Style"
>
{Object.entries(resumeStyles).map(([key, style]) => (
<MenuItem key={key} value={key}>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
}}
>
<Typography variant="body2" fontWeight="bold">
{style.name}
</Typography>
<Typography variant="caption" color="text.secondary">
{style.description}
</Typography>
</Box>
</MenuItem>
))}
</Select>
</FormControl>
{/* Style Preview Chip */}
<Chip
icon={<StyleIcon />}
label={currentStyle.name}
sx={{
backgroundColor: currentStyle.color.primary,
color: '#ffffff',
fontWeight: 'bold',
}}
/>
</Box>
{status && (
<Box sx={{ mt: 0, mb: 1 }}>
<StatusBox>
@ -514,12 +1019,11 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
sx={{
display: 'flex',
flexDirection: 'column',
height: '100%' /* Restrict to main-container's height */,
height: '100%',
width: '100%',
minHeight: 0 /* Prevent flex overflow */,
//maxHeight: "min-content",
minHeight: 0,
'& > *:not(.Scrollable)': {
flexShrink: 0 /* Prevent shrinking */,
flexShrink: 0,
},
position: 'relative',
}}
@ -535,10 +1039,9 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
width: '100%',
display: 'flex',
minHeight: '100%',
flexGrow: 1,
flex: 1 /* Take remaining space in some-container */,
overflowY: 'auto' /* Scroll if content overflows */,
flex: 1,
overflowY: 'auto',
}}
placeholder="Enter resume content..."
/>
@ -550,13 +1053,12 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
style={{
position: 'relative',
maxHeight: '100%',
// height: '100%',
width: '100%',
display: 'flex',
minHeight: '100%',
flexGrow: 1,
flex: 1 /* Take remaining space in some-container */,
overflowY: 'auto' /* Scroll if content overflows */,
flex: 1,
overflowY: 'auto',
}}
placeholder="Edit system prompt..."
/>
@ -572,52 +1074,53 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
width: '100%',
display: 'flex',
minHeight: '100%',
flexGrow: 1,
flex: 1 /* Take remaining space in some-container */,
overflowY: 'auto' /* Scroll if content overflows */,
flex: 1,
overflowY: 'auto',
}}
placeholder="Edit prompt..."
/>
)}
{tabValue === 'preview' && (
<Box className="document-container" ref={printContentRef}>
<Box className="a4-document">
<StyledMarkdown
sx={{
position: 'relative',
maxHeight: '100%',
display: 'flex',
flexGrow: 1,
flex: 1 /* Take remaining space in some-container */,
// overflowY: 'auto' /* Scroll if content overflows */,
}}
content={editContent}
/>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
textTransform: 'uppercase',
fontSize: '0.8rem',
pb: 2,
}}
>
See my full Backstory at...
<Box
component="img"
src={`/api/1.0/candidates/qr-code/${activeResume.candidateId || ''}`}
alt="QR Code"
className="qr-code"
<Box
className="document-container"
ref={printContentRef}
sx={currentStyle.contentStyle}
>
<Box
className="a4-document"
sx={{
backgroundColor: currentStyle.color.background,
padding: 5,
minHeight: '100vh',
boxShadow: '0 0 10px rgba(0,0,0,0.1)',
}}
>
{/* Custom Header */}
{activeResume.candidate && (
<StyledHeader candidate={activeResume.candidate} style={currentStyle} />
)}
{/* Styled Markdown Content */}
<Box sx={currentStyle.markdownStyle}>
<StyledMarkdown
sx={{
position: 'relative',
maxHeight: '100%',
display: 'flex',
flexGrow: 1,
flex: 1,
...currentStyle.markdownStyle,
}}
content={editContent || activeResume.resume || ''}
/>
{activeResume.candidate?.username
? `https://backstory.ketrenos.com/u/${activeResume.candidate?.username}`
: 'backstory'}
</Box>
{/* QR Code Footer */}
{activeResume.candidate && (
<StyledFooter candidate={activeResume.candidate} style={currentStyle} />
)}
</Box>
<Box sx={{ pb: 2 }}>&nbsp;</Box>
</Box>
)}
</Scrollable>
@ -647,7 +1150,7 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
job={activeResume.job}
sx={{
mt: 2,
backgroundColor: '#f8f0e0', //theme.palette.background.paper,
backgroundColor: '#f8f0e0',
}}
/>
)}

View File

@ -27,11 +27,9 @@ import { VectorVisualizer } from 'components/VectorVisualizer';
import { DocumentManager } from 'components/DocumentManager';
import { useAuth } from 'hooks/AuthContext';
import { useNavigate } from 'react-router-dom';
import { JobsView } from 'components/ui/JobsView';
import { JobsViewPage } from 'pages/JobsViewPage';
import { ResumeViewer } from 'components/ui/ResumeViewer';
import * as Types from 'types/types';
const LogoutPage = (): JSX.Element => {
const { logout } = useAuth();
const navigate = useNavigate();
@ -127,16 +125,7 @@ export const navigationConfig: NavigationConfig = {
label: 'Jobs',
path: '/candidate/jobs/:jobId?',
icon: <WorkIcon />,
component: (
<JobsView
onJobSelect={(selectedJobs: Types.Job[]): void => console.log('Selected:', selectedJobs)}
onJobView={(job: Types.Job): void => console.log('View job:', job)}
onJobEdit={(job: Types.Job): void => console.log('Edit job:', job)}
onJobDelete={(job: Types.Job): void => console.log('Delete job:', job)}
selectable={true}
showActions={true}
/>
),
component: <JobsViewPage />,
variant: 'fullWidth',
userTypes: ['candidate', 'guest', 'employer'],
showInNavigation: false,

View File

@ -29,7 +29,6 @@ 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 }) => ({

View File

@ -77,6 +77,8 @@ class GenerateResume(Agent):
Create a polished, concise, and ATS-friendly resume for the candidate based on the assessment data provided. Rephrase skills to avoid
direct duplication from the assessment.
Do not provide header information like name, email, or phone number in the resume, as that information will be added later.
## CANDIDATE INFORMATION:
Name: {self.user.full_name}
Email: {self.user.email or "N/A"}
@ -164,7 +166,7 @@ ELSE:
Provide template format only
## OUTPUT FORMAT:
Provide the resume in clean markdown format, ready for the candidate to use.
Provide the resume in clean markdown format, ready for the candidate to use. Do not provide header contact information, as that will be added later.
"""
prompt = """\