@@ -86,6 +86,7 @@ function Chat({ modelType={modelType} deleteMessage={deleteMessage} modelTitle={modelTitle} + loading={loading} /> = ({ deviceType, deviceOs }) => { const resetParams = () => { if (botParams) { dispatch(setParametres({})) - if (botParams.versions.length !== 0) { + if (botParams.versions?.length !== 0) { setVersion(botParams.versions[0].slug) dispatch( setParametres( @@ -209,7 +209,7 @@ const Page: React.FC = ({ deviceType, deviceOs }) => { /> {desktop && ( - {botParams?.versions && botParams.versions.length !== 0 && ( + {botParams?.versions && botParams.versions?.length !== 0 && ( <> ВЕРСИИ @@ -268,7 +268,7 @@ const Page: React.FC = ({ deviceType, deviceOs }) => { {!desktop && ( - {botParams?.versions && botParams.versions.length !== 0 && ( + {botParams?.versions && botParams.versions?.length !== 0 && ( <> = ({ deviceType, deviceOs }) => { const [version, setVersion] = React.useState('') const [modelType, setModelType] = React.useState('') const [params, setParams] = React.useState(false) - const refScroll = useRef(null) const includeParams = useAppSelector((state) => state.params.params) const dispatch = useDispatch() + const { messages, loading, createImage, isComplete, getMessagesPagination } = useModelImages(showError, modelType, deviceType) + + const [chatScrollHeight, setChatScrollHeight] = React.useState(0) + const [scrollBottom, setScrollBottom] = React.useState(0) + const refScrollMobile = useRef() + const [isPaginating, setIsPaginating] = React.useState(false) + React.useEffect(() => { model_api .getBotParams(router.asPath.split('/')[2], data?.access) @@ -79,7 +86,6 @@ const Images: React.FC = ({ deviceType, deviceOs }) => { .catch((err) => {}) }, [data?.access]) - const { messages, loading, createImage, isComplete } = useModelImages(showError, modelType, deviceType) const onLoadImage = (event: React.ChangeEvent) => { if (event.target.files) { setImage(event.target.files[0]) @@ -171,7 +177,7 @@ const Images: React.FC = ({ deviceType, deviceOs }) => { React.useEffect(() => { if (isComplete) { if (!desktop) { - const block = refScroll.current + const block = refScrollMobile.current if (block) { //@ts-ignore block.scrollTop = block.scrollHeight @@ -182,18 +188,55 @@ const Images: React.FC = ({ deviceType, deviceOs }) => { } }, [isComplete]) + const handleMobileScroll = () => { + setScrollBottom(refScrollMobile.current?.scrollHeight - refScrollMobile.current?.scrollTop - refScrollMobile.current?.clientHeight) + + if (refScrollMobile.current && messages?.length !== 0) { + const { scrollTop, scrollHeight, clientHeight } = refScrollMobile.current + if (scrollTop === 0) { + if (getMessagesPagination) { + setIsPaginating(true) + getMessagesPagination(deviceType) + } + } + } + } + + const handleScroll = () => { + if (window.scrollY + window.innerHeight >= document.documentElement.scrollHeight) { + setIsPaginating(true) + getMessagesPagination(deviceType) + } + } + React.useEffect(() => { - if (!desktop && messages !== null) { - const block = refScroll.current + window.addEventListener('scroll', handleScroll) + return () => { + window.removeEventListener('scroll', handleScroll) + } + }, []) - const time = setTimeout(() => { - if (block) { + React.useEffect(() => { + const block = deviceType === 'desktop' ? window : refScrollMobile.current + if (block) { + if (messages != undefined && !isPaginating) { + setChatScrollHeight(block.scrollHeight) + const time = setTimeout(() => { //@ts-ignore - block.scrollTop = block.scrollHeight - } - }, 250) - return () => clearTimeout(time) + block.scrollTo({ + top: block.scrollHeight, + behavior: 'smooth', // добавляем плавную прокрутку + }) + }, 350) + return () => clearTimeout(time) + } else if (messages != undefined && isPaginating) { + //@ts-ignore + block.scrollTop = block.scrollHeight - chatScrollHeight + + setChatScrollHeight(block.scrollHeight) + } } + setIsPaginating(false) }, [messages]) return ( @@ -239,8 +282,32 @@ const Images: React.FC = ({ deviceType, deviceOs }) => { ) : ( + {scrollBottom > 500 && ( + { + const block = refScrollMobile.current + + block.scrollTo({ + top: block.scrollHeight, + behavior: 'smooth', // добавляем плавную прокрутку + }) + }} + > + + + )} = ({ deviceType, deviceOs }) => { overflowX: 'hidden', }} className={'smallScroll'} + onScroll={handleMobileScroll} > @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react' +import React, { useCallback, useEffect, useRef, useState } from 'react' import axios, { AxiosError, AxiosResponse } from 'axios' import base64 from 'base64-encode-file' import { useSession } from 'next-auth/react' @@ -23,7 +23,7 @@ const formDataHelper = (file: File, dataForSend: MessageSend): FormData => export const ModelsWithChatsEndpoints = { getData: async (chatUid: string, offset: number, token?: string) => { try { - const { data } = await axios.get(API_URL + `/chats/${chatUid}/messages/?limit=100&offset=${offset}`, { + const { data } = await axios.get(API_URL + `/chats/${chatUid}/messages/?limit=10&offset=${offset}`, { headers: { Authorization: `Bearer ${token}`, }, @@ -77,12 +77,12 @@ export function useModel( setMessages([]) ;(async () => { setLoading(true) - const answer = await ModelsWithChatsEndpoints.getData(currentChat, offset, data?.access) + const answer = await ModelsWithChatsEndpoints.getData(currentChat, 0, data?.access) setLoading(false) if (Array.isArray(answer)) { setMessages(answer.reverse()) - // setOffset((prev) => prev + 10) + setOffset(answer.length) } else { showError('Ошибка загрузки чата') return @@ -100,7 +100,7 @@ export function useModel( if (Array.isArray(answer) && messages != null) { const newMessages = answer.reverse() setMessages([...newMessages, ...messages]) - setOffset((prev) => prev + 10) + setOffset((prev) => prev + answer.length) } else { showError('Ошибка загрузки сообщений') return @@ -192,9 +192,9 @@ export function useModel( } export const ModelsWithImagesEndpoints = { - getData: async (type: string, token?: string) => { + getData: async (type: string, offset: number, token?: string) => { try { - const { data } = await axios.get(API_URL + `/media/image/${type}?limit=100`, { + const { data } = await axios.get(API_URL + `/media/image/${type}?limit=10&offset=${offset}`, { headers: { Authorization: `Bearer ${token}`, }, @@ -242,13 +242,33 @@ export function useModelImages(showError: (message: string) => void, type: st const [isComplete, setIsComplete] = useState(false) + const [offset, setOffset] = useState(0) + const dispatch = useAppDispatch() + const typeRef = useRef(type) + const dataRef = useRef(data) + const messagesRef = useRef(messages) + const offsetRef = useRef(offset) + + useEffect(() => { + typeRef.current = type + }, [type]) + useEffect(() => { + dataRef.current = data + }, [data]) + useEffect(() => { + messagesRef.current = messages + }, [messages]) + useEffect(() => { + offsetRef.current = offset + }, [offset]) + useEffect(() => { if (data?.access && type !== '' && type !== undefined) { ;(async () => { setLoading(true) - const answer = await getData(type, data?.access) + const answer = await getData(type, offset, data?.access) setLoading(false) if ('error' in answer) { showError('Ошибка загрузки чата') @@ -258,14 +278,46 @@ export function useModelImages(showError: (message: string) => void, type: st if (Array.isArray(answer)) { if (device === 'desktop') { setMessages(answer) + setOffset(answer.length) } else { setMessages(answer.reverse()) + setOffset(answer.length) } } })() } }, [data?.access, type]) + const getMessagesPagination = useCallback( + async (device: 'mobile' | 'desktop') => { + // console.log(dataRef.current?.access); + // console.log(typeRef.current); + // console.log(offsetRef.current); + // console.log(messagesRef.current); + + if (dataRef.current?.access && typeRef.current !== '' && typeRef.current !== undefined) { + setLoading(true) + const answer = await getData(typeRef.current, offsetRef.current, dataRef.current?.access) + setLoading(false) + + if (Array.isArray(answer) && messagesRef.current != null && device === 'mobile') { + const newMessages = answer.reverse() + setMessages([...newMessages, ...messagesRef.current]) + setOffset((prev) => prev + answer.length) + } else if (Array.isArray(answer) && messagesRef.current != null && device === 'desktop') { + setMessages([...messagesRef.current, ...answer]) + setOffset((prev) => prev + answer.length) + } else { + console.log('err') + + showError('Ошибка загрузки сообщений') + return + } + } + }, + [data?.access, type, offset, messages] + ) + const createImage = async (dataForSend: MessageSend) => { const { content, file } = dataForSend @@ -301,7 +353,7 @@ export function useModelImages(showError: (message: string) => void, type: st setIsComplete(true) } - return { messages, createImage, loading, isComplete } + return { messages, createImage, loading, isComplete, getMessagesPagination } } export const ModelsMediaApi = { @@ -0,0 +1,11 @@ +import * as React from 'react' +import { SVGProps } from 'react' + +export const ArrowDownScroll: React.FC> = ({ ...props }) => { + return ( + + + + + ) +} @@ -58,7 +58,8 @@ export function BotMessage(props: any) { : {} return ( - + // + - + ) } @@ -67,7 +67,8 @@ export const UserMessage = React.memo(function UserMessage(props: IMessagesList) {props.message.file && } {!props.message.from_model ? ( - + // + - + ) : ( void onLoadImage?: (event: React.ChangeEvent | null, file?: File) => void + loading: boolean } export const ChatMessagesList: React.FC = memo( - ({ messageResponse, onLoadImage, setResendValue, modelTitle, device, modelType, mode, getMessagesPagination, deleteMessage }) => { + ({ messageResponse, onLoadImage, setResendValue, modelTitle, device, modelType, mode, getMessagesPagination, deleteMessage, loading }) => { const paginationScroll = React.useRef() + const [isPaginating, setIsPaginating] = React.useState(false) + const [chatScrollHeight, setChatScrollHeight] = React.useState(0) + const [scrollBottom, setScrollBottom] = React.useState(0) + const desktop = device === 'desktop' const { status } = useSession() const [isNewMessage, setIsNewMessage] = React.useState(false) React.useEffect(() => { - if (messageResponse != undefined) { - const block = paginationScroll.current + const block = paginationScroll.current + + if (messageResponse != undefined && !isPaginating) { + setChatScrollHeight(paginationScroll.current.scrollHeight) const time = setTimeout(() => { if (block) { //@ts-ignore - block.scrollTop = block.scrollHeight + block.scrollTo({ + top: block.scrollHeight, + behavior: 'smooth', // добавляем плавную прокрутку + }) } }, 250) return () => clearTimeout(time) + } else if (messageResponse != undefined && isPaginating) { + if (block) { + //@ts-ignore + block.scrollTop = block.scrollHeight - chatScrollHeight + setChatScrollHeight(paginationScroll.current.scrollHeight) + } } + setIsPaginating(false) }, [messageResponse]) useEffect(() => { setIsNewMessage(true) }, [messageResponse]) + const handleScroll = () => { + setScrollBottom(paginationScroll.current?.scrollHeight - paginationScroll.current?.scrollTop - paginationScroll.current?.clientHeight) + + if (paginationScroll.current && messageResponse?.length !== 0) { + const { scrollTop, scrollHeight, clientHeight } = paginationScroll.current + if (scrollTop === 0) { + if (getMessagesPagination) { + setIsPaginating(true) + getMessagesPagination() + } + } + } + } + return ( = memo( paddingLeft: desktop ? 1 : 0, }} ref={paginationScroll} + onScroll={handleScroll} > + {scrollBottom > 500 && ( + { + //@ts-ignore + const block = paginationScroll.current + + block.scrollTo({ + top: block.scrollHeight, + behavior: 'smooth', // добавляем плавную прокрутку + }) + }} + > + + + )} + + {loading && ( + + + + )} + {messageResponse?.length === 0 && status === 'authenticated' ? ( <>{desktop && modelType !== 'deepl' && } ) : (