import React, { useState, useEffect, useRef, JSX } from 'react';
import {
Box,
Button,
Typography,
Paper,
TextField,
Grid,
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
DialogActions,
IconButton,
useTheme,
useMediaQuery,
Chip,
Divider,
Card,
CardContent,
CardHeader,
LinearProgress,
Stack,
Alert
} from '@mui/material';
import {
SyncAlt,
Favorite,
Settings,
Info,
Search,
AutoFixHigh,
Image,
Psychology,
Build,
CloudUpload,
Description,
Business,
LocationOn,
Work,
CheckCircle,
Star
} from '@mui/icons-material';
import { styled } from '@mui/material/styles';
import DescriptionIcon from '@mui/icons-material/Description';
import FileUploadIcon from '@mui/icons-material/FileUpload';
import { useAuth } from 'hooks/AuthContext';
import { useAppState, useSelectedCandidate, useSelectedJob } from 'hooks/GlobalContext';
import { BackstoryElementProps } from './BackstoryTab';
import { LoginRequired } from 'components/ui/LoginRequired';
import * as Types from 'types/types';
const VisuallyHiddenInput = styled('input')({
clip: 'rect(0 0 0 0)',
clipPath: 'inset(50%)',
height: 1,
overflow: 'hidden',
position: 'absolute',
bottom: 0,
left: 0,
whiteSpace: 'nowrap',
width: 1,
});
const UploadBox = styled(Box)(({ theme }) => ({
border: `2px dashed ${theme.palette.primary.main}`,
borderRadius: theme.shape.borderRadius * 2,
padding: theme.spacing(4),
textAlign: 'center',
backgroundColor: theme.palette.action.hover,
transition: 'all 0.3s ease',
cursor: 'pointer',
'&:hover': {
backgroundColor: theme.palette.action.selected,
borderColor: theme.palette.primary.dark,
},
}));
const StatusBox = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
gap: theme.spacing(1),
padding: theme.spacing(1, 2),
backgroundColor: theme.palette.background.paper,
borderRadius: theme.shape.borderRadius,
border: `1px solid ${theme.palette.divider}`,
minHeight: 48,
}));
const getIcon = (type: Types.ApiActivityType) => {
switch (type) {
case 'converting':
return ;
case 'heartbeat':
return ;
case 'system':
return ;
case 'info':
return ;
case 'searching':
return ;
case 'generating':
return ;
case 'generating_image':
return ;
case 'thinking':
return ;
case 'tooling':
return ;
default:
return ;
}
};
interface JobCreator extends BackstoryElementProps {
onSave?: (job: Types.Job) => void;
}
const JobCreator = (props: JobCreator) => {
const { user, apiClient } = useAuth();
const { onSave } = props;
const { selectedCandidate } = useSelectedCandidate();
const { selectedJob, setSelectedJob } = useSelectedJob();
const { setSnack } = useAppState();
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
const isTablet = useMediaQuery(theme.breakpoints.down('md'));
const [openUploadDialog, setOpenUploadDialog] = useState(false);
const [jobDescription, setJobDescription] = useState('');
const [jobRequirements, setJobRequirements] = useState(null);
const [jobTitle, setJobTitle] = useState('');
const [company, setCompany] = useState('');
const [summary, setSummary] = useState('');
const [jobLocation, setJobLocation] = useState('');
const [jobId, setJobId] = useState('');
const [jobStatus, setJobStatus] = useState('');
const [jobStatusIcon, setJobStatusIcon] = useState(<>>);
const [isProcessing, setIsProcessing] = useState(false);
useEffect(() => {
}, [jobTitle, jobDescription, company]);
const fileInputRef = useRef(null);
if (!user?.id) {
return (
);
}
const jobStatusHandlers = {
onStatus: (status: Types.ChatMessageStatus) => {
console.log('status:', status.content);
setJobStatusIcon(getIcon(status.activity));
setJobStatus(status.content);
},
onMessage: (job: Types.Job) => {
console.log('onMessage - job', job);
setCompany(job.company || '');
setJobDescription(job.description);
setSummary(job.summary || '');
setJobTitle(job.title || '');
setJobRequirements(job.requirements || null);
setJobStatusIcon(<>>);
setJobStatus('');
},
onError: (error: Types.ChatMessageError) => {
console.log('onError', error);
setSnack(error.content, "error");
setIsProcessing(false);
},
onComplete: () => {
setJobStatusIcon(<>>);
setJobStatus('');
setIsProcessing(false);
}
};
const handleJobUpload = async (e: React.ChangeEvent) => {
if (e.target.files && e.target.files[0]) {
const file = e.target.files[0];
const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase();
let docType: Types.DocumentType | null = null;
switch (fileExtension.substring(1)) {
case "pdf":
docType = "pdf";
break;
case "docx":
docType = "docx";
break;
case "md":
docType = "markdown";
break;
case "txt":
docType = "txt";
break;
}
if (!docType) {
setSnack('Invalid file type. Please upload .txt, .md, .docx, or .pdf files only.', 'error');
return;
}
try {
setIsProcessing(true);
setJobDescription('');
setJobTitle('');
setJobRequirements(null);
setSummary('');
const controller = apiClient.createJobFromFile(file, jobStatusHandlers);
const job = await controller.promise;
if (!job) {
return;
}
console.log(`Job id: ${job.id}`);
e.target.value = '';
} catch (error) {
console.error(error);
setSnack('Failed to upload document', 'error');
setIsProcessing(false);
}
}
};
const handleUploadClick = () => {
fileInputRef.current?.click();
};
const renderRequirementSection = (title: string, items: string[] | undefined, icon: JSX.Element, required = false) => {
if (!items || items.length === 0) return null;
return (
{icon}
{title}
{required && }
{items.map((item, index) => (
))}
);
};
const renderJobRequirements = () => {
if (!jobRequirements) return null;
return (
}
sx={{ pb: 1 }}
/>
{renderRequirementSection(
"Technical Skills (Required)",
jobRequirements.technicalSkills.required,
,
true
)}
{renderRequirementSection(
"Technical Skills (Preferred)",
jobRequirements.technicalSkills.preferred,
)}
{renderRequirementSection(
"Experience Requirements (Required)",
jobRequirements.experienceRequirements.required,
,
true
)}
{renderRequirementSection(
"Experience Requirements (Preferred)",
jobRequirements.experienceRequirements.preferred,
)}
{renderRequirementSection(
"Soft Skills",
jobRequirements.softSkills,
)}
{renderRequirementSection(
"Experience",
jobRequirements.experience,
)}
{renderRequirementSection(
"Education",
jobRequirements.education,
)}
{renderRequirementSection(
"Certifications",
jobRequirements.certifications,
)}
{renderRequirementSection(
"Preferred Attributes",
jobRequirements.preferredAttributes,
)}
);
};
const handleSave = async () => {
const newJob: Types.Job = {
ownerId: user?.id || '',
ownerType: 'candidate',
description: jobDescription,
company: company,
summary: summary,
title: jobTitle,
requirements: jobRequirements || undefined,
createdAt: new Date(),
updatedAt: new Date(),
};
setIsProcessing(true);
const job = await apiClient.createJob(newJob);
setIsProcessing(false);
onSave ? onSave(job) : setSelectedJob(job);
};
const handleExtractRequirements = async () => {
try {
setIsProcessing(true);
const controller = apiClient.createJobFromDescription(jobDescription, jobStatusHandlers);
const job = await controller.promise;
if (!job) {
setIsProcessing(false);
return;
}
console.log(`Job id: ${job.id}`);
} catch (error) {
console.error(error);
setSnack('Failed to upload document', 'error');
setIsProcessing(false);
}
setIsProcessing(false);
};
const renderJobCreation = () => {
if (!user) {
return You must be logged in;
}
return (
{/* Upload Section */}
}
/>
Upload Job Description
Drop your job description here
Supported formats: PDF, DOCX, TXT, MD
}
disabled={isProcessing}
// onClick={handleUploadClick}
>
Choose File
Or Enter Manually
setJobDescription(e.target.value)}
disabled={isProcessing}
sx={{ mb: 2 }}
/>
{jobRequirements === null && jobDescription && (
}
disabled={isProcessing}
fullWidth={isMobile}
>
Extract Requirements
)}
{(jobStatus || isProcessing) && (
{jobStatusIcon}
{jobStatus || 'Processing...'}
{isProcessing && }
)}
{/* Job Details Section */}
}
/>
setJobTitle(e.target.value)}
required
disabled={isProcessing}
InputProps={{
startAdornment:
}}
/>
setCompany(e.target.value)}
required
disabled={isProcessing}
InputProps={{
startAdornment:
}}
/>
{/*
setJobLocation(e.target.value)}
disabled={isProcessing}
InputProps={{
startAdornment:
}}
/>
*/}
}
>
Save Job
{/* Job Summary */}
{summary !== '' &&
}
sx={{ pb: 1 }}
/>
{summary}
}
{/* Requirements Display */}
{renderJobRequirements()}
);
};
return (
{selectedJob === null && renderJobCreation()}
);
};
export { JobCreator };