@@ -14,4 +14,5 @@ export interface Message { is_favourite: boolean is_sent: boolean uid: string + model: string } @@ -0,0 +1 @@ +export * from './use-image-bot-create' \ No newline at end of file @@ -0,0 +1,54 @@ +import { getUserBalance } from '@/src/entities/balance' +import { Message, MessageSend, sendImage } from '@/src/entities/message' +import { useAppDispatch } from '@/src/main/store/store' +import { Device } from '@/src/shared/lib/types/entities' +import { formDataHelper } from '@/src/widgets/messages' +import { useSession } from 'next-auth/react' +import { Dispatch, SetStateAction, useState } from 'react' + +export function useImageBotCreateImage( + showError: (message: string) => void, + type: string, + device: Device, + setLoading: (value: boolean) => void, + setMessages: Dispatch> +) { + const [isComplete, setIsComplete] = useState(false) + + const { data } = useSession() + + const dispatch = useAppDispatch() + + const createImage = async (dataForSend: MessageSend) => { + const { content, file } = dataForSend + + setIsComplete(false) + setLoading(true) + + const dataSending = file ? formDataHelper(file, dataForSend) : dataForSend + + const response = await sendImage(type, dataSending, data?.access) + + if (response.status >= 400) showError('Ошибка отправки сообщения') + + setLoading(false) + + dispatch(getUserBalance(data?.access)) + + setMessages((prev: Message[]) => { + if (!prev || !prev.length) return response.data + + if (device === 'desktop') { + return [...response.data, ...prev] + } + return [...prev, ...response.data] + }) + + setIsComplete(true) + } + + return { + createImage, + isComplete + } +} @@ -0,0 +1 @@ +export * from './model' \ No newline at end of file @@ -1,98 +1,85 @@ +import { Message } from '@/src/entities/message' import { useAppSelector } from '@/src/main/store/store' +import { useShowData } from '@/src/shared' import { Device } from '@/src/shared/lib/types/entities' -import { Message } from '@/src/shared/lib/types/model' -import { useEffect, useRef, useState } from 'react' - -export function useImageBotPagination( - isComplete: boolean, - desktop: boolean, - messages: Message[] | null, - getMessagesPagination: (device: Device) => any, - deviceType: Device -) { - const [chatScrollHeight, setChatScrollHeight] = useState(0) - const [scrollBottom, setScrollBottom] = useState(0) - const refScrollMobile = useRef() - const [isPaginating, setIsPaginating] = useState(false) - - function handleMobileScroll() { - if (!refScrollMobile.current) return - - 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) - } - } - } - } +import { getImagesGalery } from '@/src/widgets/messages' +import { useSession } from 'next-auth/react' +import { useRef, useState } from 'react' - const handleScroll = () => { - if (window.scrollY + window.innerHeight >= document.documentElement.scrollHeight) { - setIsPaginating(true) - getMessagesPagination(deviceType) - } - } +export function useImageBotPagination(deviceType: Device) { + const refScrollMobile = useRef(null) + const refScrollDesktop = useRef(null) + const mobileScrollContainer = useRef(null) + const offset = useRef(0) - useEffect(() => { - window.addEventListener('scroll', handleScroll) - return () => { - window.removeEventListener('scroll', handleScroll) - } - }, []) - - useEffect(() => { - const block = deviceType === 'desktop' ? document.body : refScrollMobile.current - - if (!block) return - - if (messages != undefined && !isPaginating) { - setChatScrollHeight(block.scrollHeight) - const time = setTimeout(() => { - //@ts-ignore - 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) - } + const [messages, setMessages] = useState([]) - setIsPaginating(false) - }, [messages]) + const [loading, setLoading] = useState(false) - function onIsComplete() { - if (!isComplete) return + const { showError } = useShowData() - if (!desktop) { - const block = refScrollMobile.current - if (block) { - block.scrollTop = block.scrollHeight - } + const { data } = useSession() + + const fetchMessages = async () => { + if (!data) return + + setLoading(true) + const { data: answer, ...response } = await getImagesGalery(data.access, offset.current) + setLoading(false) + + if (response.status >= 400 || !Array.isArray(answer)) + return showError('Ошибка загрузки чата') + + if (deviceType === 'desktop') { + setMessages((prev) => [...prev, ...answer]) + offset.current = offset.current + answer.length return } - window.scroll(0, 0) + setMessages((prev) => [...answer.reverse(), ...prev]) + offset.current = offset.current + answer.length + } + + const callback = async function (entries: IntersectionObserverEntry[]) { + if (!entries[0].isIntersecting) return + + if (deviceType === 'desktop') return fetchMessages() + + if (!mobileScrollContainer.current) return + + const scrollBottom = + mobileScrollContainer.current.scrollHeight - mobileScrollContainer.current.scrollTop + + await fetchMessages() + + setTimeout(() => { + mobileScrollContainer.current!.scroll({ + top: mobileScrollContainer.current!.scrollHeight - scrollBottom, + behavior: 'smooth', + }) + }, 500) } - useEffect(onIsComplete, [isComplete]) + function onObserverMounted() { + const currentObserver = + deviceType === 'desktop' ? refScrollDesktop.current : refScrollMobile.current + + if (!currentObserver) return + + const observer = new IntersectionObserver(callback, { rootMargin: '400px' }) + + observer.observe(currentObserver!) + } return { - refScrollMobile, - handleMobileScroll, - scrollBottom, - } + refScrollMobile, + refScrollDesktop, + onObserverMounted, + messages, + loading, + setLoading, + setMessages, + fetchMessages, + mobileScrollContainer, + } } @@ -21,12 +21,16 @@ interface IProps { images: string[] } -export default function FullScreenModal({ modal, setModal, current, images, onSlideFalse, reverse = false }: IProps) { - const { imagesWithState, updateImageState, currentImageIndex, initialCount } = useImagesLibrary( - images, - current, - reverse - ) +export default function FullScreenModal({ + modal, + setModal, + current, + images, + onSlideFalse, + reverse = false, +}: IProps) { + const { imagesWithState, updateImageState, currentImageIndex, initialCount } = + useImagesLibrary(images, current, reverse) const { swiper, setSwiper, slideNext, slidePrev } = useLibrarySwiper(onSlideFalse, reverse) @@ -34,7 +38,7 @@ export default function FullScreenModal({ modal, setModal, current, images, onSl if (!(initialCount < images.length && swiper)) return if (reverse) { - setTimeout(() => swiper.slideTo(initialCount, 1000), 1000) + setTimeout(() => swiper.slideNext(), 1000) return } @@ -65,7 +69,13 @@ export default function FullScreenModal({ modal, setModal, current, images, onSl }} >
setModal(false)}> - + { const deviceType = getTypeDevice(context) @@ -71,8 +72,25 @@ const Images: React.FC = ({ deviceType, deviceOs }) => { const { openFiltersMobile, setOpenFiltersMobile, params, setParams, includeParams } = useImagesBotFilters() - const { messages, loading, createImage, isComplete, getMessagesPagination } = - useImagesPagination(showError, modelType, deviceType) + const { + refScrollMobile, + refScrollDesktop, + mobileScrollContainer, + onObserverMounted, + setLoading, + setMessages, + fetchMessages, + loading, + messages, + } = useImageBotPagination(deviceType) + + const { createImage, isComplete } = useImageBotCreateImage( + showError, + modelType, + deviceType, + setLoading, + setMessages + ) const { onCreateImage, onLoadImage, image, setImage } = useImagesUniqInput( version, @@ -80,20 +98,13 @@ const Images: React.FC = ({ deviceType, deviceOs }) => { createImage ) - const { refScrollMobile, handleMobileScroll, scrollBottom } = useImageBotPagination( - isComplete, - desktop, - messages, - getMessagesPagination, - deviceType - ) - async function onFetch() { await Promise.all([fetchBotParams()]) } useEffect(() => { onFetch() + onObserverMounted() }, [session, router.query]) return ( @@ -146,44 +157,25 @@ const Images: React.FC = ({ deviceType, deviceOs }) => { /> )} - + getMessagesPagination(deviceType)} isComplete={isComplete} device={deviceType} images={messages} + getMessagesPagination={fetchMessages} /> +
) : ( - {scrollBottom > 500 && ( - { - if (!refScrollMobile.current) return - const block = refScrollMobile.current - - block.scrollTo({ - top: block.scrollHeight, - behavior: 'smooth', // добавляем плавную прокрутку - }) - }} - > - - - )} = ({ deviceType, deviceOs }) => { : 'calc(200px + (400 - 200) * ((100vh - 400px) / (600 - 400)))', overflowY: 'scroll', overflowX: 'hidden', + position: 'relative', }} + ref={mobileScrollContainer} className={'smallScroll'} - onScroll={handleMobileScroll} > +
getMessagesPagination(deviceType)} isComplete={isComplete} device={deviceType} images={messages} + getMessagesPagination={fetchMessages} />
@@ -1,3 +1,3 @@ -export const c = (...classes: Array) => { - return classes.filter(Boolean).join(' ') +export function c(...args: Array) { + return args.filter(Boolean).join(" ") } @@ -39,6 +39,7 @@ export const ImageModelsPopup = ({ model }: ImageModelsPopupProps) => {
{bots.map((item) => ( { @@ -46,7 +47,6 @@ export const ImageModelsPopup = ({ model }: ImageModelsPopupProps) => { push(`/images/${slug}`) }} {...item} - key={item.uid} /> ))}
@@ -7,6 +7,11 @@ gap: 22px; justify-content: space-between; max-width: 500px; + transition: background-color 0.3s ease; + + &:hover{ + background-color: rgba($color: #a4aab5, $alpha: 0.1); + } @media (max-width: 768px) { // max-width: calc(100% - 100px) !important; @@ -18,34 +18,16 @@ export function useImagesPagination( const [loading, setLoading] = useState(false) - const [isComplete, setIsComplete] = useState(false) - const [offset, setOffset] = useState(0) const dispatch = useAppDispatch() - const dataRef = useRef(data) - const messagesRef = useRef(messages) - const offsetRef = useRef(offset) - - useEffect(() => { - dataRef.current = data - }, [data]) - - useEffect(() => { - messagesRef.current = messages - }, [messages]) - - useEffect(() => { - offsetRef.current = offset - }, [offset]) - async function onFetch() { if (!data) return setLoading(true) - - const { data: answer, ...response } = await getImagesGalery(data?.access, offset) + console.log(offset) + const { data: answer, ...response } = await getImagesGalery(data.access, offset) setLoading(false) if (response.status >= 400) return showError('Ошибка загрузки чата') @@ -65,61 +47,28 @@ export function useImagesPagination( onFetch() }, [data, type]) - const getMessagesPagination = useCallback( - async (device: 'mobile' | 'desktop') => { - if (!dataRef.current || !dataRef.current.access) return - - setLoading(true) - const { data: answer, ...response } = await getImagesGalery( - dataRef.current?.access, - offsetRef.current - ) - setLoading(false) - - if (response.status >= 400) return showError('Ошибка загрузки чата') - - if (!Array.isArray(answer) || !messagesRef.current) - return showError('Ошибка загрузки сообщений') - - if (device === 'mobile') { - const newMessages = answer.reverse() - setMessages([...newMessages, ...messagesRef.current]) - return setOffset((prev) => prev + answer.length) - } - - setMessages([...messagesRef.current, ...answer]) - setOffset((prev) => prev + answer.length) - }, - [data, type, offset, messages] - ) - - const createImage = async (dataForSend: MessageSend) => { - const { content, file } = dataForSend + const getMessagesPagination = async (device: 'mobile' | 'desktop') => { + if (!data) return - setIsComplete(false) setLoading(true) - - const dataSending = file ? formDataHelper(file, dataForSend) : dataForSend - - const response = await sendImage(type, dataSending, data?.access) - - if (response.status >= 400) showError('Ошибка отправки сообщения') - + const { data: answer, ...response } = await getImagesGalery(data.access, offset) setLoading(false) - dispatch(getUserBalance(data?.access)) + if (response.status >= 400) return showError('Ошибка загрузки чата') - setMessages((prev) => { - if (!prev || !prev.length) return response.data + if (!Array.isArray(answer)) return showError('Ошибка загрузки сообщений') - if (device === 'desktop') { - return [...response.data, ...prev] - } - return [...prev, ...response.data] - }) + debugger + + if (device === 'mobile') { + const newMessages = answer.reverse() + setMessages([...newMessages, ...messages]) + return setOffset((prev) => prev + answer.length) + } - setIsComplete(true) + setMessages([...messages, ...answer]) + setOffset((prev) => prev + answer.length) } - return { messages, createImage, loading, isComplete, getMessagesPagination } + return { messages, loading, getMessagesPagination } } @@ -54,7 +54,9 @@ export const ChatMessagesList: React.FC = memo( console.log(messageResponse) return messageResponse .map((item) => item.file) - .filter((item) => item !== null && !(item as string).includes('.zip')) as string[] + .filter( + (item) => item !== null && !(item as string).includes('.zip') + ) as string[] }, [messageResponse]) React.useEffect(() => { @@ -111,7 +113,7 @@ export const ChatMessagesList: React.FC = memo( {createPortal( { getMessagesPagination && (await getMessagesPagination()) @@ -121,6 +123,7 @@ export const ChatMessagesList: React.FC = memo( document.getElementById('modal-container')! )} + = memo( )} {messageResponse?.length === 0 && status === 'authenticated' ? ( - <>{desktop && modelType !== 'deepl' && } + <> + {desktop && modelType !== 'deepl' && ( + + )} + ) : ( messageResponse?.map((message, idx) => { return ( @@ -1,36 +1,47 @@ +.model { + position: absolute; + top: 12px; + left: 12px; + z-index: 100; + background-color: rgba($color: #151518, $alpha: 0.5); + font-size: 13px; + padding: 2px 8px; + border-radius: 10px; + font-weight: 500; + color: #fff; +} .wrap { - display: flex; - overflow: hidden; - width: 100%; - flex-wrap: wrap; - gap: 20px; - @media (max-width: 766px) { - height: 70vh; - width: 100%; - overflow-y: scroll; - text-align: center; - justify-content: center; - } - div { - margin-right: 10px; - @media (max-width: 766px) { - width: 100%; - } + display: flex; + overflow: hidden; + width: 100%; + flex-wrap: wrap; + gap: 20px; + @media (max-width: 766px) { + height: 70vh; + width: 100%; + overflow-y: scroll; + text-align: center; + justify-content: center; + } + div { + margin-right: 10px; + @media (max-width: 766px) { + width: 100%; + } - .image { - margin:10px auto; - width: 250px; - height: 250px; - border-radius: 15px !important; - } + .image { + margin: 10px auto; + width: 250px; + height: 250px; + border-radius: 15px !important; + } - @media (max-width: 1700px) { + @media (max-width: 1700px) { + } - } - - @media (max-width: 766px) { - text-align: center; - width: 80%; - } - } -} \ No newline at end of file + @media (max-width: 766px) { + text-align: center; + width: 80%; + } + } +} @@ -8,11 +8,11 @@ import { ImageModal } from '@/src/features/image-modal' import { useAppSelector } from '@/src/main/store/store' import { ClientOnly, Success, TooltipCustom, useShowData } from '@/src/shared' import { useAutoScroll } from '@/src/shared/lib/hooks' -import { Message } from '@/src/shared/lib/types/model' import styles from './image-messages-list.module.scss' import { createPortal } from 'react-dom' import { useMessages } from '../model/use-messages' +import { Message } from '@/src/entities/message' interface MessagesList { device: 'mobile' | 'desktop' @@ -201,13 +201,13 @@ export const ImageMessagesList: React.FC = memo(({ device, images, > {images?.length !== 0 && Array.isArray(images) && - images.map((message) => { + images.map((message, index) => { //@ts-ignore const isZip = message.file && message.file.includes('.zip') //@ts-ignore const isSvg = message.file && message.file.includes('.svg') return ( - + {isZip ? ( = memo(({ device, images, borderRadius: '15px', }} > + + {message.model} + {!isSvg ? ( <> = memo(({ device, token, favorites, title }) => { - const theme = useAppSelector((state) => state.theme.theme) - - const [anchorElModel, setAnchorElModel] = React.useState(null) - - const desktop = device === 'desktop' - - const { status } = useSession() - - const { pathname } = useRouter() - - const balance = useAppSelector((state) => state.balance.balance) - - const closeMenuModel = () => setAnchorElModel(null) - const openMenuModel = (event: React.MouseEvent) => { - setAnchorElModel(event.currentTarget) - } - const goToPayment = () => { - Router.push('/account') - } - - const LazyTutorial = dynamic(() => import('@/src/features/tutorial-gpt').then((component) => component.Tutorial)) - - const LazyArrowIcon = dynamic(() => import('./arrow-up-or-down')) - - return ( - - - {desktop && ( - - )} - - - {title} - - {!desktop && } - - - {desktop ? ( - - ) : ( - - )} - - - - {desktop && status === 'authenticated' && pathname === '/chatgpt' && } - - {token && ( - - - - )} - {desktop && ( - - Купить токены - - )} - - - - ) -}) - -ModelTopBar.displayName = 'ModelTopBar'