Skip to content

Commit 4e2fe13

Browse files
committed
autoscroll chatv2 to bottom on chat expand
1 parent 14f27c0 commit 4e2fe13

File tree

4 files changed

+62
-27
lines changed

4 files changed

+62
-27
lines changed

src/client/App.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect } from 'react'
1+
import React, { useEffect, useRef } from 'react'
22
import { Outlet, useLocation, useParams } from 'react-router-dom'
33
import { SnackbarProvider } from 'notistack'
44
import { initShibbolethPinger } from 'unfuck-spa-shibboleth-session'
@@ -7,6 +7,7 @@ import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'
77
import { fi } from 'date-fns/locale'
88
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFnsV3'
99
import { Box, Button, Container, CssBaseline, Snackbar } from '@mui/material'
10+
import { AppContext } from './util/context'
1011

1112
import { PUBLIC_URL } from '../config'
1213
import { User } from './types'
@@ -69,6 +70,7 @@ const AdminLoggedInAsBanner = () => {
6970

7071
const App = () => {
7172
const theme = useTheme()
73+
const appRef = useRef<HTMLDivElement>(null)
7274
const { courseId } = useParams()
7375
const location = useLocation()
7476

@@ -94,13 +96,15 @@ const App = () => {
9496
<CssBaseline />
9597
<LocalizationProvider dateAdapter={AdapterDateFns} adapterLocale={fi}>
9698
<SnackbarProvider preventDuplicate>
97-
<Box minHeight="100vh" display="flex" flexDirection="column">
98-
<NavBar />
99-
<Container component="main" sx={{ mt: '4rem', mb: '10rem' }}>
100-
<Outlet />
101-
</Container>
102-
<Footer />
103-
</Box>
99+
<AppContext.Provider value={appRef}>
100+
<Box minHeight="100vh" display="flex" flexDirection="column" ref={appRef}>
101+
<NavBar />
102+
<Container component="main" sx={{ mt: '4rem', mb: '10rem' }}>
103+
<Outlet />
104+
</Container>
105+
<Footer />
106+
</Box>
107+
</AppContext.Provider>
104108
<AdminLoggedInAsBanner />
105109
</SnackbarProvider>
106110
</LocalizationProvider>

src/client/components/ChatV2/ChatV2.tsx

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useParams } from 'react-router-dom'
22
import useCourse from '../../hooks/useCourse'
33
import useUserStatus from '../../hooks/useUserStatus'
4-
import { useRef, useState } from 'react'
4+
import { useRef, useState, useContext, useEffect } from 'react'
55
import useLocalStorageState from '../../hooks/useLocalStorageState'
66
import { DEFAULT_MODEL } from '../../../config'
77
import useInfoTexts from '../../hooks/useInfoTexts'
@@ -15,6 +15,7 @@ import { Conversation } from './Conversation'
1515
import { ChatBox } from './ChatBox'
1616
import { getCompletionStream } from './util'
1717
import { SystemPrompt } from './System'
18+
import { AppContext } from '../../util/context'
1819

1920
export const ChatV2 = () => {
2021
const { courseId } = useParams()
@@ -29,6 +30,10 @@ export const ChatV2 = () => {
2930
const [system, setSystem] = useLocalStorageState<{ content: string }>('general-chat-system', { content: '' })
3031
const [message, setMessage] = useLocalStorageState<{ content: string }>('general-chat-current', { content: '' })
3132
const [messages, setMessages] = useLocalStorageState<Message[]>('general-chat-messages', [])
33+
34+
const appContainerRef = useContext(AppContext)
35+
const chatContainerRef = useRef<HTMLDivElement>(null)
36+
3237
const inputFileRef = useRef<HTMLInputElement>(null)
3338
const [fileName, setFileName] = useState<string>('')
3439
const [completion, setCompletion] = useState('')
@@ -130,6 +135,28 @@ export const ChatV2 = () => {
130135
clearRetryTimeout()
131136
}
132137

138+
useEffect(() => {
139+
const chatContainer = chatContainerRef.current
140+
const appContainer = appContainerRef.current
141+
142+
if (!chatContainer || !appContainer || !messages.length) return
143+
144+
const scrollToBottom = () => {
145+
appContainer.scrollIntoView({
146+
behavior: 'smooth',
147+
block: 'end',
148+
})
149+
}
150+
151+
const resizeObserver = new ResizeObserver(() => {
152+
scrollToBottom()
153+
})
154+
155+
resizeObserver.observe(chatContainer)
156+
157+
return () => resizeObserver.disconnect()
158+
}, [])
159+
133160
return (
134161
<Box
135162
sx={{
@@ -138,26 +165,23 @@ export const ChatV2 = () => {
138165
flexDirection: 'column',
139166
}}
140167
>
141-
<Box
142-
sx={{
143-
display: 'flex',
144-
gap: '1rem',
145-
}}
146-
>
168+
<Box sx={{ display: 'flex', gap: '1rem' }}>
147169
{disclaimerInfo && <Disclaimer disclaimer={disclaimerInfo} />}
148170
<SystemPrompt content={system.content} setContent={(content) => setSystem({ content })} />
149171
<Button onClick={handleReset}>Reset</Button>
150172
</Box>
151-
<Conversation messages={messages} completion={completion} />
152-
<ChatBox
153-
disabled={false}
154-
onSubmit={(message) => {
155-
if (message.trim()) {
156-
handleSubmit(message)
157-
setMessage({ content: '' })
158-
}
159-
}}
160-
/>
173+
<Box ref={chatContainerRef}>
174+
<Conversation messages={messages} completion={completion} />
175+
<ChatBox
176+
disabled={false}
177+
onSubmit={(message) => {
178+
if (message.trim()) {
179+
handleSubmit(message)
180+
setMessage({ content: '' })
181+
}
182+
}}
183+
/>
184+
</Box>
161185
</Box>
162186
)
163187
}

src/client/components/ChatV2/Conversation.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const MessageItem = ({ message }: { message: Message }) => (
88
<Paper
99
elevation={3}
1010
sx={{
11-
my: '1rem',
11+
my: '2rem',
1212
ml: message.role === 'assistant' ? '0' : '2rem',
1313
mr: message.role === 'assistant' ? '2rem' : '0',
1414
p: '1rem',
@@ -42,7 +42,7 @@ const PöhinäLogo = () => (
4242
)
4343

4444
export const Conversation = ({ messages, completion }: { messages: Message[]; completion: string }) => (
45-
<Box sx={{ flex: 1, overflowY: 'auto' }}>
45+
<Box sx={{ flex: 1, overflowY: 'auto', gap: 2 }}>
4646
{messages.map((message, idx) => (
4747
<MessageItem key={idx} message={message} />
4848
))}

src/client/util/context.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { createContext, RefObject } from 'react'
2+
3+
/**
4+
* Context to hold the reference to the App component which is the most parent component in the application.
5+
* This is used to help trigger auto scrolling when chat containers expands.
6+
*/
7+
export const AppContext = createContext<RefObject<HTMLDivElement> | null>(null)

0 commit comments

Comments
 (0)