backstory/frontend/src/pages/ResumeBuilderPage.tsx

390 lines
14 KiB
TypeScript

import React, { useState, useCallback, useRef } from 'react';
import {
Tabs,
Tab,
Box,
} from '@mui/material';
import { SxProps } from '@mui/material';
import { BackstoryQuery } from 'components/BackstoryQuery';
import { Conversation } from 'components/Conversation';
import { BackstoryPageProps } from 'components/BackstoryTab';
import { ChatQuery, ChatMessage } from "types/types";
import './ResumeBuilderPage.css';
/**
* ResumeBuilder component
*
* A responsive component that displays job descriptions, generated resumes and fact checks
* with different layouts for mobile and desktop views.
*/
const ResumeBuilderPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps) => {
const {
sx,
setSnack,
submitQuery,
} = props
// State for editing job description
const [hasJobDescription, setHasJobDescription] = useState<boolean>(false);
const [hasResume, setHasResume] = useState<boolean>(false);
const [hasFacts, setHasFacts] = useState<boolean>(false);
const jobConversationRef = useRef<any>(null);
const resumeConversationRef = useRef<any>(null);
const factsConversationRef = useRef<any>(null);
const [activeTab, setActiveTab] = useState<number>(0);
/**
* Handle tab change for mobile view
*/
const handleTabChange = (_event: React.SyntheticEvent, newValue: number): void => {
setActiveTab(newValue);
};
const handleJobQuery = (query: ChatQuery) => {
console.log(`handleJobQuery: ${query.prompt} -- `, jobConversationRef.current ? ' sending' : 'no handler');
jobConversationRef.current?.submitQuery(query);
};
const handleResumeQuery = (query: ChatQuery) => {
console.log(`handleResumeQuery: ${query.prompt} -- `, resumeConversationRef.current ? ' sending' : 'no handler');
resumeConversationRef.current?.submitQuery(query);
};
const handleFactsQuery = (query: ChatQuery) => {
console.log(`handleFactsQuery: ${query.prompt} -- `, factsConversationRef.current ? ' sending' : 'no handler');
factsConversationRef.current?.submitQuery(query);
};
const filterJobDescriptionMessages = useCallback((messages: ChatMessage[]): ChatMessage[] => {
if (messages === undefined || messages.length === 0) {
return [];
}
if (messages.length > 0) {
// messages[0].role = 'content';
// messages[0].title = 'Job Description';
// messages[0].disableCopy = false;
// messages[0].expandable = true;
}
if (-1 !== messages.findIndex(m => m.status === 'done')) { // || (m.actions && m.actions.includes("resume_generated")))) {
setHasResume(true);
setHasFacts(true);
}
return messages;
if (messages.length > 1) {
setHasResume(true);
setHasFacts(true);
}
if (messages.length > 3) {
// messages[2] is Show job requirements
// messages[3].role = 'job-requirements';
// messages[3].title = 'Job Requirements';
// messages[3].disableCopy = false;
// messages[3].expanded = false;
// messages[3].expandable = true;
}
/* Filter out the 2nd and 3rd (0-based) */
const filtered = messages;//.filter((m, i) => i !== 1 && i !== 2);
console.warn("Set filtering back on");
return filtered;
}, [setHasResume, setHasFacts]);
const filterResumeMessages = useCallback((messages: ChatMessage[]): ChatMessage[] => {
if (messages === undefined || messages.length === 0) {
return [];
}
return messages;
if (messages.length > 1) {
// messages[0] is Show Qualifications
// messages[1].role = 'qualifications';
// messages[1].title = 'Candidate qualifications';
// messages[1].disableCopy = false;
// messages[1].expanded = false;
// messages[1].expandable = true;
}
if (messages.length > 3) {
// messages[2] is Show Resume
// messages[3].role = 'resume';
// messages[3].title = 'Generated Resume';
// messages[3].disableCopy = false;
// messages[3].expanded = true;
// messages[3].expandable = true;
}
/* Filter out the 1st and 3rd messages (0-based) */
const filtered = messages.filter((m, i) => i !== 0 && i !== 2);
return filtered;
}, []);
const filterFactsMessages = useCallback((messages: ChatMessage[]): ChatMessage[] => {
if (messages === undefined || messages.length === 0) {
return [];
}
if (messages.length > 1) {
// messages[0] is Show verification
// messages[1].role = 'fact-check';
// messages[1].title = 'Fact Check';
// messages[1].disableCopy = false;
// messages[1].expanded = true;
// messages[1].expandable = true;
}
/* Filter out the 1st (0-based) */
const filtered = messages.filter((m, i) => i !== 0);
return filtered;
}, []);
const jobResponse = useCallback(async (message: ChatMessage) => {
// if (message.actions && message.actions.includes("job_description")) {
// if (jobConversationRef.current) {
// await jobConversationRef.current.fetchHistory();
// }
// }
// if (message.actions && message.actions.includes("resume_generated")) {
// if (resumeConversationRef.current) {
// await resumeConversationRef.current.fetchHistory();
// }
// setHasResume(true);
// setActiveTab(1); // Switch to Resume tab
// }
// if (message.actions && message.actions.includes("facts_checked")) {
// if (factsConversationRef.current) {
// await factsConversationRef.current.fetchHistory();
// }
// setHasFacts(true);
// }
}, [setHasFacts, setHasResume, setActiveTab]);
const resumeResponse = useCallback((message: ChatMessage): void => {
console.log('onResumeResponse', message);
setHasFacts(true);
}, [setHasFacts]);
const factsResponse = useCallback((message: ChatMessage): void => {
console.log('onFactsResponse', message);
}, []);
const resetJobDescription = useCallback(() => {
setHasJobDescription(false);
setHasResume(false);
setHasFacts(false);
}, [setHasJobDescription, setHasResume, setHasFacts]);
const resetResume = useCallback(() => {
setHasResume(false);
setHasFacts(false);
}, [setHasResume, setHasFacts]);
const resetFacts = useCallback(() => {
setHasFacts(false);
}, [setHasFacts]);
return (<Box>Not re-implmented yet</Box>);
// const renderJobDescriptionView = useCallback((sx?: SxProps) => {
// console.log('renderJobDescriptionView');
// const jobDescriptionQuestions = [
// <Box sx={{ display: "flex", flexDirection: "column" }}>
// <BackstoryQuery query={{ prompt: "What are the key skills necessary for this position?", tunables: { enableTools: false } }} submitQuery={handleJobQuery} />
// <BackstoryQuery query={{ prompt: "How much should this position pay (accounting for inflation)?", tunables: { enableTools: false } }} submitQuery={handleJobQuery} />
// </Box>,
// ];
// const jobDescriptionPreamble: ChatMessage[] = [{
// role: 'info',
// content: `Once you paste a job description and press **Generate Resume**, Backstory will perform the following actions:
// 1. **Job Analysis**: LLM extracts requirements from '\`Job Description\`' to generate a list of desired '\`Skills\`'.
// 2. **Candidate Analysis**: LLM determines candidate qualifications by performing skill assessments.
// For each '\`Skill\`' from **Job Analysis** phase:
// 1. **RAG**: Retrieval Augmented Generation collection is queried for context related content for each '\`Skill\`'.
// 2. **Evidence Creation**: LLM is queried to generate supporting evidence of '\`Skill\`' from the '\`RAG\`' and '\`Candidate Resume\`'.
// 3. **Resume Generation**: LLM is provided the output from the **Candidate Analysis:Evidence Creation** phase and asked to generate a professional resume.
// See [About > Resume Generation Architecture](/about/resume-generation) for more details.
// `,
// disableCopy: true
// }];
// if (!hasJobDescription) {
// return <Conversation
// ref={jobConversationRef}
// {...{
// type: "job_description",
// actionLabel: "Generate Resume",
// preamble: jobDescriptionPreamble,
// hidePreamble: true,
// placeholder: "Paste a job description, then click Generate...",
// multiline: true,
// resetLabel: `job description${hasFacts ? ", resume, and fact check" : hasResume ? " and resume" : ""}`,
// messageFilter: filterJobDescriptionMessages,
// resetAction: resetJobDescription,
// onResponse: jobResponse,
// sessionId,
// setSnack,
// submitQuery,
// sx,
// }}
// />
// } else {
// return <Conversation
// ref={jobConversationRef}
// {...{
// type: "job_description",
// actionLabel: "Send",
// placeholder: "Ask a question about this job description...",
// resetLabel: `job description${hasFacts ? ", resume, and fact check" : hasResume ? " and resume" : ""}`,
// messageFilter: filterJobDescriptionMessages,
// defaultPrompts: jobDescriptionQuestions,
// resetAction: resetJobDescription,
// onResponse: jobResponse,
// sessionId,
// setSnack,
// submitQuery,
// sx,
// }}
// />
// }
// }, [filterJobDescriptionMessages, hasJobDescription, sessionId, setSnack, jobResponse, resetJobDescription, hasFacts, hasResume, submitQuery]);
// /**
// * Renders the resume view with loading indicator
// */
// const renderResumeView = useCallback((sx?: SxProps) => {
// const resumeQuestions = [
// <Box sx={{ display: "flex", flexDirection: "column" }}>
// <BackstoryQuery query={{ prompt: "Is this resume a good fit for the provided job description?", tunables: { enableTools: false } }} submitQuery={handleResumeQuery} />
// <BackstoryQuery query={{ prompt: "Provide a more concise resume.", tunables: { enableTools: false } }} submitQuery={handleResumeQuery} />
// </Box>,
// ];
// if (!hasFacts) {
// return <Conversation
// ref={resumeConversationRef}
// {...{
// type: "resume",
// actionLabel: "Fact Check",
// defaultQuery: "Fact check the resume.",
// resetLabel: `job description${hasFacts ? ", resume, and fact check" : hasResume ? " and resume" : ""}`,
// messageFilter: filterResumeMessages,
// onResponse: resumeResponse,
// resetAction: resetResume,
// sessionId,
// setSnack,
// submitQuery,
// sx,
// }}
// />
// } else {
// return <Conversation
// ref={resumeConversationRef}
// {...{
// type: "resume",
// actionLabel: "Send",
// placeholder: "Ask a question about this job resume...",
// resetLabel: `job description${hasFacts ? ", resume, and fact check" : hasResume ? " and resume" : ""}`,
// messageFilter: filterResumeMessages,
// onResponse: resumeResponse,
// resetAction: resetResume,
// sessionId,
// setSnack,
// defaultPrompts: resumeQuestions,
// submitQuery,
// sx,
// }}
// />
// }
// }, [filterResumeMessages, hasFacts, sessionId, setSnack, resumeResponse, resetResume, hasResume, submitQuery]);
// /**
// * Renders the fact check view
// */
// const renderFactCheckView = useCallback((sx?: SxProps) => {
// const factsQuestions = [
// <Box sx={{ display: "flex", flexDirection: "column" }}>
// <BackstoryQuery query={{ prompt: "Rewrite the resume to address any discrepancies.", tunables: { enableTools: false } }} submitQuery={handleFactsQuery} />
// </Box>,
// ];
// return <Conversation
// ref={factsConversationRef}
// {...{
// type: "fact_check",
// actionLabel: "Send",
// placeholder: "Ask a question about any discrepencies...",
// resetLabel: `job description${hasFacts ? ", resume, and fact check" : hasResume ? " and resume" : ""}`,
// messageFilter: filterFactsMessages,
// defaultPrompts: factsQuestions,
// resetAction: resetFacts,
// onResponse: factsResponse,
// sessionId,
// submitQuery,
// setSnack,
// sx,
// }}
// />
// }, [ sessionId, setSnack, factsResponse, filterFactsMessages, resetFacts, hasResume, hasFacts, submitQuery]);
// return (
// <Box className="ResumeBuilder"
// sx={{
// p: 0,
// m: 0,
// display: "flex",
// flexGrow: 1,
// margin: "0 auto",
// overflow: "hidden",
// backgroundColor: "#F5F5F5",
// flexDirection: "column",
// maxWidth: "1024px",
// }}
// >
// {/* Tabs */}
// <Tabs
// value={activeTab}
// onChange={handleTabChange}
// variant="fullWidth"
// sx={{ bgcolor: 'background.paper' }}
// >
// <Tab value={0} label="Job Description" />
// {hasResume && <Tab value={1} label="Resume" />}
// {hasFacts && <Tab value={2} label="Fact Check" />}
// </Tabs>
// {/* Document display area */}
// <Box sx={{
// display: 'flex', flexDirection: 'column', flexGrow: 1, p: 0, width: "100%", ...sx,
// overflow: "hidden"
// }}>
// <Box sx={{ display: activeTab === 0 ? "flex" : "none" }}>{renderJobDescriptionView(/*{ height: "calc(100% - 72px - 48px)" }*/)}</Box>
// <Box sx={{ display: activeTab === 1 ? "flex" : "none" }}>{renderResumeView(/*{ height: "calc(100% - 72px - 48px)" }*/)}</Box>
// <Box sx={{ display: activeTab === 2 ? "flex" : "none" }}>{renderFactCheckView(/*{ height: "calc(100% - 72px - 48px)" }*/)}</Box>
// </Box>
// </Box>
// );
};
export {
ResumeBuilderPage
};