Scroll to bottom working

This commit is contained in:
James Ketr 2025-04-02 17:35:10 -07:00
parent 847de136cf
commit 5dca368a87
2 changed files with 75 additions and 26 deletions

View File

@ -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;

View File

@ -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}