Working on Job Analysis
This commit is contained in:
parent
0994c95f91
commit
138f61b777
@ -1,20 +1,20 @@
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
|
||||
import { ChatQuery } from "types/types";
|
||||
import { CandidateQuestion } from "types/types";
|
||||
|
||||
type ChatSubmitQueryInterface = (query: ChatQuery) => void;
|
||||
type ChatSubmitQueryInterface = (query: CandidateQuestion) => void;
|
||||
|
||||
interface BackstoryQueryInterface {
|
||||
query: ChatQuery,
|
||||
question: CandidateQuestion,
|
||||
submitQuery?: ChatSubmitQueryInterface
|
||||
}
|
||||
|
||||
const BackstoryQuery = (props : BackstoryQueryInterface) => {
|
||||
const { query, submitQuery } = props;
|
||||
const { question, submitQuery } = props;
|
||||
|
||||
if (submitQuery === undefined) {
|
||||
return (<Box>{query.prompt}</Box>);
|
||||
return (<Box>{question.question}</Box>);
|
||||
}
|
||||
return (
|
||||
<Button variant="outlined" sx={{
|
||||
@ -22,8 +22,8 @@ const BackstoryQuery = (props : BackstoryQueryInterface) => {
|
||||
borderColor: theme => theme.palette.custom.highlight,
|
||||
m: 1
|
||||
}}
|
||||
size="small" onClick={(e: any) => { submitQuery(query); }}>
|
||||
{query.prompt}
|
||||
size="small" onClick={(e: any) => { submitQuery(question); }}>
|
||||
{question.question}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
Button,
|
||||
useMediaQuery,
|
||||
Tooltip,
|
||||
SxProps,
|
||||
} from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import ResetIcon from '@mui/icons-material/History';
|
||||
@ -20,7 +21,7 @@ interface DeleteConfirmationProps {
|
||||
label?: string;
|
||||
action?: "delete" | "reset";
|
||||
color?: "inherit" | "default" | "primary" | "secondary" | "error" | "info" | "success" | "warning" | undefined;
|
||||
|
||||
sx?: SxProps;
|
||||
// New props for controlled mode
|
||||
open?: boolean;
|
||||
onClose?: () => void;
|
||||
@ -54,7 +55,8 @@ const DeleteConfirmation = (props: DeleteConfirmationProps) => {
|
||||
message,
|
||||
hideButton = false,
|
||||
confirmButtonText,
|
||||
cancelButtonText = "Cancel"
|
||||
cancelButtonText = "Cancel",
|
||||
sx
|
||||
} = props;
|
||||
|
||||
// Internal state for uncontrolled mode
|
||||
@ -104,7 +106,7 @@ const DeleteConfirmation = (props: DeleteConfirmationProps) => {
|
||||
aria-label={action}
|
||||
onClick={handleClickOpen}
|
||||
color={color || "inherit"}
|
||||
sx={{ display: "flex", margin: 'auto 0px' }}
|
||||
sx={{ display: "flex", margin: 'auto 0px', ...sx }}
|
||||
size="large"
|
||||
edge="start"
|
||||
disabled={disabled}
|
||||
|
@ -13,7 +13,7 @@ import { GenerateImage } from 'components/GenerateImage';
|
||||
|
||||
import './StyledMarkdown.css';
|
||||
import { BackstoryElementProps } from './BackstoryTab';
|
||||
import { ChatSession } from 'types/types';
|
||||
import { CandidateQuestion, ChatSession } from 'types/types';
|
||||
|
||||
interface StyledMarkdownProps extends BackstoryElementProps {
|
||||
className?: string,
|
||||
@ -106,8 +106,11 @@ const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProp
|
||||
const queryString = props.query.replace(/(\w+):/g, '"$1":');
|
||||
try {
|
||||
const query = JSON.parse(queryString);
|
||||
const backstoryQuestion: CandidateQuestion = {
|
||||
question: queryString
|
||||
}
|
||||
|
||||
return <BackstoryQuery submitQuery={submitQuery} query={query} />
|
||||
return <BackstoryQuery submitQuery={submitQuery} question={query} />
|
||||
} catch (e) {
|
||||
console.log("StyledMarkdown error:", queryString, e);
|
||||
return props.query;
|
||||
|
@ -44,7 +44,7 @@ const ViewerNavItems: NavigationLinkType[] = [
|
||||
|
||||
const CandidateNavItems : NavigationLinkType[]= [
|
||||
{ name: 'Chat', path: '/chat', icon: <ChatIcon /> },
|
||||
// { name: 'Job Analysis', path: '/candidate/job-analysis', icon: <WorkIcon /> },
|
||||
{ name: 'Job Analysis', path: '/candidate/job-analysis', icon: <WorkIcon /> },
|
||||
{ name: 'Resume Builder', path: '/candidate/resume-builder', icon: <WorkIcon /> },
|
||||
// { name: 'Knowledge Explorer', path: '/candidate/knowledge-explorer', icon: <WorkIcon /> },
|
||||
// { name: 'Dashboard', icon: <DashboardIcon />, path: '/candidate/dashboard' },
|
||||
|
@ -21,6 +21,7 @@ import { LoginPage } from "pages/LoginPage";
|
||||
import { CandidateDashboardPage } from "pages/CandidateDashboardPage"
|
||||
import { EmailVerificationPage } from "components/EmailVerificationComponents";
|
||||
import { CandidateProfilePage } from "pages/candidate/Profile";
|
||||
import { JobMatchAnalysis } from "components/JobMatchAnalysis";
|
||||
|
||||
const BackstoryPage = () => (<BetaPage><Typography variant="h4">Backstory</Typography></BetaPage>);
|
||||
const ResumesPage = () => (<BetaPage><Typography variant="h4">Resumes</Typography></BetaPage>);
|
||||
@ -70,6 +71,7 @@ const getBackstoryDynamicRoutes = (props: BackstoryDynamicRoutesProps): ReactNod
|
||||
routes.splice(-1, 0, ...[
|
||||
<Route key={`${index++}`} path="/candidate/dashboard" element={<BetaPage><CandidateDashboardPage {...backstoryProps} /></BetaPage>} />,
|
||||
<Route key={`${index++}`} path="/candidate/profile" element={<CandidateProfilePage {...backstoryProps} />} />,
|
||||
<Route key={`${index++}`} path="/candidate/job-analysis" element={<JobAnalysisPage {...backstoryProps} />} />,
|
||||
<Route key={`${index++}`} path="/candidate/backstory" element={<BackstoryPage />} />,
|
||||
<Route key={`${index++}`} path="/candidate/resumes" element={<ResumesPage />} />,
|
||||
<Route key={`${index++}`} path="/candidate/qa-setup" element={<QASetupPage />} />,
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
Divider,
|
||||
useTheme,
|
||||
useMediaQuery,
|
||||
Tooltip,
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Send as SendIcon
|
||||
@ -21,6 +22,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { useSelectedCandidate } from 'hooks/GlobalContext';
|
||||
import PropagateLoader from 'react-spinners/PropagateLoader';
|
||||
import { BackstoryTextField, BackstoryTextFieldRef } from 'components/BackstoryTextField';
|
||||
import { BackstoryQuery } from 'components/BackstoryQuery';
|
||||
|
||||
const defaultMessage: ChatMessage = {
|
||||
type: "preparing", status: "done", sender: "system", sessionId: "", timestamp: new Date(), content: ""
|
||||
@ -279,12 +281,13 @@ const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>((pr
|
||||
</Box>
|
||||
</>}
|
||||
</Paper>
|
||||
|
||||
{selectedCandidate.questions?.length !== 0 && selectedCandidate.questions?.map(q => <BackstoryQuery question={q} />)}
|
||||
{/* Fixed Message Input */}
|
||||
<Box sx={{ display: 'flex', flexShrink: 0, gap: 1 }}>
|
||||
<DeleteConfirmation
|
||||
onDelete={() => { chatSession && onDelete(chatSession); }}
|
||||
disabled={!chatSession}
|
||||
sx={{ minWidth: 'auto', px: 2, maxHeight: "min-content" }}
|
||||
action="reset"
|
||||
label="chat session"
|
||||
title="Reset Chat Session"
|
||||
@ -296,14 +299,19 @@ const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>((pr
|
||||
onEnter={sendMessage}
|
||||
disabled={streaming || loading}
|
||||
/>
|
||||
<Tooltip title="Send">
|
||||
<span style={{ minWidth: 'auto', maxHeight: "min-content", alignSelf: "center" }}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => { sendMessage((backstoryTextRef.current && backstoryTextRef.current.getAndResetValue()) || ""); }}
|
||||
disabled={streaming || loading}
|
||||
sx={{ minWidth: 'auto', px: 2 }}
|
||||
disabled={streaming || loading}
|
||||
>
|
||||
<SendIcon />
|
||||
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
@ -145,7 +145,6 @@ const documents : DocType[] = [
|
||||
{ title: "Authentication Architecture", route: "authentication.md", description: "Complete authentication architecture", icon: <LayersIcon /> },
|
||||
{ title: "UI Overview", route: "ui-overview", description: "Guide to the user interface components and interactions", icon: <DashboardIcon /> },
|
||||
{ title: "UI Mockup", route: "ui-mockup", description: "Visual previews of interfaces and layout concepts", icon: <DashboardIcon /> },
|
||||
{ title: "Chat Mockup", route: "mockup-chat-system", description: "Mockup of chat system", icon: <DashboardIcon /> },
|
||||
{ title: "Theme Visualizer", route: "theme-visualizer", description: "Explore and customize application themes and visual styles", icon: <PaletteIcon /> },
|
||||
{ title: "App Analysis", route: "app-analysis", description: "Statistics and performance metrics of the application", icon: <AnalyticsIcon /> },
|
||||
{ title: 'Text Mockups', route: "backstory-ui-mockups", description: "Early text mockups of many of the interaction points." },
|
||||
|
@ -55,7 +55,7 @@ const CandidateListingPage = (props: BackstoryPageProps) => {
|
||||
Generate your own perfect AI candidate!
|
||||
</Button>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap" }}>
|
||||
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap", justifyContent: "center" }}>
|
||||
{candidates?.map((u, i) =>
|
||||
<Box key={`${u.username}`}
|
||||
onClick={() => { setSelectedCandidate(u); navigate("/chat"); }}
|
||||
|
@ -38,15 +38,16 @@ import { Candidate } from "types/types";
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { BackstoryPageProps } from 'components/BackstoryTab';
|
||||
import { useAuth } from 'hooks/AuthContext';
|
||||
import { useSelectedCandidate } from 'hooks/GlobalContext';
|
||||
|
||||
// Main component
|
||||
const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps) => {
|
||||
const theme = useTheme();
|
||||
const { user } = useAuth();
|
||||
const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate()
|
||||
|
||||
// State management
|
||||
const [activeStep, setActiveStep] = useState(0);
|
||||
const [selectedCandidate, setSelectedCandidate] = useState<Candidate | null>(null);
|
||||
const [jobDescription, setJobDescription] = useState('');
|
||||
const [jobTitle, setJobTitle] = useState('');
|
||||
const [jobLocation, setJobLocation] = useState('');
|
||||
@ -58,7 +59,7 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
|
||||
const [candidates, setCandidates] = useState<Candidate[] | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (candidates !== null) {
|
||||
if (candidates !== null || selectedCandidate) {
|
||||
return;
|
||||
}
|
||||
const getCandidates = async () => {
|
||||
@ -85,11 +86,20 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
|
||||
getCandidates();
|
||||
}, [candidates, setSnack]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedCandidate && activeStep === 0) {
|
||||
setActiveStep(1);
|
||||
}
|
||||
}, [selectedCandidate, activeStep]);
|
||||
|
||||
// Steps in our process
|
||||
const steps = [
|
||||
{ label: 'Select Candidate', icon: <PersonIcon /> },
|
||||
{ label: 'Job Description', icon: <WorkIcon /> },
|
||||
{ label: 'View Analysis', icon: <AssessmentIcon /> }
|
||||
const steps = selectedCandidate === null ? [
|
||||
{ index: 0, label: 'Select Candidate', icon: <PersonIcon /> },
|
||||
{ index: 1, label: 'Job Description', icon: <WorkIcon /> },
|
||||
{ index: 2, label: 'View Analysis', icon: <AssessmentIcon /> }
|
||||
] : [
|
||||
{ index: 1, label: 'Job Description', icon: <WorkIcon /> },
|
||||
{ index: 2, label: 'View Analysis', icon: <AssessmentIcon /> }
|
||||
];
|
||||
|
||||
// Mock handlers for our analysis APIs
|
||||
@ -235,7 +245,7 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeStep === 1 && (!jobTitle || !jobDescription)) {
|
||||
if (activeStep === 1 && (/*(extraInfo && !jobTitle) || */!jobDescription)) {
|
||||
setError('Please provide both job title and description before continuing.');
|
||||
return;
|
||||
}
|
||||
@ -252,11 +262,12 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setActiveStep(0);
|
||||
setSelectedCandidate(null);
|
||||
// setActiveStep(0);
|
||||
setActiveStep(1);
|
||||
// setSelectedCandidate(null);
|
||||
setJobDescription('');
|
||||
setJobTitle('');
|
||||
setJobLocation('');
|
||||
// setJobTitle('');
|
||||
// setJobLocation('');
|
||||
setAnalysisStarted(false);
|
||||
};
|
||||
|
||||
@ -341,50 +352,56 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
|
||||
</Paper>
|
||||
);
|
||||
|
||||
const extraInfo = false;
|
||||
|
||||
// Render function for the job description step
|
||||
const renderJobDescription = () => (
|
||||
<Paper elevation={3} sx={{ p: 3, mt: 3, mb: 4, borderRadius: 2 }}>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
Enter Job Details
|
||||
</Typography>
|
||||
|
||||
<Grid container spacing={3}>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Job Title"
|
||||
variant="outlined"
|
||||
value={jobTitle}
|
||||
onChange={(e) => setJobTitle(e.target.value)}
|
||||
required
|
||||
margin="normal"
|
||||
/>
|
||||
{extraInfo && <>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
Enter Job Details
|
||||
</Typography>
|
||||
|
||||
<Grid container spacing={3}>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Job Title"
|
||||
variant="outlined"
|
||||
value={jobTitle}
|
||||
onChange={(e) => setJobTitle(e.target.value)}
|
||||
required
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Job Location"
|
||||
variant="outlined"
|
||||
value={jobLocation}
|
||||
onChange={(e) => setJobLocation(e.target.value)}
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Job Location"
|
||||
variant="outlined"
|
||||
value={jobLocation}
|
||||
onChange={(e) => setJobLocation(e.target.value)}
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
</>
|
||||
}
|
||||
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mt: 2, mb: 1 }}>
|
||||
<Typography variant="subtitle1" sx={{ mr: 2 }}>
|
||||
Job Description
|
||||
</Typography>
|
||||
<Button
|
||||
{extraInfo && <Button
|
||||
variant="outlined"
|
||||
startIcon={<FileUploadIcon />}
|
||||
size="small"
|
||||
onClick={() => setOpenUploadDialog(true)}
|
||||
>
|
||||
Upload
|
||||
</Button>
|
||||
</Button>}
|
||||
</Box>
|
||||
|
||||
<TextField
|
||||
@ -406,8 +423,7 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
|
||||
/>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
|
||||
The job description will be used to extract requirements for candidate matching.
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
@ -455,13 +471,13 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
|
||||
|
||||
<Box sx={{ mt: 4, mb: 4 }}>
|
||||
<Stepper activeStep={activeStep} alternativeLabel>
|
||||
{steps.map((step, index) => (
|
||||
<Step key={index}>
|
||||
{steps.map(step => (
|
||||
<Step key={step.index}>
|
||||
<StepLabel slots={{
|
||||
stepIcon: () => (
|
||||
<Avatar
|
||||
sx={{
|
||||
bgcolor: activeStep >= index ? theme.palette.primary.main : theme.palette.grey[300],
|
||||
bgcolor: activeStep >= step.index ? theme.palette.primary.main : theme.palette.grey[300],
|
||||
color: 'white'
|
||||
}}
|
||||
>
|
||||
@ -484,7 +500,7 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
|
||||
<Box sx={{ display: 'flex', flexDirection: 'row', pt: 2 }}>
|
||||
<Button
|
||||
color="inherit"
|
||||
disabled={activeStep === 0}
|
||||
disabled={activeStep === steps[0].index}
|
||||
onClick={handleBack}
|
||||
sx={{ mr: 1 }}
|
||||
>
|
||||
@ -492,13 +508,13 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
|
||||
</Button>
|
||||
<Box sx={{ flex: '1 1 auto' }} />
|
||||
|
||||
{activeStep === steps.length - 1 ? (
|
||||
{activeStep === steps[steps.length - 1].index ? (
|
||||
<Button onClick={handleReset} variant="outlined">
|
||||
Start New Analysis
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={handleNext} variant="contained">
|
||||
{activeStep === steps.length - 2 ? 'Start Analysis' : 'Next'}
|
||||
{activeStep === steps[steps.length - 1].index - 1 ? 'Start Analysis' : 'Next'}
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
|
@ -27,8 +27,8 @@ const ChatPage = forwardRef<ConversationHandle, BackstoryPageProps>((props: Back
|
||||
|
||||
setQuestions([
|
||||
<Box sx={{ display: "flex", flexDirection: isMobile ? "column" : "row" }}>
|
||||
{candidate.questions?.map(({ question, tunables }, i: number) =>
|
||||
<BackstoryQuery key={i} query={{ prompt: question, tunables: tunables }} submitQuery={submitQuery} />
|
||||
{candidate.questions?.map((q, i: number) =>
|
||||
<BackstoryQuery key={i} question={q} submitQuery={submitQuery} />
|
||||
)}
|
||||
</Box>,
|
||||
<Box sx={{ p: 1 }}>
|
||||
|
@ -1,6 +1,6 @@
|
||||
// Generated TypeScript types from Pydantic models
|
||||
// Source: src/backend/models.py
|
||||
// Generated on: 2025-06-02T23:24:36.213957
|
||||
// Generated on: 2025-06-03T02:54:38.566349
|
||||
// DO NOT EDIT MANUALLY - This file is auto-generated
|
||||
|
||||
// ============================
|
||||
@ -367,7 +367,6 @@ export interface ChatSession {
|
||||
lastActivity?: Date;
|
||||
title?: string;
|
||||
context: ChatContext;
|
||||
messages?: Array<ChatMessage>;
|
||||
isArchived: boolean;
|
||||
systemPrompt?: string;
|
||||
}
|
||||
|
@ -3032,15 +3032,9 @@ async def delete_chat_session(
|
||||
|
||||
# Delete all messages associated with this session
|
||||
try:
|
||||
await database.delete_chat_messages(session_id)
|
||||
chat_messages = await database.get_chat_messages(session_id)
|
||||
message_count = len(chat_messages)
|
||||
|
||||
# Delete each message
|
||||
for message_data in chat_messages:
|
||||
message_id = message_data.get("id")
|
||||
if message_id:
|
||||
await database.delete_chat_message(session_id, message_id)
|
||||
|
||||
message_count = len(chat_messages)
|
||||
logger.info(f"🗑️ Deleted {message_count} messages from session {session_id}")
|
||||
|
||||
except Exception as e:
|
||||
@ -3092,15 +3086,9 @@ async def reset_chat_session(
|
||||
|
||||
# Delete all messages associated with this session
|
||||
try:
|
||||
await database.delete_chat_messages(session_id)
|
||||
chat_messages = await database.get_chat_messages(session_id)
|
||||
message_count = len(chat_messages)
|
||||
|
||||
# Delete each message
|
||||
for message_data in chat_messages:
|
||||
message_id = message_data.get("id")
|
||||
if message_id:
|
||||
await database.delete_chat_message(session_id, message_id)
|
||||
|
||||
message_count = len(chat_messages)
|
||||
logger.info(f"🗑️ Deleted {message_count} messages from session {session_id}")
|
||||
|
||||
except Exception as e:
|
||||
|
@ -774,7 +774,7 @@ class ChatSession(BaseModel):
|
||||
last_activity: datetime = Field(default_factory=lambda: datetime.now(UTC), alias="lastActivity")
|
||||
title: Optional[str] = None
|
||||
context: ChatContext
|
||||
messages: Optional[List[ChatMessage]] = None
|
||||
# messages: Optional[List[ChatMessage]] = None
|
||||
is_archived: bool = Field(False, alias="isArchived")
|
||||
system_prompt: Optional[str] = Field(None, alias="systemPrompt")
|
||||
model_config = {
|
||||
|
Loading…
x
Reference in New Issue
Block a user