Scroll to bottom working
This commit is contained in:
parent
847de136cf
commit
5dca368a87
@ -2,14 +2,6 @@ div {
|
||||
box-sizing: border-box
|
||||
}
|
||||
|
||||
.App {
|
||||
display: flex;
|
||||
text-align: center;
|
||||
max-height: 100%;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.SystemInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -18,6 +18,7 @@ import AppBar from '@mui/material/AppBar';
|
||||
import Drawer from '@mui/material/Drawer';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import SettingsIcon from '@mui/icons-material/Settings';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import IconButton, { IconButtonProps } from '@mui/material/IconButton';
|
||||
import Box from '@mui/material/Box';
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
@ -53,13 +54,10 @@ import '@fontsource/roboto/700.css';
|
||||
//const use_mui_markdown = true
|
||||
const use_mui_markdown = true
|
||||
|
||||
|
||||
const welcomeMarkdown = `
|
||||
# Welcome to Ketr-Chat
|
||||
|
||||
This LLM agent was built by James Ketrenos in order to provide answers to any questions you may have about his work history.
|
||||
|
||||
In addition to being a RAG enabled expert system, the LLM is configured with real-time access to weather, stocks, the current time, and can answer questions about the contents of a website.
|
||||
This LLM agent was built by James Ketrenos in order to provide answers to any questions you may have about his work history. In addition to being a RAG enabled expert system, the LLM is configured with real-time access to weather, stocks, the current time, and can answer questions about the contents of a website.
|
||||
|
||||
You can ask things like:
|
||||
* <ChatQuery text="What is James Ketrenos' work history?"/>
|
||||
@ -559,13 +557,26 @@ const App = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Scroll to bottom of conversation when conversation updates
|
||||
useEffect(() => {
|
||||
const queryElement = document.getElementById('QueryInput');
|
||||
if (queryElement) {
|
||||
queryElement.scrollIntoView();
|
||||
}
|
||||
}, [conversation]);
|
||||
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 5px)
|
||||
return scrollTop + clientHeight >= scrollHeight - 5;
|
||||
}, []);
|
||||
|
||||
const scrollToBottom = useCallback(() => {
|
||||
console.log("Scroll to bottom");
|
||||
window.scrollTo({
|
||||
top: document.body.scrollHeight,
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Set the snack pop-up and open it
|
||||
const setSnack = useCallback((message: string, severity: SeverityType = "success") => {
|
||||
@ -960,22 +971,25 @@ const App = () => {
|
||||
|
||||
const userMessage = [{ role: 'user', content: query }];
|
||||
|
||||
let scrolledToBottom = isScrolledToBottom();
|
||||
|
||||
// Add user message to conversation
|
||||
const newConversation: MessageList = [
|
||||
...conversation,
|
||||
...userMessage
|
||||
];
|
||||
setConversation(newConversation);
|
||||
scrollToBottom();
|
||||
|
||||
// Clear input
|
||||
setQuery('');
|
||||
setTimeout(() => {
|
||||
document.getElementById("QueryIput")?.focus();
|
||||
}, 1000);
|
||||
// setTimeout(() => {
|
||||
// document.getElementById("QueryIput")?.focus();
|
||||
// }, 1000);
|
||||
|
||||
try {
|
||||
scrolledToBottom = isScrolledToBottom();
|
||||
setProcessing(true);
|
||||
|
||||
// Create a unique ID for the processing message
|
||||
const processingId = Date.now().toString();
|
||||
|
||||
@ -984,6 +998,9 @@ const App = () => {
|
||||
...prev,
|
||||
{ role: 'assistant', content: 'Processing request...', id: processingId, isProcessing: true }
|
||||
]);
|
||||
if (scrolledToBottom) {
|
||||
setTimeout(() => { scrollToBottom() }, 0);
|
||||
}
|
||||
|
||||
// Make the fetch request with proper headers
|
||||
const response = await fetch(getConnectionBase(loc) + `/api/chat/${sessionId}`, {
|
||||
@ -998,8 +1015,13 @@ const App = () => {
|
||||
// We'll guess that the response will be around 500 tokens...
|
||||
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() }, 0);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Server responded with ${response.status}: ${response.statusText}`);
|
||||
@ -1034,18 +1056,23 @@ const App = () => {
|
||||
|
||||
// Force an immediate state update based on the message type
|
||||
if (update.status === 'processing') {
|
||||
scrolledToBottom = isScrolledToBottom();
|
||||
// Update processing message with immediate re-render
|
||||
setConversation(prev => prev.map(msg =>
|
||||
msg.id === processingId
|
||||
? { ...msg, content: update.message }
|
||||
: msg
|
||||
));
|
||||
if (scrolledToBottom) {
|
||||
setTimeout(() => { scrollToBottom() }, 0);
|
||||
}
|
||||
|
||||
// Add a small delay to ensure React has time to update the UI
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
|
||||
} else if (update.status === 'done') {
|
||||
// Replace processing message with final result
|
||||
scrolledToBottom = isScrolledToBottom();
|
||||
setConversation(prev => [
|
||||
...prev.filter(msg => msg.id !== processingId),
|
||||
update.message
|
||||
@ -1056,12 +1083,19 @@ const App = () => {
|
||||
setLastEvalTPS(evalTPS ? evalTPS : 35);
|
||||
setLastPromptTPS(promptTPS ? promptTPS : 35);
|
||||
updateContextStatus();
|
||||
if (scrolledToBottom) {
|
||||
setTimeout(() => { scrollToBottom() }, 0);
|
||||
}
|
||||
} else if (update.status === 'error') {
|
||||
// Show error
|
||||
scrolledToBottom = isScrolledToBottom();
|
||||
setConversation(prev => [
|
||||
...prev.filter(msg => msg.id !== processingId),
|
||||
{ role: 'assistant', type: 'error', content: update.message }
|
||||
]);
|
||||
if (scrolledToBottom) {
|
||||
setTimeout(() => { scrollToBottom() }, 0);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
setSnack("Error processing query", "error")
|
||||
@ -1076,10 +1110,14 @@ const App = () => {
|
||||
const update = JSON.parse(buffer);
|
||||
|
||||
if (update.status === 'done') {
|
||||
scrolledToBottom = isScrolledToBottom();
|
||||
setConversation(prev => [
|
||||
...prev.filter(msg => msg.id !== processingId),
|
||||
update.message
|
||||
]);
|
||||
if (scrolledToBottom) {
|
||||
setTimeout(() => { scrollToBottom() }, 0);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
setSnack("Error processing query", "error")
|
||||
@ -1091,12 +1129,16 @@ const App = () => {
|
||||
} catch (error) {
|
||||
console.error('Fetch error:', error);
|
||||
setSnack("Unable to process query", "error");
|
||||
scrolledToBottom = isScrolledToBottom();
|
||||
setConversation(prev => [
|
||||
...prev.filter(msg => !msg.isProcessing),
|
||||
{ role: 'assistant', type: 'error', content: `Error: ${error}` }
|
||||
]);
|
||||
setProcessing(false);
|
||||
stopCountdown();
|
||||
if (scrolledToBottom) {
|
||||
setTimeout(() => { scrollToBottom() }, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -1114,7 +1156,7 @@ const App = () => {
|
||||
const Offset = styled('div')(({ theme }) => theme.mixins.toolbar);
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100dvh' }}>
|
||||
<Box className="App" sx={{ display: 'flex', flexDirection: 'column', height: '100dvh' }}>
|
||||
<CssBaseline />
|
||||
<AppBar
|
||||
position="fixed"
|
||||
@ -1149,7 +1191,23 @@ const App = () => {
|
||||
<Typography variant="h6" noWrap component="div">
|
||||
Ketr-Chat
|
||||
</Typography>
|
||||
|
||||
{
|
||||
(mobileOpen === true || isScrolledToBottom()) &&
|
||||
<Tooltip title="Close Settings">
|
||||
<IconButton
|
||||
color="inherit"
|
||||
aria-label="close drawer"
|
||||
edge="end"
|
||||
onClick={handleDrawerToggle}
|
||||
sx={{ mr: 2, right: 0, position: "absolute" }}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
</Toolbar>
|
||||
|
||||
</AppBar>
|
||||
|
||||
<Offset />
|
||||
@ -1219,7 +1277,6 @@ const App = () => {
|
||||
<TextField
|
||||
variant="outlined"
|
||||
disabled={processing}
|
||||
autoFocus
|
||||
fullWidth
|
||||
type="text"
|
||||
value={query}
|
||||
|
Loading…
x
Reference in New Issue
Block a user