From 52ec9fdcf042c2e7af9dc7532ac09d5da19874c6 Mon Sep 17 00:00:00 2001 From: James Ketrenos Date: Sat, 26 Apr 2025 13:17:47 -0700 Subject: [PATCH] Restyling in progress --- frontend/src/App.css | 16 +- frontend/src/App.tsx | 266 ++++++++++++++++++++------------- frontend/src/AutoScroll.tsx | 131 ++++++++++++++++ frontend/src/ChatBubble.tsx | 2 +- frontend/src/Conversation.css | 13 ++ frontend/src/Conversation.tsx | 76 ++-------- frontend/src/Message.tsx | 2 +- frontend/src/ResumeBuilder.tsx | 150 +++++++++++-------- frontend/src/index.css | 1 + src/server.py | 15 +- 10 files changed, 420 insertions(+), 252 deletions(-) create mode 100644 frontend/src/AutoScroll.tsx create mode 100644 frontend/src/Conversation.css diff --git a/frontend/src/App.css b/frontend/src/App.css index ebce5fa..4331fa7 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,3 +1,7 @@ +.App { + overflow: hidden; +} + div { box-sizing: border-box; overflow-wrap: break-word; @@ -114,18 +118,6 @@ button { background-color: #D3CDBF; } -.Conversation { - display: flex; - background-color: #F5F5F5; - border: 1px solid #E0E0E0; - flex-grow: 1; - padding: 10px; - flex-direction: column; - font-size: 0.9rem; - width: 100%; - margin: 0 auto; -} - .user-message.MuiCard-root { background-color: #DCF8C6; border: 1px solid #B2E0A7; diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8484401..ac269b9 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef, useCallback } from 'react'; +import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'; import useMediaQuery from '@mui/material/useMediaQuery'; import Card from '@mui/material/Card'; import { styled } from '@mui/material/styles'; @@ -23,8 +23,10 @@ import { Snack, SeverityType } from './Snack'; import { VectorVisualizer } from './VectorVisualizer'; import { Controls } from './Controls'; import { Conversation, ConversationHandle } from './Conversation'; +import { useAutoScrollToBottom } from './AutoScroll'; import './App.css'; +import './Conversation.css'; import '@fontsource/roboto/300.css'; import '@fontsource/roboto/400.css'; @@ -33,7 +35,6 @@ import '@fontsource/roboto/700.css'; import MuiMarkdown from 'mui-markdown'; - const getConnectionBase = (loc: any): string => { if (!loc.host.match(/.*battle-linux.*/)) { return loc.protocol + "//" + loc.host; @@ -69,9 +70,10 @@ function CustomTabPanel(props: TabPanelProps) { const App = () => { const [sessionId, setSessionId] = useState(undefined); const [connectionBase,] = useState(getConnectionBase(window.location)) + const [selectedPath, setSelectedPath] = useState(""); const [menuOpen, setMenuOpen] = useState(false); const [isMenuClosing, setIsMenuClosing] = useState(false); - const [tab, setTab] = useState(0); + const [activeTab, setActiveTab] = useState(0); const [about, setAbout] = useState(""); const isDesktop = useMediaQuery('(min-width:650px)'); const prevIsDesktopRef = useRef(isDesktop); @@ -79,6 +81,7 @@ const App = () => { const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); const snackRef = useRef(null); + const scrollRef = useAutoScrollToBottom(); useEffect(() => { if (prevIsDesktopRef.current === isDesktop) @@ -126,46 +129,19 @@ const App = () => { const handleSubmitChatQuery = (query: string) => { console.log(`handleSubmitChatQuery: ${query} -- `, chatRef.current ? ' sending' : 'no handler'); chatRef.current?.submitQuery(query); + setActiveTab(0); }; - const chatPreamble: MessageList = [ - { - role: 'content', - title: 'Welcome to Backstory', - content: ` -Backstory is a RAG enabled expert system with access to real-time data running self-hosted -(no cloud) versions of industry leading Large and Small Language Models (LLM/SLMs). -It was written by James Ketrenos in order to provide answers to -questions potential employers may have about his work history. - -What would you like to know about James? -` - } - ]; - - const chatQuestions = [ - - - - - - , - - - As with all LLM interactions, the results may not be 100% accurate. If you have questions about my career, - I'd love to hear from you. You can send me an email at **james_backstory@ketrenos.com**. - - - ]; - // Extract the sessionId from the URL if present, otherwise // request a sessionId from the server. + const validPaths = useMemo(() => ['chat', 'notes', 'tasks'], []); // allowed paths + useEffect(() => { const url = new URL(window.location.href); - const pathParts = url.pathname.split('/').filter(Boolean); + const pathParts = url.pathname.split('/').filter(Boolean); // [path, sessionId] - const fetchSession = async () => { + const fetchSession = async (pathOverride?: string) => { try { const response = await fetch(connectionBase + `/api/context`, { method: 'POST', @@ -179,20 +155,31 @@ What would you like to know about James? } const data = await response.json(); setSessionId(data.id); - window.history.replaceState({}, '', `/${data.id}`); + + const newPath = pathOverride || 'chat'; // default fallback + window.history.replaceState({}, '', `/${newPath}/${data.id}`); } catch (error: any) { setSnack("Server is temporarily down", "error"); - }; + } }; - if (!pathParts.length) { - console.log("No session id -- creating a new session") + if (pathParts.length < 2) { + console.log("No session id or path -- creating new session"); fetchSession(); } else { - console.log(`Session id: ${pathParts[0]} -- existing session`) - setSessionId(pathParts[0]); + const currentPath = pathParts[0]; + const session = pathParts[1]; + + if (!validPaths.includes(currentPath)) { + console.log(`Invalid path "${currentPath}" -- redirecting to default`); + fetchSession(); // or you could window.location.replace if you want + } else { + console.log(`Path: ${currentPath}, Session id: ${session}`); + setSessionId(session); + setSelectedPath(currentPath); + } } - }, [setSessionId, connectionBase, setSnack]); + }, [setSessionId, setSelectedPath, connectionBase, setSnack, validPaths]); const handleMenuClose = () => { setIsMenuClosing(true); @@ -209,24 +196,42 @@ What would you like to know about James? } }; - const settingsPanel = ( - <> - {sessionId !== undefined && - } - - ); - const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { - setTab(newValue); + setActiveTab(newValue); handleMenuClose(); }; + const handleTabSelect = (newPath: string) => { + if (!sessionId) return; // safety + setSelectedPath(newPath); + window.history.pushState({}, '', `/${newPath}/${sessionId}`); + }; + + useEffect(() => { + const handlePopState = () => { + const url = new URL(window.location.href); + const pathParts = url.pathname.split('/').filter(Boolean); + + if (pathParts.length >= 2) { + const path = pathParts[0]; + const session = pathParts[1]; + + if (validPaths.includes(path)) { + setSelectedPath(path); + setSessionId(session); + } + } + }; + + window.addEventListener('popstate', handlePopState); + return () => window.removeEventListener('popstate', handlePopState); + }, [setSelectedPath, setSessionId, validPaths]); const menuDrawer = ( ); - /* toolbar height is 56px + 8px margin-top */ - const Offset = styled('div')(({ theme }) => ({ ...theme.mixins.toolbar, minHeight: '64px', height: '64px' })); + const tabs = useMemo(() => { + const chatPreamble: MessageList = [ + { + role: 'content', + title: 'Welcome to Backstory', + content: ` + Backstory is a RAG enabled expert system with access to real-time data running self-hosted + (no cloud) versions of industry leading Large and Small Language Models (LLM/SLMs). + It was written by James Ketrenos in order to provide answers to + questions potential employers may have about his work history. + + What would you like to know about James? + ` + } + ]; + + const chatQuestions = [ + + + + + + , + + + As with all LLM interactions, the results may not be 100% accurate. If you have questions about my career, + I'd love to hear from you. You can send me an email at **james_backstory@ketrenos.com**. + + + ]; + + return [ + + + , + , + + + , + + + + + , + + {sessionId !== undefined && + + } + + ]; + }, [about, connectionBase, sessionId, setSnack, isMobile, scrollRef]); + + + /* toolbar height is 64px + 8px margin-top */ + const Offset = styled('div')(() => ({ minHeight: '72px', height: '72px' })); return ( - + { setTab(0); setMenuOpen(false); }} + onClick={() => { setActiveTab(0); setMenuOpen(false); }} > - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - { settingsPanel } - - - - + { + tabs.map((tab: any, i: number) => + {tab} + ) + } => { + const containerRef = useRef(null); + const [isUserScrollingUp, setIsUserScrollingUp] = useState(false); + const lastScrollTop = useRef(0); + const scrollTimeout = useRef(null); + + useEffect(() => { + const container = containerRef.current; + if (!container) { + // console.log("No ref"); + return; + } + const lastScrollHeight = container.scrollHeight; + + // Function to check if we should scroll to bottom + const checkAndScrollToBottom = (priorScrollHeight?: number | undefined): void => { + if (!container) return; + + const scrollHeight = (priorScrollHeight !== undefined) ? priorScrollHeight : container.scrollHeight; + + // Only auto-scroll if the user is near the bottom and not actively scrolling up + const isNearBottom: boolean = + scrollHeight - container.scrollTop - container.clientHeight <= container.clientHeight * threshold; + + if (isNearBottom && !isUserScrollingUp) { + console.log('Scrolling', { + isNearBottom, + isUserScrollingUp, + scrollHeightToUser: scrollHeight, + scrollHeight: container.scrollHeight, + scrollTop: container.scrollTop, + clientHeight: container.clientHeight, + threshold, + delta: container.scrollHeight - container.scrollTop - container.clientHeight + }); + container.scrollTo({ + top: container.scrollHeight, + behavior: smooth ? 'smooth' : 'auto' + }); + } else { + console.log('Not scrolling', { + isNearBottom, + isUserScrollingUp, + scrollHeight: container.scrollHeight, + scrollTop: container.scrollTop, + clientHeight: container.clientHeight, + threshold, + delta: container.scrollHeight - container.scrollTop - container.clientHeight + }); + } + }; + + // Set up ResizeObserver to detect content size changes + const resizeObserver = new ResizeObserver(() => { + const container = containerRef.current; + if (!container) { + return; + } + + checkAndScrollToBottom(lastScrollHeight); + }); + + // Observe the container and its children + resizeObserver.observe(container); + Array.from(container.children).forEach((child) => { + resizeObserver.observe(child); + }); + + // Track user scrolling behavior + const handleScroll = (): void => { + if (!container) { + // console.log("No ref in handleScroll"); + return; + } + + // Clear existing timeout + if (scrollTimeout.current) { + clearTimeout(scrollTimeout.current); + } + + // Determine scroll direction + const currentScrollTop = container.scrollTop; + setIsUserScrollingUp(currentScrollTop < lastScrollTop.current); + lastScrollTop.current = currentScrollTop; + + // Reset the scrolling flag after user stops scrolling + scrollTimeout.current = setTimeout(() => { + setIsUserScrollingUp(false); + }, 500); + }; + + // Add scroll event listener + container.addEventListener('scroll', handleScroll); + + // Run initial check + checkAndScrollToBottom(); + + // Cleanup + return () => { + if (resizeObserver) { + resizeObserver.disconnect(); + } + if (container) { + container.removeEventListener('scroll', handleScroll); + } + if (scrollTimeout.current) { + clearTimeout(scrollTimeout.current); + } + }; + }, [isUserScrollingUp, smooth, threshold]); // Re-run when dependencies change or scrolling state changes + + return containerRef as RefObject; +}; + +export { + useAutoScrollToBottom +}; + diff --git a/frontend/src/ChatBubble.tsx b/frontend/src/ChatBubble.tsx index 16c5872..6d0e98b 100644 --- a/frontend/src/ChatBubble.tsx +++ b/frontend/src/ChatBubble.tsx @@ -79,7 +79,7 @@ function ChatBubble({ role, isFullWidth, children, sx, className, title }: ChatB ...defaultStyle, backgroundColor: 'rgba(74, 122, 125, 0.15)', // Translucent dusty teal border: `1px solid ${theme.palette.secondary.light}`, // Lighter dusty teal - borderRadius: defaultRadius, + borderRadius: '4px', maxWidth: isFullWidth ? '100%' : '75%', alignSelf: 'center', color: theme.palette.secondary.dark, // Darker dusty teal for text diff --git a/frontend/src/Conversation.css b/frontend/src/Conversation.css new file mode 100644 index 0000000..9bca1ef --- /dev/null +++ b/frontend/src/Conversation.css @@ -0,0 +1,13 @@ + +.Conversation { + display: flex; + background-color: #F5F5F5; + border: 1px solid #E0E0E0; + flex-grow: 1; + padding: 10px; + flex-direction: column; + font-size: 0.9rem; + width: 100%; + margin: 0 auto; + overflow-y: auto; +} diff --git a/frontend/src/Conversation.tsx b/frontend/src/Conversation.tsx index 5c10f65..36f3772 100644 --- a/frontend/src/Conversation.tsx +++ b/frontend/src/Conversation.tsx @@ -13,6 +13,9 @@ import PropagateLoader from "react-spinners/PropagateLoader"; import { Message, MessageList, MessageData } from './Message'; import { SetSnackType } from './Snack'; import { ContextStatus } from './ContextStatus'; +import { useAutoScrollToBottom } from './AutoScroll'; + +import './Conversation.css'; const loadingMessage: MessageData = { "role": "status", "content": "Establishing connection with server..." }; @@ -79,6 +82,7 @@ const Conversation = forwardRef(({ const [contextWarningShown, setContextWarningShown] = useState(false); const [noInteractions, setNoInteractions] = useState(true); const conversationRef = useRef([]); + const scrollRef = useAutoScrollToBottom(); // Keep the ref updated whenever items changes useEffect(() => { @@ -182,27 +186,6 @@ const Conversation = forwardRef(({ fetchHistory(); }, [setConversation, setFilteredConversation, updateContextStatus, connectionBase, setSnack, type, sessionId]); - const isScrolledToBottom = useCallback(()=> { - // Current vertical scroll position - const scrollTop = window.scrollY || document.documentElement.scrollTop; - - // Total height of the page content - const scrollHeight = document.documentElement.scrollHeight; - - // Height of the visible window - const clientHeight = document.documentElement.clientHeight; - - // If we're at the bottom (allowing a small buffer of 16px) - return scrollTop + clientHeight >= scrollHeight - 16; - }, []); - - const scrollToBottom = useCallback(() => { - console.log("Scroll to bottom"); - window.scrollTo({ - top: document.body.scrollHeight, - }); - }, []); - const startCountdown = (seconds: number) => { if (timerRef.current) clearInterval(timerRef.current); setCountdown(seconds); @@ -211,11 +194,6 @@ const Conversation = forwardRef(({ if (prev <= 1) { clearInterval(timerRef.current); timerRef.current = null; - if (isScrolledToBottom()) { - setTimeout(() => { - scrollToBottom(); - }, 50) - } return 0; } return prev - 1; @@ -317,14 +295,10 @@ const Conversation = forwardRef(({ await new Promise(resolve => setTimeout(resolve, 0)); console.log(conversation); - let scrolledToBottom; - - scrollToBottom(); // Clear input setQuery(''); try { - scrolledToBottom = isScrolledToBottom(); setProcessing(true); // Create a unique ID for the processing message const processingId = Date.now().toString(); @@ -337,10 +311,6 @@ const Conversation = forwardRef(({ // Add a small delay to ensure React has time to update the UI await new Promise(resolve => setTimeout(resolve, 0)); - if (scrolledToBottom) { - setTimeout(() => { scrollToBottom() }, 50); - } - // Make the fetch request with proper headers const response = await fetch(connectionBase + `/api/chat/${sessionId}/${type}`, { method: 'POST', @@ -355,12 +325,8 @@ const Conversation = forwardRef(({ const token_guess = 500; const estimate = Math.round(token_guess / lastEvalTPS + contextStatus.context_used / lastPromptTPS); - scrolledToBottom = isScrolledToBottom(); setSnack(`Query sent. Response estimated in ${estimate}s.`, "info"); startCountdown(Math.round(estimate)); - if (scrolledToBottom) { - setTimeout(() => { scrollToBottom() }, 50); - } if (!response.ok) { throw new Error(`Server responded with ${response.status}: ${response.statusText}`); @@ -395,17 +361,12 @@ const Conversation = forwardRef(({ // Force an immediate state update based on the message type if (update.status === 'processing') { - scrolledToBottom = isScrolledToBottom(); // Update processing message with immediate re-render setProcessingMessage({ role: 'status', content: update.message }); // Add a small delay to ensure React has time to update the UI await new Promise(resolve => setTimeout(resolve, 0)); - if (scrolledToBottom) { - setTimeout(() => { scrollToBottom() }, 50); - } } else if (update.status === 'done') { // Replace processing message with final result - scrolledToBottom = isScrolledToBottom(); if (onResponse) { update.message = onResponse(update.message); } @@ -425,12 +386,8 @@ const Conversation = forwardRef(({ setLastPromptTPS(promptTPS ? promptTPS : 35); updateContextStatus(); } - if (scrolledToBottom) { - setTimeout(() => { scrollToBottom() }, 50); - } } else if (update.status === 'error') { // Show error - scrolledToBottom = isScrolledToBottom(); setProcessingMessage({ role: 'error', content: update.message }); setTimeout(() => { setProcessingMessage(undefined); @@ -438,9 +395,6 @@ const Conversation = forwardRef(({ // Add a small delay to ensure React has time to update the UI await new Promise(resolve => setTimeout(resolve, 0)); - if (scrolledToBottom) { - setTimeout(() => { scrollToBottom() }, 50); - } } } catch (e) { setSnack("Error processing query", "error") @@ -455,7 +409,6 @@ const Conversation = forwardRef(({ const update = JSON.parse(buffer); if (update.status === 'done') { - scrolledToBottom = isScrolledToBottom(); if (onResponse) { update.message = onResponse(update.message); } @@ -464,25 +417,17 @@ const Conversation = forwardRef(({ ...conversationRef.current, update.message ]); - if (scrolledToBottom) { - setTimeout(() => { scrollToBottom() }, 500); - } } } catch (e) { setSnack("Error processing query", "error") } } - scrolledToBottom = isScrolledToBottom(); stopCountdown(); setProcessing(false); - if (scrolledToBottom) { - setTimeout(() => { scrollToBottom() }, 50); - } } catch (error) { console.error('Fetch error:', error); setSnack("Unable to process query", "error"); - scrolledToBottom = isScrolledToBottom(); setProcessingMessage({ role: 'error', content: "Unable to process query" }); setTimeout(() => { setProcessingMessage(undefined); @@ -490,19 +435,18 @@ const Conversation = forwardRef(({ setProcessing(false); stopCountdown(); - if (scrolledToBottom) { - setTimeout(() => { scrollToBottom() }, 50); - } // Add a small delay to ensure React has time to update the UI await new Promise(resolve => setTimeout(resolve, 0)); } }; return ( - + { filteredConversation.map((message, index) => diff --git a/frontend/src/Message.tsx b/frontend/src/Message.tsx index 92cd8c8..8c92c95 100644 --- a/frontend/src/Message.tsx +++ b/frontend/src/Message.tsx @@ -248,7 +248,7 @@ const Message = ({ message, submitQuery, isFullWidth, sessionId, setSnack, conne m: 0, mb: 1, mt: 1, - overflowX: "auto" + // overflowX: "auto" }}> diff --git a/frontend/src/ResumeBuilder.tsx b/frontend/src/ResumeBuilder.tsx index 193bc5a..9cbf5dd 100644 --- a/frontend/src/ResumeBuilder.tsx +++ b/frontend/src/ResumeBuilder.tsx @@ -18,38 +18,31 @@ import { } from '@mui/icons-material'; import { SxProps, Theme } from '@mui/material'; -import { SeverityType } from './Snack'; - import { ChatQuery } from './Message'; import { MessageList, MessageData } from './Message'; import { SetSnackType } from './Snack'; import { Conversation } from './Conversation'; -/** - * Props for the DocumentViewer component - * @interface DocumentViewerProps - * @property {SxProps} [sx] - Optional styling properties - * @property {string} [connectionBase] - Base URL for fetch calls - * @property {string} [sessionId] - Session ID - * @property {SetSnackType} - setSnack UI callback - */ -export interface DocumentViewerProps { +interface ResumeBuilderProps { + connectionBase: string, + sessionId: string | undefined, + setSnack: SetSnackType, sx?: SxProps; - connectionBase: string; - sessionId: string; - setSnack: SetSnackType; -} +}; + + /** - * DocumentViewer component + * ResumeBuilder 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 = ({ +const ResumeBuilder: React.FC = ({ sx, connectionBase, sessionId, setSnack + }) => { // State for editing job description const [hasJobDescription, setHasJobDescription] = useState(false); @@ -339,26 +332,43 @@ const DocumentViewer: React.FC = ({ /> }, [connectionBase, sessionId, setSnack, factsResponse, filterFactsMessages]); + /** - * Gets the appropriate content based on active state for Desktop - */ + * Gets the appropriate content based on active state for Desktop + */ const getActiveDesktopContent = useCallback(() => { - /* Left panel - Job Description */ - const showResume = hasResume - const showFactCheck = hasFacts + const hasSlider = hasResume || hasFacts; const ratio = 75 + 25 * splitRatio / 100; - const otherRatio = showResume ? ratio / (hasFacts ? 3 : 2) : 100; + const otherRatio = hasResume ? ratio / (hasFacts ? 3 : 2) : 100; const resumeRatio = 100 - otherRatio * (hasFacts ? 2 : 1); const children = []; children.push( - + {renderJobDescriptionView(false)} ); /* Resume panel - conditionally rendered if resume defined, or processing is in progress */ - if (showResume) { + if (hasResume) { children.push( - + {renderResumeView(false)} @@ -366,9 +376,19 @@ const DocumentViewer: React.FC = ({ } /* Fact Check panel - conditionally rendered if facts defined, or processing is in progress */ - if (showFactCheck) { + if (hasFacts) { children.push( - + {renderFactCheckView(false)} @@ -377,7 +397,7 @@ const DocumentViewer: React.FC = ({ /* Split control panel - conditionally rendered if either facts or resume is set */ let slider = ; - if (showResume || showFactCheck) { + if (hasSlider) { slider = ( @@ -406,14 +426,33 @@ const DocumentViewer: React.FC = ({ } return ( - - + + {children} {slider} ) - }, [renderFactCheckView, renderJobDescriptionView, renderResumeView, splitRatio, sx, hasFacts, hasResume]); + }, [renderFactCheckView, renderJobDescriptionView, renderResumeView, splitRatio, hasFacts, hasResume]); // Render mobile view if (isMobile) { @@ -434,7 +473,18 @@ const DocumentViewer: React.FC = ({ }; return ( - + {/* Tabs */} = ({ ); } - return ( - - {getActiveDesktopContent()} - - ); + return getActiveDesktopContent(); }; -interface ResumeBuilderProps { - connectionBase: string, - sessionId: string | undefined, - setSnack: (message: string, severity?: SeverityType) => void, -}; - -const ResumeBuilder = ({ connectionBase, sessionId, setSnack }: ResumeBuilderProps) => { - if (sessionId === undefined) { - return (<>); - } - - return ( - - - - - - ); -} - export type { ResumeBuilderProps }; diff --git a/frontend/src/index.css b/frontend/src/index.css index 267cf37..c629883 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -7,6 +7,7 @@ body { -moz-osx-font-smoothing: grayscale; padding: 0; height: 100dvh; + overflow: hidden; } code { diff --git a/src/server.py b/src/server.py index 9ea52b7..a992a6d 100644 --- a/src/server.py +++ b/src/server.py @@ -161,6 +161,8 @@ When answering queries, follow these steps: 4. When both [INFO] and tool outputs are relevant, synthesize information from both sources to provide the most complete answer 5. Always prioritize the most up-to-date and relevant information, whether it comes from [INFO] or tools 6. If [INFO] and tool outputs contain conflicting information, prefer the tool outputs as they likely represent more current data +7. If there is information in the [INFO], [JOB DESCRIPTION], or [WORK HISTORY] sections to enhance the answer, incorporate it seamlessly and refer to it as 'the latest information' or 'recent data' instead of mentioning '[INFO]' (etc.) or quoting it directly. +8. Avoid phrases like 'According to the [INFO]' or similar references to the [INFO], [JOB DESCRIPTION], or [WORK HISTORY] tags. Always use tools and [INFO] when possible. Be concise, and never make up information. If you do not know the answer, say so. """.strip() @@ -182,6 +184,8 @@ When answering queries, follow these steps: 8. Use the [INTRO] to highlight the use of AI in generating this resume. 9. Use the [WORK HISTORY] to create a polished, professional resume. 10. Do not list any locations or mailing addresses in the resume. +11. If there is information in the [INFO], [JOB DESCRIPTION], [WORK HISTORY], or [RESUME] sections to enhance the answer, incorporate it seamlessly and refer to it using natural language instead of mentioning '[JOB DESCRIPTION]' (etc.) or quoting it directly. +12. Avoid phrases like 'According to the [INFO]' or similar references to the [INFO], [JOB DESCRIPTION], or [WORK HISTORY] tags. Structure the resume professionally with the following sections where applicable: @@ -205,6 +209,8 @@ If there are inaccuracies, list them in a bullet point format. When answering queries, follow these steps: 1. You must not invent or assume any information not explicitly present in the [WORK HISTORY]. 2. Analyze the [RESUME] to identify any discrepancies or inaccuracies based on the [WORK HISTORY]. +3. If there is information in the [INFO], [JOB DESCRIPTION], [WORK HISTORY], or [RESUME] sections to enhance the answer, incorporate it seamlessly and refer to it using natural language instead of mentioning '[JOB DESCRIPTION]' (etc.) or quoting it directly. +4. Avoid phrases like 'According to the [INFO]' or similar references to the [INFO], [JOB DESCRIPTION], [RESUME], or [WORK HISTORY] tags. """.strip() system_job_description = f""" @@ -215,6 +221,8 @@ You are a hiring and job placing specialist. Your task is to answers about a job When answering queries, follow these steps: 1. Analyze the [JOB DESCRIPTION] to provide insights for the asked question. 2. If any financial information is requested, be sure to account for inflation. +3. If there is information in the [INFO], [JOB DESCRIPTION], [WORK HISTORY], or [RESUME] sections to enhance the answer, incorporate it seamlessly and refer to it using natural language instead of mentioning '[JOB DESCRIPTION]' (etc.) or quoting it directly. +4. Avoid phrases like 'According to the [INFO]' or similar references to the [INFO], [JOB DESCRIPTION], [RESUME], or [WORK HISTORY] tags. """.strip() def create_system_message(prompt): @@ -1057,14 +1065,15 @@ class WebServer: if rag_context: preamble = f""" 1. Respond to this query: {content} -2. If there is information in the [INFO] section to enhance the answer, incorporate it seamlessly and refer to it as 'the latest information' or 'recent data' instead of mentioning '[INFO]' or quoting it directly. -3. Avoid phrases like 'According to the [INFO]' or similar references to the [INFO] tag. +2. If there is information in the [INFO], [JOB DESCRIPTION], [WORK HISTORY], or [RESUME] sections to enhance the answer, incorporate it seamlessly and refer to it using natural language instead of mentioning '[JOB DESCRIPTION]' (etc.) or quoting it directly. +3. Avoid phrases like 'According to the [INFO]' or similar references to the [INFO], [JOB DESCRIPTION], or [WORK HISTORY] tags. [INFO] {rag_context} [/INFO] Use that information to respond to:""" - system_prompt = context["sessions"]["chat"]["system_prompt"] + # Use the mode specific system_prompt instead of 'chat' + system_prompt = context["sessions"][type]["system_prompt"] # On first entry, a single job_description is provided ("user") # Generate a resume to append to RESUME history