import React, { useState, useCallback, useRef } from 'react'; import { Tabs, Tab, Box, } from '@mui/material'; import { SxProps } from '@mui/material'; import { ChatQuery, Query } from './ChatQuery'; import { MessageList, BackstoryMessage } from './Message'; import { Conversation } from './Conversation'; import { BackstoryPageProps } from './BackstoryTab'; 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 = (props: BackstoryPageProps) => { const { sx, sessionId, setSnack, submitQuery, } = props // State for editing job description const [hasJobDescription, setHasJobDescription] = useState(false); const [hasResume, setHasResume] = useState(false); const [hasFacts, setHasFacts] = useState(false); const jobConversationRef = useRef(null); const resumeConversationRef = useRef(null); const factsConversationRef = useRef(null); const [activeTab, setActiveTab] = useState(0); /** * Handle tab change for mobile view */ const handleTabChange = (_event: React.SyntheticEvent, newValue: number): void => { setActiveTab(newValue); }; const handleJobQuery = (query: Query) => { console.log(`handleJobQuery: ${query.prompt} -- `, jobConversationRef.current ? ' sending' : 'no handler'); jobConversationRef.current?.submitQuery(query); }; const handleResumeQuery = (query: Query) => { console.log(`handleResumeQuery: ${query.prompt} -- `, resumeConversationRef.current ? ' sending' : 'no handler'); resumeConversationRef.current?.submitQuery(query); }; const handleFactsQuery = (query: Query) => { console.log(`handleFactsQuery: ${query.prompt} -- `, factsConversationRef.current ? ' sending' : 'no handler'); factsConversationRef.current?.submitQuery(query); }; const filterJobDescriptionMessages = useCallback((messages: MessageList): MessageList => { 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')) { 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: MessageList): MessageList => { 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: MessageList): MessageList => { 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: BackstoryMessage) => { console.log('onJobResponse', message); if (message.actions && message.actions.includes("job_description")) { await jobConversationRef.current.fetchHistory(); } if (message.actions && message.actions.includes("resume_generated")) { await resumeConversationRef.current.fetchHistory(); setHasResume(true); setActiveTab(1); // Switch to Resume tab } if (message.actions && message.actions.includes("facts_checked")) { await factsConversationRef.current.fetchHistory(); setHasFacts(true); } }, [setHasFacts, setHasResume, setActiveTab]); const resumeResponse = useCallback((message: BackstoryMessage): void => { console.log('onResumeResponse', message); setHasFacts(true); }, [setHasFacts]); const factsResponse = useCallback((message: BackstoryMessage): 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]); const renderJobDescriptionView = useCallback((sx: SxProps) => { console.log('renderJobDescriptionView'); const jobDescriptionQuestions = [ , ]; const jobDescriptionPreamble: MessageList = [{ 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 } else { return } }, [filterJobDescriptionMessages, hasJobDescription, sessionId, setSnack, jobResponse, resetJobDescription, hasFacts, hasResume, submitQuery]); /** * Renders the resume view with loading indicator */ const renderResumeView = useCallback((sx: SxProps) => { const resumeQuestions = [ , ]; if (!hasFacts) { return } else { return } }, [filterResumeMessages, hasFacts, sessionId, setSnack, resumeResponse, resetResume, hasResume, submitQuery]); /** * Renders the fact check view */ const renderFactCheckView = useCallback((sx: SxProps) => { const factsQuestions = [ , ]; return }, [ sessionId, setSnack, factsResponse, filterFactsMessages, resetFacts, hasResume, hasFacts, submitQuery]); return ( {/* Tabs */} {hasResume && } {hasFacts && } {/* Document display area */} {renderJobDescriptionView({ height: "calc(100vh - 72px - 48px)" })} {renderResumeView({ height: "calc(100vh - 72px - 48px)" })} {renderFactCheckView({ height: "calc(100vh - 72px - 48px)" })} ); }; export { ResumeBuilderPage };