Added resume styles
This commit is contained in:
parent
cacbb0fd0f
commit
d66e1ee1e4
1
frontend/package-lock.json
generated
1
frontend/package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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();
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 }}> </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 }}> </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',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
@ -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,
|
||||
|
@ -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 }) => ({
|
||||
|
@ -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 = """\
|
||||
|
Loading…
x
Reference in New Issue
Block a user