Restyling in progress
This commit is contained in:
parent
0173657477
commit
52ec9fdcf0
@ -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;
|
||||
|
@ -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<string | undefined>(undefined);
|
||||
const [connectionBase,] = useState<string>(getConnectionBase(window.location))
|
||||
const [selectedPath, setSelectedPath] = useState<string>("");
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const [isMenuClosing, setIsMenuClosing] = useState(false);
|
||||
const [tab, setTab] = useState<number>(0);
|
||||
const [activeTab, setActiveTab] = useState<number>(0);
|
||||
const [about, setAbout] = useState<string>("");
|
||||
const isDesktop = useMediaQuery('(min-width:650px)');
|
||||
const prevIsDesktopRef = useRef<boolean>(isDesktop);
|
||||
@ -79,6 +81,7 @@ const App = () => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||
const snackRef = useRef<any>(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 = [
|
||||
<Box sx={{ display: "flex", flexDirection: isMobile ? "column" : "row" }}>
|
||||
<ChatQuery text="What is James Ketrenos' work history?" submitQuery={handleSubmitChatQuery} />
|
||||
<ChatQuery text="What programming languages has James used?" submitQuery={handleSubmitChatQuery} />
|
||||
<ChatQuery text="What are James' professional strengths?" submitQuery={handleSubmitChatQuery} />
|
||||
<ChatQuery text="What are today's headlines on CNBC.com?" submitQuery={handleSubmitChatQuery} />
|
||||
</Box>,
|
||||
<Box sx={{ p: 1 }}>
|
||||
<MuiMarkdown>
|
||||
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**.
|
||||
</MuiMarkdown>
|
||||
</Box>
|
||||
];
|
||||
|
||||
|
||||
// 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 &&
|
||||
<Controls {...{ sessionId, setSnack, connectionBase }} />}
|
||||
</>
|
||||
);
|
||||
|
||||
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 = (
|
||||
<Card className="MenuCard">
|
||||
<Tabs sx={{ display: "flex", flexGrow: 1 }}
|
||||
orientation="vertical"
|
||||
value={tab}
|
||||
value={activeTab}
|
||||
indicatorColor="secondary"
|
||||
textColor="inherit"
|
||||
variant="scrollable"
|
||||
@ -265,11 +270,105 @@ What would you like to know about James?
|
||||
</Card>
|
||||
);
|
||||
|
||||
/* 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 = [
|
||||
<Box sx={{ display: "flex", flexDirection: isMobile ? "column" : "row" }}>
|
||||
<ChatQuery text="What is James Ketrenos' work history?" submitQuery={handleSubmitChatQuery} />
|
||||
<ChatQuery text="What programming languages has James used?" submitQuery={handleSubmitChatQuery} />
|
||||
<ChatQuery text="What are James' professional strengths?" submitQuery={handleSubmitChatQuery} />
|
||||
<ChatQuery text="What are today's headlines on CNBC.com?" submitQuery={handleSubmitChatQuery} />
|
||||
</Box>,
|
||||
<Box sx={{ p: 1 }}>
|
||||
<MuiMarkdown>
|
||||
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**.
|
||||
</MuiMarkdown>
|
||||
</Box>
|
||||
];
|
||||
|
||||
return [
|
||||
<Box
|
||||
sx={{
|
||||
maxWidth: "1024px",
|
||||
margin: "0 auto",
|
||||
flexGrow: 1,
|
||||
display: "flex",
|
||||
height: "calc(100vh - 72px)",
|
||||
overflow: "auto",
|
||||
backgroundColor: "#F5F5F5",
|
||||
}}
|
||||
ref={scrollRef}
|
||||
>
|
||||
<Conversation
|
||||
ref={chatRef}
|
||||
{...{
|
||||
type: "chat",
|
||||
prompt: "What would you like to know about James?",
|
||||
sessionId,
|
||||
connectionBase,
|
||||
setSnack,
|
||||
preamble: chatPreamble,
|
||||
defaultPrompts: chatQuestions
|
||||
}}
|
||||
/>
|
||||
</Box>,
|
||||
<ResumeBuilder sx={{
|
||||
margin: "0 auto",
|
||||
height: "calc(100vh - 72px)",
|
||||
overflow: "auto",
|
||||
backgroundColor: "#F5F5F5",
|
||||
display: "flex",
|
||||
flexGrow: 1
|
||||
}} {...{ setSnack, connectionBase, sessionId }} />,
|
||||
<Box
|
||||
sx={{
|
||||
maxWidth: "1024px",
|
||||
margin: "0 auto",
|
||||
flexGrow: 1,
|
||||
display: "flex",
|
||||
height: "calc(100vh - 72px)",
|
||||
overflow: "auto",
|
||||
backgroundColor: "#F5F5F5",
|
||||
}}
|
||||
ref={scrollRef}
|
||||
>
|
||||
<VectorVisualizer {...{ connectionBase, sessionId, setSnack }} />
|
||||
</Box>,
|
||||
<Box className="ChatBox">
|
||||
<Box className="Conversation">
|
||||
<Message {...{ message: { role: 'content', content: about }, submitQuery: handleSubmitChatQuery, connectionBase, sessionId, setSnack }} />
|
||||
</Box>
|
||||
</Box>,
|
||||
<Box className="ChatBox">
|
||||
{sessionId !== undefined &&
|
||||
<Controls {...{ sessionId, setSnack, connectionBase }} />
|
||||
}
|
||||
</Box>
|
||||
];
|
||||
}, [about, connectionBase, sessionId, setSnack, isMobile, scrollRef]);
|
||||
|
||||
|
||||
/* toolbar height is 64px + 8px margin-top */
|
||||
const Offset = styled('div')(() => ({ minHeight: '72px', height: '72px' }));
|
||||
|
||||
return (
|
||||
<Box className="App" sx={{ display: 'flex', flexDirection: 'column', height: '100dvh' }}>
|
||||
<Box className="App"
|
||||
sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<CssBaseline />
|
||||
<AppBar
|
||||
position="fixed"
|
||||
@ -296,7 +395,7 @@ What would you like to know about James?
|
||||
<Tooltip title="Backstory">
|
||||
<Box
|
||||
sx={{ m: 1, gap: 1, display: "flex", flexDirection: "row", alignItems: "center", fontWeight: "bold", fontSize: "1.0rem", cursor: "pointer" }}
|
||||
onClick={() => { setTab(0); setMenuOpen(false); }}
|
||||
onClick={() => { setActiveTab(0); setMenuOpen(false); }}
|
||||
>
|
||||
<Avatar sx={{
|
||||
width: 24,
|
||||
@ -313,7 +412,7 @@ What would you like to know about James?
|
||||
|
||||
{menuOpen === false && isDesktop &&
|
||||
<Tabs sx={{ display: "flex", flexGrow: 1 }}
|
||||
value={tab}
|
||||
value={activeTab}
|
||||
indicatorColor="secondary"
|
||||
textColor="inherit"
|
||||
variant="fullWidth"
|
||||
@ -357,7 +456,9 @@ What would you like to know about James?
|
||||
|
||||
<Offset />
|
||||
|
||||
<Box sx={{ display: "flex", flexGrow: 1, flexDirection: "column" }}>
|
||||
<Box
|
||||
sx={{ display: "flex", flexGrow: 1, flexDirection: "column" }}
|
||||
>
|
||||
<Box
|
||||
component="nav"
|
||||
aria-label="mailbox folders"
|
||||
@ -382,52 +483,11 @@ What would you like to know about James?
|
||||
{menuDrawer}
|
||||
</Drawer>
|
||||
</Box>
|
||||
|
||||
<CustomTabPanel tab={tab} index={0}>
|
||||
<Box component="main" sx={{ flexGrow: 1, overflow: 'auto' }} className="ChatBox">
|
||||
<Conversation
|
||||
ref={chatRef}
|
||||
{...{
|
||||
type: "chat",
|
||||
prompt: "What would you like to know about James?",
|
||||
sessionId,
|
||||
connectionBase,
|
||||
setSnack,
|
||||
preamble: chatPreamble,
|
||||
defaultPrompts: chatQuestions
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</CustomTabPanel>
|
||||
|
||||
<CustomTabPanel tab={tab} index={1}>
|
||||
<ResumeBuilder {...{ setSnack, connectionBase, sessionId }} />
|
||||
</CustomTabPanel>
|
||||
|
||||
<CustomTabPanel tab={tab} index={2}>
|
||||
<Box className="ChatBox">
|
||||
<Box className="Conversation">
|
||||
<VectorVisualizer {...{ connectionBase, sessionId, setSnack }} />
|
||||
</Box>
|
||||
</Box>
|
||||
</CustomTabPanel>
|
||||
|
||||
<CustomTabPanel tab={tab} index={3}>
|
||||
<Box className="ChatBox">
|
||||
<Box className="Conversation">
|
||||
<Message {...{ message: { role: 'assistant', content: about }, submitQuery: handleSubmitChatQuery, connectionBase, sessionId, setSnack }} />
|
||||
</Box>
|
||||
</Box>
|
||||
</CustomTabPanel>
|
||||
|
||||
<CustomTabPanel tab={tab} index={4}>
|
||||
<Box className="ChatBox">
|
||||
<Box className="Conversation">
|
||||
{ settingsPanel }
|
||||
</Box>
|
||||
</Box>
|
||||
</CustomTabPanel>
|
||||
|
||||
{
|
||||
tabs.map((tab: any, i: number) =>
|
||||
<CustomTabPanel key={i} tab={activeTab} index={i}>{tab}</CustomTabPanel>
|
||||
)
|
||||
}
|
||||
</Box>
|
||||
|
||||
<Snack
|
||||
|
131
frontend/src/AutoScroll.tsx
Normal file
131
frontend/src/AutoScroll.tsx
Normal file
@ -0,0 +1,131 @@
|
||||
import { useEffect, useRef, useState, RefObject } from 'react';
|
||||
|
||||
/**
|
||||
* Hook that automatically scrolls a container to the bottom when content changes
|
||||
* or when the container is resized, but only if the user is already near the bottom.
|
||||
*
|
||||
* @param threshold - Distance from bottom (px) to consider "near bottom" (default: 100)
|
||||
* @param smooth - Whether to use smooth scrolling (default: true)
|
||||
* @returns Ref to attach to the scrollable container
|
||||
*/
|
||||
const useAutoScrollToBottom = (
|
||||
threshold: number = 0.33, // Percentage of viewport to trigger threshold
|
||||
smooth: boolean = true
|
||||
): RefObject<HTMLDivElement> => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [isUserScrollingUp, setIsUserScrollingUp] = useState<boolean>(false);
|
||||
const lastScrollTop = useRef<number>(0);
|
||||
const scrollTimeout = useRef<NodeJS.Timeout | null>(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<HTMLDivElement>;
|
||||
};
|
||||
|
||||
export {
|
||||
useAutoScrollToBottom
|
||||
};
|
||||
|
@ -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
|
||||
|
13
frontend/src/Conversation.css
Normal file
13
frontend/src/Conversation.css
Normal file
@ -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;
|
||||
}
|
@ -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<ConversationHandle, ConversationProps>(({
|
||||
const [contextWarningShown, setContextWarningShown] = useState<boolean>(false);
|
||||
const [noInteractions, setNoInteractions] = useState<boolean>(true);
|
||||
const conversationRef = useRef<MessageList>([]);
|
||||
const scrollRef = useAutoScrollToBottom();
|
||||
|
||||
// Keep the ref updated whenever items changes
|
||||
useEffect(() => {
|
||||
@ -182,27 +186,6 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
|
||||
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<ConversationHandle, ConversationProps>(({
|
||||
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<ConversationHandle, ConversationProps>(({
|
||||
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<ConversationHandle, ConversationProps>(({
|
||||
// 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<ConversationHandle, ConversationProps>(({
|
||||
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<ConversationHandle, ConversationProps>(({
|
||||
|
||||
// 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<ConversationHandle, ConversationProps>(({
|
||||
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<ConversationHandle, ConversationProps>(({
|
||||
|
||||
// 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<ConversationHandle, ConversationProps>(({
|
||||
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<ConversationHandle, ConversationProps>(({
|
||||
...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<ConversationHandle, ConversationProps>(({
|
||||
|
||||
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 (
|
||||
<Box className={className || "Conversation"} sx={{
|
||||
display: "flex", flexDirection: "column", flexGrow: 1, p: 1, mt: 1,
|
||||
...sx
|
||||
}}>
|
||||
<Box className={className || "Conversation"}
|
||||
ref={scrollRef}
|
||||
sx={{
|
||||
display: "flex", flexDirection: "column", flexGrow: 1, p: 1, mt: 0,
|
||||
...sx
|
||||
}}>
|
||||
{
|
||||
filteredConversation.map((message, index) =>
|
||||
<Message key={index} {...{ sendQuery, message, connectionBase, sessionId, setSnack }} />
|
||||
|
@ -248,7 +248,7 @@ const Message = ({ message, submitQuery, isFullWidth, sessionId, setSnack, conne
|
||||
m: 0,
|
||||
mb: 1,
|
||||
mt: 1,
|
||||
overflowX: "auto"
|
||||
// overflowX: "auto"
|
||||
}}>
|
||||
<CardContent ref={textFieldRef} sx={{ position: "relative", display: "flex", flexDirection: "column", overflowX: "auto", m: 0, p: 0 }}>
|
||||
<CopyBubble content={message?.content} />
|
||||
|
@ -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<Theme>} [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<Theme>;
|
||||
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<DocumentViewerProps> = ({
|
||||
const ResumeBuilder: React.FC<ResumeBuilderProps> = ({
|
||||
sx,
|
||||
connectionBase,
|
||||
sessionId,
|
||||
setSnack
|
||||
|
||||
}) => {
|
||||
// State for editing job description
|
||||
const [hasJobDescription, setHasJobDescription] = useState<boolean>(false);
|
||||
@ -339,26 +332,43 @@ const DocumentViewer: React.FC<DocumentViewerProps> = ({
|
||||
/>
|
||||
}, [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(
|
||||
<Box key="JobDescription" className="ChatBox" sx={{ display: 'flex', flexDirection: 'column', minWidth: `${otherRatio}%`, width: `${otherRatio}%`, maxWidth: `${otherRatio}%`, p: 0, flexGrow: 1, overflowY: 'auto' }}>
|
||||
<Box key="JobDescription" className="ChatBox" sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
minWidth: `${otherRatio}%`,
|
||||
width: `${otherRatio}%`,
|
||||
maxWidth: `${otherRatio}%`,
|
||||
p: 0,
|
||||
flexGrow: 1,
|
||||
}}>
|
||||
{renderJobDescriptionView(false)}
|
||||
</Box>);
|
||||
|
||||
/* Resume panel - conditionally rendered if resume defined, or processing is in progress */
|
||||
if (showResume) {
|
||||
if (hasResume) {
|
||||
children.push(
|
||||
<Box key="ResumeView" className="ChatBox" sx={{ display: 'flex', flexDirection: 'column', minWidth: `${resumeRatio}%`, width: `${resumeRatio}%`, maxWidth: `${resumeRatio}%`, p: 0, flexGrow: 1, overflowY: 'auto' }}>
|
||||
<Box key="ResumeView"
|
||||
className="ChatBox"
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
minWidth: `${resumeRatio}%`,
|
||||
width: `${resumeRatio}%`,
|
||||
maxWidth: `${resumeRatio}%`,
|
||||
p: 0,
|
||||
flexGrow: 1
|
||||
}}>
|
||||
<Divider orientation="vertical" flexItem />
|
||||
{renderResumeView(false)}
|
||||
</Box>
|
||||
@ -366,9 +376,19 @@ const DocumentViewer: React.FC<DocumentViewerProps> = ({
|
||||
}
|
||||
|
||||
/* Fact Check panel - conditionally rendered if facts defined, or processing is in progress */
|
||||
if (showFactCheck) {
|
||||
if (hasFacts) {
|
||||
children.push(
|
||||
<Box key="FactCheckView" className="ChatBox" sx={{ display: 'flex', flexDirection: 'column', minWidth: `${otherRatio}%`, width: `${otherRatio}%`, maxWidth: `${otherRatio}%`, p: 0, flexGrow: 1, overflowY: 'auto' }}>
|
||||
<Box key="FactCheckView"
|
||||
className="ChatBox"
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
minWidth: `${otherRatio}%`,
|
||||
width: `${otherRatio}%`,
|
||||
maxWidth: `${otherRatio}%`,
|
||||
p: 0,
|
||||
flexGrow: 1,
|
||||
}}>
|
||||
<Divider orientation="vertical" flexItem />
|
||||
{renderFactCheckView(false)}
|
||||
</Box>
|
||||
@ -377,7 +397,7 @@ const DocumentViewer: React.FC<DocumentViewerProps> = ({
|
||||
|
||||
/* Split control panel - conditionally rendered if either facts or resume is set */
|
||||
let slider = <Box key="slider"></Box>;
|
||||
if (showResume || showFactCheck) {
|
||||
if (hasSlider) {
|
||||
slider = (
|
||||
<Paper key="slider" sx={{ p: 2, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Stack direction="row" spacing={2} alignItems="center" sx={{ width: '60%' }}>
|
||||
@ -406,14 +426,33 @@ const DocumentViewer: React.FC<DocumentViewerProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ ...sx, display: 'flex', flexGrow: 1, flexDirection: 'column', p: 0 }}>
|
||||
<Box sx={{ display: 'flex', flexGrow: 1, flexDirection: 'row', overflow: 'hidden', p: 0 }}>
|
||||
<Box sx={{
|
||||
p: 0,
|
||||
m: 0,
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
flexDirection: "column",
|
||||
}}>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
flexDirection: 'row',
|
||||
overflow: 'hidden',
|
||||
p: 0,
|
||||
m: 0,
|
||||
margin: "0 auto",
|
||||
maxWidth: hasSlider ? "100%" : "1024px",
|
||||
width: hasSlider ? "100%" : "1024px",
|
||||
height: `calc(100vh - ${hasSlider ? 144 : 72}px)`,
|
||||
backgroundColor: "#F5F5F5",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
{slider}
|
||||
</Box>
|
||||
)
|
||||
}, [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<DocumentViewerProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', flexGrow: 1, ...sx }}>
|
||||
<Box sx={{
|
||||
p: 0,
|
||||
m: 0,
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
margin: "0 auto",
|
||||
overflow: "hidden",
|
||||
height: "calc(100vh - 72px)",
|
||||
backgroundColor: "#F5F5F5",
|
||||
flexDirection: "column"
|
||||
}}
|
||||
>
|
||||
{/* Tabs */}
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
@ -455,41 +505,9 @@ const DocumentViewer: React.FC<DocumentViewerProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', flexGrow: 1, width: "100%", ...sx }}>
|
||||
{getActiveDesktopContent()}
|
||||
</Box>
|
||||
);
|
||||
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 (
|
||||
<Box className="DocBox">
|
||||
<Box className="Conversation" sx={{ p: 0, pt: 1 }}>
|
||||
<DocumentViewer sx={{
|
||||
p: 0,
|
||||
m: 0,
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
overflowY: "auto",
|
||||
flexDirection: "column",
|
||||
height: "calc(0vh - 0px)", // Hack to make the height work
|
||||
}} {...{ setSnack, connectionBase, sessionId }} />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export type {
|
||||
ResumeBuilderProps
|
||||
};
|
||||
|
@ -7,6 +7,7 @@ body {
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
padding: 0;
|
||||
height: 100dvh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
code {
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user