import React, { useEffect, useState, useCallback } from 'react'; import { Typography, Card, Button, Tabs, Tab, Paper, IconButton, Box, useMediaQuery, Divider, Slider, Stack, TextField, Tooltip } from '@mui/material'; import { useTheme } from '@mui/material/styles'; import SendIcon from '@mui/icons-material/Send'; import { ChevronLeft, ChevronRight, SwapHoriz, RestartAlt as ResetIcon, } from '@mui/icons-material'; import PropagateLoader from "react-spinners/PropagateLoader"; import { Message } from './Message'; import { Document } from './Document'; import { DocumentViewerProps } from './DocumentTypes'; import MuiMarkdown from 'mui-markdown'; /** * DocumentViewer component * * A responsive component that displays job descriptions, generated resumes and fact checks * with different layouts for mobile and desktop views. */ const DocumentViewer: React.FC = ({ generateResume, jobDescription, factCheck, resume, setResume, facts, setFacts, sx }) => { // State for editing job description const [editJobDescription, setEditJobDescription] = useState(jobDescription); // Processing state to show loading indicators const [processing, setProcessing] = useState(undefined); // Theme and responsive design setup const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); // State for controlling which document is active on mobile const [activeTab, setActiveTab] = useState(0); // State for controlling split ratio on desktop const [splitRatio, setSplitRatio] = useState(0); /** * Reset processing state when resume is generated */ useEffect(() => { if (resume !== undefined && processing === "resume") { setProcessing(undefined); } }, [processing, resume]); /** * Reset processing state when facts is generated */ useEffect(() => { if (facts !== undefined && processing === "facts") { setProcessing(undefined); } }, [processing, facts]); /** * Trigger resume generation and update UI state */ const triggerGeneration = useCallback((description: string | undefined) => { if (description === undefined) { setProcessing(undefined); setResume(undefined); setActiveTab(0); return; } setProcessing("resume"); setTimeout(() => { setActiveTab(1); }, 250); // Switch to resume view on mobile generateResume(description); }, [generateResume, setProcessing, setActiveTab, setResume]); /** * Trigger fact check and update UI state */ const triggerFactCheck = useCallback((resume: string | undefined) => { if (resume === undefined) { setProcessing(undefined); setResume(undefined); setFacts(undefined); setActiveTab(1); return; } setProcessing("facts"); factCheck(resume); setTimeout(() => { setActiveTab(2); }, 250); // Switch to resume view on mobile }, [factCheck, setResume, setProcessing, setActiveTab, setFacts]); useEffect(() => { setEditJobDescription(jobDescription); }, [jobDescription, setEditJobDescription]); /** * Switch to resume tab when resume become available */ useEffect(() => { if (resume !== undefined) { setTimeout(() => { setActiveTab(1); }, 250); // Switch to resume view on mobile } }, [resume]); /** * Switch to fact check tab when facts become available */ useEffect(() => { if (facts !== undefined) { setTimeout(() => { setActiveTab(2); }, 250); // Switch to resume view on mobile } }, [facts]); /** * Handle tab change for mobile view */ const handleTabChange = (_event: React.SyntheticEvent, newValue: number): void => { setActiveTab(newValue); }; /** * Adjust split ratio for desktop view */ const handleSliderChange = (_event: Event, newValue: number | number[]): void => { setSplitRatio(newValue as number); }; /** * Reset split ratio to default */ const resetSplit = (): void => { setSplitRatio(50); }; /** * Handle keyboard shortcuts */ const handleKeyPress = (event: React.KeyboardEvent): void => { if (event.key === 'Enter' && event.ctrlKey) { triggerGeneration(editJobDescription || ""); } }; const renderJobDescriptionView = () => { const children = []; if (resume === undefined && processing === undefined) { children.push( setEditJobDescription(e.target.value)} onKeyDown={handleKeyPress} placeholder="Paste a job description, then click Generate..." /> ); } else { children.push({editJobDescription}) } children.push( { setEditJobDescription(""); triggerGeneration(undefined); }} > ); return children; } /** * Renders the resume view with loading indicator */ const renderResumeView = () => ( {resume !== undefined && } {processing === "resume" && ( Generating resume... )} ); /** * Renders the fact check view */ const renderFactCheckView = () => ( {facts !== undefined && } {/*

          With over 20 years of experience as a software architect, team lead, and developer, James Ketrenos brings a unique blend of technical expertise and leadership to the table. Focused on advancing energy-efficient AI solutions, he excels in designing, building, and deploying scalable systems that enable rapid product development. His extensive background in Linux software architecture, DevOps, and open-source technologies makes him an ideal candidate for leading roles at technology-driven companies.

          ---
        
*/}
{processing === "facts" && ( Fact Checking resume... )}
); // Render mobile view if (isMobile) { /** * Gets the appropriate content based on active tab */ const getActiveMobileContent = () => { switch (activeTab) { case 0: return renderJobDescriptionView(); case 1: return renderResumeView(); case 2: return renderFactCheckView(); default: return renderJobDescriptionView(); } }; return ( {/* Tabs */} {(resume !== undefined || processing === "resume") && } {(facts !== undefined || processing === "facts") && } {/* Document display area */} {getActiveMobileContent()} ); } /** * Gets the appropriate content based on active state for Desktop */ const getActiveDesktopContent = () => { /* Left panel - Job Description */ const showResume = resume !== undefined || processing === "resume" const showFactCheck = facts !== undefined || processing === "facts" const otherRatio = showResume ? (100 - splitRatio / 2) : 100; const children = []; children.push( {renderJobDescriptionView()} ); /* Resume panel - conditionally rendered if resume defined, or processing is in progress */ if (showResume) { children.push( {renderResumeView()} ); } /* Fact Check panel - conditionally rendered if facts defined, or processing is in progress */ if (showFactCheck) { children.push( {renderFactCheckView()} ); } /* Split control panel - conditionally rendered if either facts or resume is set */ let slider = ; if (showResume || showFactCheck) { slider = ( setSplitRatio(Math.max(0, splitRatio - 10))}> setSplitRatio(Math.min(100, splitRatio + 10))}> ); } return ( {children} {slider} ) } return ( {getActiveDesktopContent()} ); }; /** * Props for the ResumeActionCard component */ interface ResumeActionCardProps { resume: any; processing: string | undefined; triggerFactCheck: (resume: string | undefined) => void; } /** * Action card displayed underneath the resume with notes and fact check button */ const ResumeActionCard: React.FC = ({ resume, processing, triggerFactCheck }) => ( {resume !== undefined || processing === "resume" ? ( NOTE: As with all LLMs, hallucination is always a possibility. Click Fact Check to have the LLM analyze the generated resume vs. the actual resume. ) : ( Once you click Generate under the Job Description, a resume will be generated based on the user's RAG content and the job description. )} { triggerFactCheck(undefined); }} > ); export { DocumentViewer };