From 138f61b777a2115ba57d3a29c9f985e8aead6e7d Mon Sep 17 00:00:00 2001 From: James Ketrenos Date: Mon, 2 Jun 2025 20:24:46 -0700 Subject: [PATCH] Working on Job Analysis --- frontend/src/components/BackstoryQuery.tsx | 14 +-- .../src/components/DeleteConfirmation.tsx | 8 +- frontend/src/components/StyledMarkdown.tsx | 7 +- .../src/components/layout/BackstoryLayout.tsx | 2 +- .../src/components/layout/BackstoryRoutes.tsx | 2 + frontend/src/pages/CandidateChatPage.tsx | 14 ++- frontend/src/pages/DocsPage.tsx | 1 - frontend/src/pages/FindCandidatePage.tsx | 2 +- frontend/src/pages/JobAnalysisPage.tsx | 112 ++++++++++-------- frontend/src/pages/OldChatPage.tsx | 4 +- frontend/src/types/types.ts | 3 +- src/backend/main.py | 20 +--- src/backend/models.py | 2 +- 13 files changed, 104 insertions(+), 87 deletions(-) diff --git a/frontend/src/components/BackstoryQuery.tsx b/frontend/src/components/BackstoryQuery.tsx index 074495b..692ffac 100644 --- a/frontend/src/components/BackstoryQuery.tsx +++ b/frontend/src/components/BackstoryQuery.tsx @@ -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 ({query.prompt}); + return ({question.question}); } return ( ); } diff --git a/frontend/src/components/DeleteConfirmation.tsx b/frontend/src/components/DeleteConfirmation.tsx index f87d959..643d15b 100644 --- a/frontend/src/components/DeleteConfirmation.tsx +++ b/frontend/src/components/DeleteConfirmation.tsx @@ -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} diff --git a/frontend/src/components/StyledMarkdown.tsx b/frontend/src/components/StyledMarkdown.tsx index 2a740de..cf24b57 100644 --- a/frontend/src/components/StyledMarkdown.tsx +++ b/frontend/src/components/StyledMarkdown.tsx @@ -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 = (props: StyledMarkdownProp const queryString = props.query.replace(/(\w+):/g, '"$1":'); try { const query = JSON.parse(queryString); + const backstoryQuestion: CandidateQuestion = { + question: queryString + } - return + return } catch (e) { console.log("StyledMarkdown error:", queryString, e); return props.query; diff --git a/frontend/src/components/layout/BackstoryLayout.tsx b/frontend/src/components/layout/BackstoryLayout.tsx index 936c3de..c3dfb4d 100644 --- a/frontend/src/components/layout/BackstoryLayout.tsx +++ b/frontend/src/components/layout/BackstoryLayout.tsx @@ -44,7 +44,7 @@ const ViewerNavItems: NavigationLinkType[] = [ const CandidateNavItems : NavigationLinkType[]= [ { name: 'Chat', path: '/chat', icon: }, - // { name: 'Job Analysis', path: '/candidate/job-analysis', icon: }, + { name: 'Job Analysis', path: '/candidate/job-analysis', icon: }, { name: 'Resume Builder', path: '/candidate/resume-builder', icon: }, // { name: 'Knowledge Explorer', path: '/candidate/knowledge-explorer', icon: }, // { name: 'Dashboard', icon: , path: '/candidate/dashboard' }, diff --git a/frontend/src/components/layout/BackstoryRoutes.tsx b/frontend/src/components/layout/BackstoryRoutes.tsx index d2941da..660ba15 100644 --- a/frontend/src/components/layout/BackstoryRoutes.tsx +++ b/frontend/src/components/layout/BackstoryRoutes.tsx @@ -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 = () => (Backstory); const ResumesPage = () => (Resumes); @@ -70,6 +71,7 @@ const getBackstoryDynamicRoutes = (props: BackstoryDynamicRoutesProps): ReactNod routes.splice(-1, 0, ...[ } />, } />, + } />, } />, } />, } />, diff --git a/frontend/src/pages/CandidateChatPage.tsx b/frontend/src/pages/CandidateChatPage.tsx index 207c627..7b0703f 100644 --- a/frontend/src/pages/CandidateChatPage.tsx +++ b/frontend/src/pages/CandidateChatPage.tsx @@ -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((pr } - + {selectedCandidate.questions?.length !== 0 && selectedCandidate.questions?.map(q => )} {/* Fixed Message Input */} { 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((pr onEnter={sendMessage} disabled={streaming || loading} /> + + + + ); diff --git a/frontend/src/pages/DocsPage.tsx b/frontend/src/pages/DocsPage.tsx index d4514de..2d3e96f 100644 --- a/frontend/src/pages/DocsPage.tsx +++ b/frontend/src/pages/DocsPage.tsx @@ -145,7 +145,6 @@ const documents : DocType[] = [ { title: "Authentication Architecture", route: "authentication.md", description: "Complete authentication architecture", icon: }, { title: "UI Overview", route: "ui-overview", description: "Guide to the user interface components and interactions", icon: }, { title: "UI Mockup", route: "ui-mockup", description: "Visual previews of interfaces and layout concepts", icon: }, - { title: "Chat Mockup", route: "mockup-chat-system", description: "Mockup of chat system", icon: }, { title: "Theme Visualizer", route: "theme-visualizer", description: "Explore and customize application themes and visual styles", icon: }, { title: "App Analysis", route: "app-analysis", description: "Statistics and performance metrics of the application", icon: }, { title: 'Text Mockups', route: "backstory-ui-mockups", description: "Early text mockups of many of the interaction points." }, diff --git a/frontend/src/pages/FindCandidatePage.tsx b/frontend/src/pages/FindCandidatePage.tsx index 6072baf..5487e0f 100644 --- a/frontend/src/pages/FindCandidatePage.tsx +++ b/frontend/src/pages/FindCandidatePage.tsx @@ -55,7 +55,7 @@ const CandidateListingPage = (props: BackstoryPageProps) => { Generate your own perfect AI candidate! - + {candidates?.map((u, i) => { setSelectedCandidate(u); navigate("/chat"); }} diff --git a/frontend/src/pages/JobAnalysisPage.tsx b/frontend/src/pages/JobAnalysisPage.tsx index bf4a945..5291721 100644 --- a/frontend/src/pages/JobAnalysisPage.tsx +++ b/frontend/src/pages/JobAnalysisPage.tsx @@ -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 = (props: BackstoryPageProps) => { const theme = useTheme(); const { user } = useAuth(); + const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate() // State management const [activeStep, setActiveStep] = useState(0); - const [selectedCandidate, setSelectedCandidate] = useState(null); const [jobDescription, setJobDescription] = useState(''); const [jobTitle, setJobTitle] = useState(''); const [jobLocation, setJobLocation] = useState(''); @@ -58,7 +59,7 @@ const JobAnalysisPage: React.FC = (props: BackstoryPageProps const [candidates, setCandidates] = useState(null); useEffect(() => { - if (candidates !== null) { + if (candidates !== null || selectedCandidate) { return; } const getCandidates = async () => { @@ -85,11 +86,20 @@ const JobAnalysisPage: React.FC = (props: BackstoryPageProps getCandidates(); }, [candidates, setSnack]); + useEffect(() => { + if (selectedCandidate && activeStep === 0) { + setActiveStep(1); + } + }, [selectedCandidate, activeStep]); + // Steps in our process - const steps = [ - { label: 'Select Candidate', icon: }, - { label: 'Job Description', icon: }, - { label: 'View Analysis', icon: } + const steps = selectedCandidate === null ? [ + { index: 0, label: 'Select Candidate', icon: }, + { index: 1, label: 'Job Description', icon: }, + { index: 2, label: 'View Analysis', icon: } + ] : [ + { index: 1, label: 'Job Description', icon: }, + { index: 2, label: 'View Analysis', icon: } ]; // Mock handlers for our analysis APIs @@ -235,7 +245,7 @@ const JobAnalysisPage: React.FC = (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 = (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 = (props: BackstoryPageProps ); + const extraInfo = false; + // Render function for the job description step const renderJobDescription = () => ( - - Enter Job Details - - - - - setJobTitle(e.target.value)} - required - margin="normal" - /> + {extraInfo && <> + + Enter Job Details + + + + + setJobTitle(e.target.value)} + required + margin="normal" + /> + + + + setJobLocation(e.target.value)} + margin="normal" + /> + - - - setJobLocation(e.target.value)} - margin="normal" - /> - - + + } + Job Description - + } = (props: BackstoryPageProps /> The job description will be used to extract requirements for candidate matching. - - + ); @@ -455,13 +471,13 @@ const JobAnalysisPage: React.FC = (props: BackstoryPageProps - {steps.map((step, index) => ( - + {steps.map(step => ( + ( = 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 = (props: BackstoryPageProps - {activeStep === steps.length - 1 ? ( + {activeStep === steps[steps.length - 1].index ? ( ) : ( )} diff --git a/frontend/src/pages/OldChatPage.tsx b/frontend/src/pages/OldChatPage.tsx index a346b73..c58f549 100644 --- a/frontend/src/pages/OldChatPage.tsx +++ b/frontend/src/pages/OldChatPage.tsx @@ -27,8 +27,8 @@ const ChatPage = forwardRef((props: Back setQuestions([ - {candidate.questions?.map(({ question, tunables }, i: number) => - + {candidate.questions?.map((q, i: number) => + )} , diff --git a/frontend/src/types/types.ts b/frontend/src/types/types.ts index 9dde1b7..9dcf9ab 100644 --- a/frontend/src/types/types.ts +++ b/frontend/src/types/types.ts @@ -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; isArchived: boolean; systemPrompt?: string; } diff --git a/src/backend/main.py b/src/backend/main.py index fd4ce61..e2a3b84 100644 --- a/src/backend/main.py +++ b/src/backend/main.py @@ -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: diff --git a/src/backend/models.py b/src/backend/models.py index 2bb4517..b3cab3d 100644 --- a/src/backend/models.py +++ b/src/backend/models.py @@ -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 = {