@@ -1,3 +1,5 @@
+
+
\ No newline at end of file
@@ -1,3 +1,4 @@
+
+
\ No newline at end of file
@@ -0,0 +1,11 @@
+
\ No newline at end of file
@@ -0,0 +1,12 @@
+
\ No newline at end of file
@@ -0,0 +1,5 @@
+
\ No newline at end of file
@@ -0,0 +1,5 @@
+
\ No newline at end of file
@@ -0,0 +1,125 @@
+import { useAppSelector } from '@/src/main/store/store'
+import { TooltipCustom } from '@/src/shared'
+import { API_URL } from '@/src/shared/lib/constants'
+import { InputStyleSmallLight, InputStyleSmallDark } from '@/src/shared/ui/input'
+import { TableRow, TableCell, TextField, Stack } from '@mui/material'
+import { useMask } from '@react-input/mask'
+import axios from 'axios'
+import { useSession } from 'next-auth/react'
+import Image from 'next/image'
+import { useState, useMemo, useEffect } from 'react'
+
+export interface KeyRowProps extends ApiKeyDTO {
+ deleteKey: (name: string) => void
+ keyValue: string
+}
+
+export const KeyRow = ({
+ key,
+ name,
+ user,
+ keyValue,
+ deleteKey,
+ token_limit,
+ created_at,
+ expires_at,
+ ...props
+}: KeyRowProps) => {
+ const [isCopy, setIsCopy] = useState(false)
+ const [limit, setLimit] = useState(token_limit)
+ const theme = useAppSelector((state) => state.theme.theme)
+ const { data: session } = useSession()
+
+ const inputRef = useMask({
+ mask: '_'.repeat(10),
+ replacement: {
+ _: /\d+/,
+ },
+ })
+
+ const copy = (text: string) => {
+ navigator.clipboard.writeText(text)
+ setIsCopy(true)
+ setTimeout(() => setIsCopy(false), 3000)
+ }
+
+ const computedLimit = useMemo(() => {
+ return limit === '' || limit === 'Бесконечно' ? null : Number(limit)
+ }, [limit])
+
+ useEffect(() => {
+ let timeout = window.setTimeout(() => {
+ console.log(limit)
+ if (limit !== token_limit) {
+ axios.patch(
+ API_URL + '/public/api-key',
+ { token_limit: computedLimit, name },
+ { headers: { Authorization: `Bearer ${session?.access}` } }
+ )
+ }
+ }, 1000)
+ return () => window.clearTimeout(timeout)
+ }, [limit])
+
+ return (
+
+
+ {name}
+
+
+ {keyValue}
+
+
+ setLimit(e.target.value)}
+ sx={
+ theme === 'light'
+ ? { ...InputStyleSmallLight }
+ : { ...InputStyleSmallDark }
+ }
+ />
+
+
+ {created_at.split('T')[0]}
+
+
+ {expires_at !== null ? expires_at : 'Бессрочно'}
+
+
+
+ {isCopy ? (
+
+ ) : (
+
+ copy(keyValue)}
+ src={'/svg/copy.svg'}
+ width={20}
+ height={20}
+ style={{ cursor: 'pointer' }}
+ alt={'copy'}
+ />
+
+ )}
+ deleteKey(name)}
+ style={{ cursor: 'pointer' }}
+ src='/svg/main_menu/trash.svg'
+ width={20}
+ height={20}
+ alt='Удалить'
+ />
+
+
+
+ )
+}
@@ -0,0 +1 @@
+export * from './message.routes'
\ No newline at end of file
@@ -0,0 +1,25 @@
+import { API_URL } from "@/src/shared/lib/constants"
+import { IMessageRequest } from "@/src/shared/lib/types/types-gpt"
+import axios, { AxiosResponse } from "axios"
+import { Message, MessageSend } from "../types"
+
+export async function sendImage(
+ model: string | null,
+ dataForSend: MessageSend | FormData,
+ token?: string
+) {
+ const HeaderDataType =
+ dataForSend instanceof FormData ? 'multipart/form-data' : 'application/json'
+
+ return await axios.post(
+ API_URL + `/media/image/${model}`,
+ dataForSend,
+ {
+ withCredentials: true,
+ headers: {
+ Authorization: `Bearer ${token}`,
+ 'Content-Type': HeaderDataType,
+ },
+ }
+ )
+}
\ No newline at end of file
@@ -0,0 +1 @@
+export * from './message'
\ No newline at end of file
@@ -0,0 +1,18 @@
+export interface MessageSend {
+ content: string
+ file?: File | null
+ info: T
+}
+
+export interface Message {
+ content: string
+ created_at: string
+ elapsed_time: string
+ file: File | null | string
+ from_model: boolean
+ info: null
+ is_favourite: boolean
+ is_sent: boolean
+ uid: string
+ model: string
+}
@@ -0,0 +1,2 @@
+export * from './types'
+export * from './api'
\ No newline at end of file
@@ -0,0 +1,11 @@
+import { API_URL } from '@/src/shared/lib/constants'
+import axios from 'axios'
+import { IModel } from '../types'
+
+export async function getBotParams(slug: string, token?: string) {
+ return await axios.get(API_URL + `/ml_models/${slug}`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ })
+}
@@ -0,0 +1,11 @@
+import { API_URL } from '@/src/shared/lib/constants'
+import axios from 'axios'
+import { IShortModel } from '../types'
+
+export async function getModelsImages(token?: string) {
+ return await axios.get(API_URL + '/ml_models/?category=images', {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ })
+}
@@ -0,0 +1,2 @@
+export * from './bot.route'
+export * from './images-bots.route'
@@ -0,0 +1 @@
+export * from './use-images-bots'
\ No newline at end of file
@@ -0,0 +1,133 @@
+import { useState } from 'react'
+import { IModel } from '../types'
+import { useSession } from 'next-auth/react'
+import { getBotParams } from '../api'
+import { useAppDispatch } from '@/src/main/store/store'
+import { setParams } from '@/src/main/store/model-parametres-store'
+
+export function useImageBot(slug: string) {
+ const [botParams, setBotParams] = useState(null)
+ const [version, setVersion] = useState('')
+ const [modelType, setModelType] = useState('')
+
+ const dispatch = useAppDispatch()
+
+ const { data } = useSession()
+
+ function setDefault(bot: IModel) {
+ setVersion('')
+
+ dispatch(
+ setParams(bot.parameters.reduce((a, v) => ({ ...a, [v.key]: v.values.default }), {}))
+ )
+ }
+
+ // это пиз**ц
+ // нужен рефакторинг (я то в этом не разбираюсь)
+ // а стажеры и подавно))))
+ function setStoreParams(bot: IModel) {
+ dispatch(
+ setParams(
+ bot.parameters.reduce(
+ (a, v) =>
+ v.versions.includes(bot.versions[0].slug)
+ ? { ...a, [v.key]: v.values.default }
+ : { ...a },
+ {}
+ )
+ )
+ )
+ }
+
+ function setAllParams(bot: IModel) {
+ setBotParams(bot)
+ setModelType(bot.slug)
+
+ // store
+ if (bot.versions.length === 0) return setDefault(bot)
+
+ setVersion(bot.versions[0].slug)
+ setStoreParams(bot)
+ }
+
+ // это пиз**ц
+ const resetParams = () => {
+ if (botParams) {
+ dispatch(setParams({}))
+ if (botParams.versions.length !== 0) {
+ setVersion(botParams.versions[0].slug)
+ dispatch(
+ setParams(
+ botParams.parameters.reduce(
+ (a, v) =>
+ v.versions.includes(botParams.versions[0].slug)
+ ? { ...a, [v.key]: v.values.default }
+ : { ...a },
+ {}
+ )
+ )
+ )
+ } else {
+ setVersion(botParams.slug)
+ dispatch(
+ setParams(
+ botParams.parameters.reduce(
+ (a, v) => ({ ...a, [v.key]: v.values.default }),
+ {}
+ )
+ )
+ )
+ }
+ }
+ }
+
+ // это пиз**ц
+ const setDefaultParams = () => {
+ if (!botParams) return
+
+ dispatch(setParams({}))
+
+ if (version === '') {
+ return dispatch(
+ setParams(
+ botParams.parameters.reduce(
+ (a, v) => ({ ...a, [v.key]: v.values.default }),
+ {}
+ )
+ )
+ )
+ }
+
+ dispatch(
+ setParams(
+ botParams.parameters.reduce(
+ (a, v) =>
+ v.versions.includes(version)
+ ? { ...a, [v.key]: v.values.default }
+ : { ...a },
+ {}
+ )
+ )
+ )
+ }
+
+ // api functions
+
+ async function fetchBotParams() {
+ if (!data) return
+
+ const { data: bot, ...response } = await getBotParams(slug, data.access)
+
+ if (response.status < 400) setAllParams(bot)
+ }
+
+ return {
+ botParams,
+ version,
+ modelType,
+ fetchBotParams,
+ setVersion,
+ resetParams,
+ setDefaultParams
+ }
+}
@@ -0,0 +1,24 @@
+import { useState } from 'react'
+import { getModelsImages } from '../api'
+import { useSession } from 'next-auth/react'
+import { IShortModel } from '../types'
+
+export function useImagesBots() {
+ const [bots, setBots] = useState([])
+
+ const { data } = useSession()
+
+ async function fetchBots() {
+ if (!data) return
+
+ const response = await getModelsImages(data.access)
+
+ if (response.status < 400) setBots(response.data)
+ }
+
+ return {
+ bots,
+ fetchBots,
+ setBots
+ }
+}
@@ -0,0 +1 @@
+export * from './model.types'
\ No newline at end of file
@@ -0,0 +1,54 @@
+type ModelForChats = 'chatgpt' | 'llama2' | 'vicuna' | 'deepl' | 'mistral'
+
+export interface IShortModel {
+ uid: string
+ title: string
+ description: string
+ slug: string
+ image: string
+ actual_stat: {
+ generation_time: string
+ tokens_cost: string
+ }
+}
+
+export interface IModel {
+ uid: string
+ title: string
+ description: string
+ slug: string
+ image: string
+ settings: { is_active: boolean }
+ parameters: IModelParams[]
+ versions: IModelVersions[]
+ inputs: IModelInputs[]
+}
+export interface IModelParams {
+ name: string
+ description: string
+ key: string
+ type: string
+ required: boolean
+ values: {
+ availables: string[]
+ default: any
+ end: number
+ start: number
+ step: number
+ }
+ versions: string[]
+}
+export interface IModelVersions {
+ name: string
+ description: string
+ default: boolean
+ slug: string
+}
+export interface IModelInputs {
+ // type: 'image' | 'zip' | 'text' | 'audio' | 'pdf' | 'txt'
+ type: string
+ required: boolean
+ versions: string[]
+}
+
+export default ModelForChats
@@ -0,0 +1,3 @@
+export * from './api'
+export * from './model'
+export * from './types'
\ No newline at end of file
@@ -0,0 +1,168 @@
+import axios, { AxiosResponse } from 'axios'
+import { User } from 'next-auth'
+
+import { API_URL } from '@/src/shared/lib/constants'
+import { IOffer } from '@/src/widgets/payment/model/payment'
+import { AccountType, DataForLogin, PayProductRequest, PayProductResponse } from '../model/types'
+
+export async function getPaymentsPlans(token: string): Promise {
+ try {
+ const { data } = await axios.get(API_URL + '/payments/plans', {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ })
+
+ return data
+ } catch (err) {
+ return null
+ }
+}
+
+export async function payProduct(token: string | null, plan: string): Promise {
+ if (token === null) {
+ return null
+ }
+
+ try {
+ const { data } = await axios.post>(
+ API_URL + '/payments/plans',
+ {
+ uid: plan,
+ is_test: 1,
+ },
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ }
+ )
+ return data.payment_url
+ } catch (err) {
+ return null
+ }
+}
+
+export async function changePassword(
+ token: string | null,
+ password_1: string,
+ password_2: string,
+ current_password: string
+): Promise {
+ try {
+ const { status } = await axios.put(
+ API_URL + '/auth/reset-pass',
+ {
+ password_1,
+ password_2,
+ current_password,
+ },
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ }
+ )
+
+ return status
+ } catch (err) {
+ return 400
+ }
+}
+
+export async function getApiKeys(token: string | null): Promise {
+ try {
+ const { data } = await axios.get(API_URL + '/public/api-key', {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ })
+
+ return data
+ } catch (err) {
+ return null
+ }
+}
+
+export async function createApiKeys(data: any, token: string | null): Promise {
+ try {
+ const { data: result } = await axios.post(API_URL + '/public/api-key', data, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ })
+
+ return result
+ } catch (err) {
+ return null
+ }
+}
+
+export async function deleteApiKey(name: any, token: string | null): Promise {
+ try {
+ const { data: result, status } = await axios.delete(API_URL + '/public/api-key', {
+ data: {
+ name,
+ },
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ })
+
+ return status
+ } catch (err) {
+ return null
+ }
+}
+
+export async function loginByEmail(email: string, password: string): Promise {
+ try {
+ const { data } = await axios.post>(
+ API_URL + '/auth/login',
+ {
+ email,
+ password,
+ }
+ )
+
+ return data
+ } catch (err) {
+ return null
+ }
+}
+
+export async function removeSub(token?: string) {
+ try {
+ const { status } = await axios.delete(API_URL + '/payments/plans', {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ })
+
+ return status
+ } catch (err) {
+ return null
+ }
+}
+
+
+export const getAccountType = async (token: string | null | undefined): Promise => {
+ if (!token) {
+ return 'regular'
+ }
+
+ try {
+ const { data } = await axios.get>(
+ API_URL + '/auth/account-type',
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ }
+ )
+
+ return data.status
+ } catch (err) {
+ return 'regular'
+ }
+}
@@ -1,22 +0,0 @@
-import axios, { AxiosResponse } from 'axios'
-
-import { API_URL } from '@/src/shared/lib/constants'
-
-import { AccountType } from '../model/types'
-export const getAccountType = async (token: string | null | undefined): Promise => {
- if (!token) {
- return 'regular'
- }
-
- try {
- const { data } = await axios.get>(API_URL + '/auth/account-type', {
- headers: {
- Authorization: `Bearer ${token}`,
- },
- })
-
- return data.status
- } catch (err) {
- return 'regular'
- }
-}
@@ -0,0 +1 @@
+export * from './account-endpoints'
@@ -14,3 +14,21 @@ export interface IUserSetting {
type: SettingType
value: SettingValueType
}
+
+export interface UserBalance {
+ current_token_balance: number
+}
+
+export interface PayProductRequest {
+ uid: string
+ is_test: number
+}
+
+export interface PayProductResponse {
+ payment_url: string
+}
+
+export interface DataForLogin {
+ email: string
+ password: string
+}
@@ -1 +1,2 @@
export { getAllInfo, userSlice } from './model/user-type-slice'
+export * from './api'
@@ -7,12 +7,12 @@ import { useSession } from 'next-auth/react'
import { useAppSelector } from '@/src/main/store/store'
import { ButtonGray, ButtonUI, Error, InputStyleDark, InputStyleLight, Loader, Modal } from '@/src/shared'
-import { accountApi } from '@/src/shared/api/account-endpoints'
import { API_URL } from '@/src/shared/lib/constants'
import { useShowData } from '@/src/shared/lib/hooks'
import { DateInput } from '@/src/shared/ui/date-input/date-input'
import styles from '../invite-person-in-business/ui/invite-modal.module.scss'
+import { createApiKeys, getApiKeys } from '@/src/entities/user-account'
export const ApiKeyModal = ({
open,
@@ -45,10 +45,10 @@ export const ApiKeyModal = ({
setIsLoading(true)
const { result } =
endDate && endDate !== ''
- ? await accountApi.createApiKeys({ name: title !== '' ? title : keyName, expires_at: endDate }, data?.access)
- : await accountApi.createApiKeys({ name: title !== '' ? title : keyName }, data?.access)
+ ? await createApiKeys({ name: title !== '' ? title : keyName, expires_at: endDate }, data?.access)
+ : await createApiKeys({ name: title !== '' ? title : keyName }, data?.access)
if (result !== null) {
- accountApi.getApiKeys(data?.access).then((res) => {
+ getApiKeys(data?.access).then((res) => {
setKeys(res)
setIsLoading(false)
})
@@ -1,7 +1,7 @@
-import { accountApi } from '@/src/shared/api/account-endpoints'
+import { loginByEmail } from "@/src/entities/user-account"
export const authTelegram = async (email: any, password: any) => {
- const user = await accountApi.loginByEmail(email, password)
+ const user = await loginByEmail(email, password)
if (user !== null) {
(window as any).Telegram.WebApp.sendData(user.token.access)
@@ -10,7 +10,7 @@ export const authTelegram = async (email: any, password: any) => {
export const authTelegramYandex = async (email: any, password: any) => {
- const user = await accountApi.loginByEmail(email, password)
+ const user = await loginByEmail(email, password)
if (user !== null) {
;(window as any).Telegram.WebApp.sendData(user.token.access)
@@ -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
@@ -0,0 +1 @@
+export * from './use-images-bot-filters'
\ No newline at end of file
@@ -0,0 +1,16 @@
+import { useAppSelector } from '@/src/main/store/store'
+import { useState } from 'react'
+
+export function useImagesBotFilters() {
+ const [openFiltersMobile, setOpenFiltersMobile] = useState(false)
+ const [params, setParams] = useState(false)
+ const includeParams = useAppSelector((state) => state.params.params)
+
+ return {
+ openFiltersMobile,
+ setOpenFiltersMobile,
+ params,
+ setParams,
+ includeParams,
+ }
+}
@@ -0,0 +1 @@
+export * from './model'
\ No newline at end of file
@@ -0,0 +1 @@
+export * from './use-images-uniq-input'
\ No newline at end of file
@@ -0,0 +1,65 @@
+import { useShowData } from '@/src/shared'
+import { MessageSend } from '@/src/shared/lib/types/model'
+import { useState, ChangeEvent } from 'react'
+
+export function useImagesUniqInput(
+ version: string,
+ includeParams: object,
+ createImage: (dataForSend: MessageSend) => any
+) {
+ const [image, setImage] = useState(null)
+
+ const { error, showError } = useShowData()
+
+ function onLoadImage(event: ChangeEvent) {
+ if (event.target.files) {
+ setImage(event.target.files[0])
+ }
+ }
+
+ function onCreateImage(input: string, required: (string | null)[]) {
+
+ // про switch не слышали люди))
+ if (required.includes('text') && (input === '' || input === null)) {
+ showError('Введите сообщение!')
+ return false
+ }
+ if (required.includes('image') && image === null) {
+ showError('Прикрепите изображение!')
+ return false
+ }
+ if (required.includes('zip') && image === null) {
+ showError('Прикрепите архив!')
+ return false
+ }
+
+ // Снова какой то пиз**ц
+ let data = {}
+ if (version === '') {
+ data = {
+ ...includeParams,
+ }
+ } else {
+ data = {
+ version: version,
+ ...includeParams,
+ }
+ }
+
+ createImage({
+ content: input,
+ file: image,
+ info: {
+ ...data,
+ },
+ })
+ return true
+ }
+
+ return {
+ image,
+ setImage,
+ onLoadImage,
+ onCreateImage,
+ }
+}
@@ -0,0 +1 @@
+export * from './model'
\ No newline at end of file
@@ -0,0 +1 @@
+export * from './use-images-bot-pagination'
\ No newline at end of file
@@ -0,0 +1,115 @@
+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 { getImagesGalery } from '@/src/widgets/messages'
+import { useMediaQuery } from '@mui/material'
+import { useSession } from 'next-auth/react'
+import { useRef, useState } from 'react'
+import { Limit, LimitSize } from '../types'
+
+export function useImageBotPagination(deviceType: Device) {
+ const refScrollMobile = useRef(null)
+ const refScrollDesktop = useRef(null)
+ const mobileScrollContainer = useRef(null)
+ const offset = useRef(0)
+
+ const [messages, setMessages] = useState([])
+
+ const [loading, setLoading] = useState(false)
+
+ const { showError } = useShowData()
+
+ const { data } = useSession()
+
+ const limits: Record = {
+ small: {
+ active: useMediaQuery('(max-height: 600px)'),
+ limit: 20,
+ firstLimit: 30,
+ },
+ medium: {
+ active: useMediaQuery('(min-height: 600px) and (max-height: 900px)'),
+ limit: 30,
+ firstLimit: 50,
+ },
+ large: {
+ active: useMediaQuery('(min-height: 900px)'),
+ limit: 45,
+ firstLimit: 70,
+ },
+ }
+
+ const fetchMessages = async (count?: number) => {
+ if (!data) return
+
+ setLoading(true)
+ const { data: answer, ...response } = await getImagesGalery(
+ data.access,
+ offset.current,
+ count || 10
+ )
+ 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
+ }
+
+ 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') {
+ const active = Object.values(limits).find((item) => item.active)
+
+ if (offset.current > 0) return fetchMessages(active?.limit)
+
+ fetchMessages(active?.firstLimit)
+ }
+
+ 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)
+ }
+
+ 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,
+ refScrollDesktop,
+ onObserverMounted,
+ messages,
+ loading,
+ setLoading,
+ setMessages,
+ fetchMessages,
+ mobileScrollContainer,
+ }
+}
@@ -0,0 +1 @@
+export * from './limits'
\ No newline at end of file
@@ -0,0 +1,7 @@
+export type LimitSize = 'small' | 'medium' | 'large'
+
+export interface Limit {
+ active: boolean
+ limit: number
+ firstLimit: number
+}
\ No newline at end of file
@@ -0,0 +1,2 @@
+export * from './model'
+export * from './types'
\ No newline at end of file
@@ -0,0 +1,3 @@
+
+
+export * from './use-images-library'
\ No newline at end of file
@@ -0,0 +1,4 @@
+export interface ImageWithState {
+ image: string
+ state: boolean
+}
\ No newline at end of file
@@ -0,0 +1,53 @@
+import { useEffect, useMemo, useState } from 'react'
+import { ImageWithState } from './types'
+
+export const useImagesLibrary = (images: string[], current: string | null, reverse: boolean) => {
+ const [imagesWithState, setImagesWithState] = useState(getImagesWithState(images))
+ const [initialCount, setInitialCount] = useState(0)
+
+ function getImagesWithState(images: string[]) {
+ const imagesWithState = images.map((image) => ({
+ image,
+ state: false,
+ }))
+
+ return imagesWithState
+ }
+
+ function updateImageState(image: string, state: boolean) {
+ setImagesWithState((prev) => prev.map((item) => (item.image === image ? { ...item, state } : item)))
+ }
+
+ const currentImageIndex = useMemo(
+ () => (!current ? undefined : imagesWithState.findIndex((image) => image.image === current)),
+ [imagesWithState, current]
+ )
+
+ useEffect(() => {
+ if (initialCount === images.length) {
+ return
+ }
+
+ setInitialCount(images.length)
+
+ const filteredMessages = images.filter((item) =>
+ !imagesWithState.find((i) => i.image === item) ? true : false
+ )
+
+ if (filteredMessages.length === images.length) {
+ return setImagesWithState(getImagesWithState(filteredMessages))
+ }
+
+ if (reverse) setImagesWithState([...imagesWithState, ...getImagesWithState(filteredMessages)])
+ else setImagesWithState([...getImagesWithState(filteredMessages), ...imagesWithState])
+ }, [images])
+
+ return {
+ imagesWithState,
+ currentImageIndex,
+ initialCount,
+ setInitialCount,
+ setImagesWithState,
+ updateImageState,
+ }
+}
@@ -0,0 +1,53 @@
+import { debounce } from 'lodash'
+import { useCallback, useEffect, useState } from 'react'
+import { Swiper as SwiperCore } from 'swiper'
+import { ImageWithState } from './types'
+
+export const useLibrarySwiper = (onSlideFalse: ((...args: any) => any) | undefined, reverse: boolean) => {
+ const [swiper, setSwiper] = useState(null)
+
+ const keydown = (e: KeyboardEvent) => {
+ if (e.key === 'ArrowRight') {
+ e.preventDefault()
+ slideNext()
+ }
+ if (e.key === 'ArrowLeft') {
+ e.preventDefault()
+ slidePrev()
+ }
+ }
+
+ const slidePrev = function () {
+ const result = swiper?.slidePrev()
+
+ if (!result && onSlideFalse && !reverse) {
+ onSlideFalse()
+ }
+ }
+
+ const slideNext = function () {
+ const result = swiper?.slideNext()
+
+ if (!result && onSlideFalse && reverse) {
+ onSlideFalse()
+ }
+ }
+
+ useEffect(() => {
+ document.addEventListener('keydown', keydown, true)
+ return () => {
+ document.removeEventListener('keydown', keydown, true)
+ }
+ }, [swiper])
+
+ // useEffect(() => {
+ // swiper?.slideTo(currentImageIndex || 0)
+ // }, [currentImageIndex])
+
+ return {
+ swiper,
+ setSwiper,
+ slidePrev,
+ slideNext,
+ }
+}
@@ -1,27 +1,62 @@
-import React, { Dispatch, SetStateAction } from 'react'
+import React, { Dispatch, SetStateAction, useEffect, useState } from 'react'
import Image from 'next/image'
import styles from './modal-styles.module.scss'
+import { ArrowDropDown } from '@mui/icons-material'
+import { c, Loader } from '@/src/shared'
+import { useImagesLibrary } from '../model'
+
+import { Swiper, SwiperSlide } from 'swiper/react'
+import 'swiper/css'
+import { Swiper as SwiperCore } from 'swiper'
+import { useLibrarySwiper } from '../model/use-swiper'
interface IProps {
modal: boolean
setModal: Dispatch>
- image: string
+ current: string | null
+ setCurrent?: (value: string | null) => void
+ onSlideFalse?: (...args: any) => any
+ reverse?: boolean
+ images: string[]
}
-export default function FullScreenModal({ modal, setModal, image }: IProps) {
- const isSvg = image.includes('.svg')
+
+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)
+
+ useEffect(() => {
+ if (!(initialCount < images.length && swiper)) return
+
+ if (reverse) {
+ setTimeout(() => swiper.slideNext(), 1000)
+ return
+ }
+
+ setTimeout(() => swiper.slideTo(images.length - initialCount - 1, 1000), 1000)
+ }, [images])
return (
setModal(false)}
>
- {!isSvg && image ? (
-
- ) : (
-

+
+ {modal && (
+
setSwiper(swiper)}
+ >
+ {imagesWithState.map(({ image, state }, index) => (
+
+ {!state && }
+ {!image.includes('.svg') ? (
+ e.stopPropagation()}
+ onLoadingComplete={() => {
+ updateImageState(image, true)
+ }}
+ loading='lazy'
+ src={image}
+ width={'1500'}
+ height={'1500'}
+ alt='К сожалению, изображение не загрузилось'
+ className={styles.image_style}
+ />
+ ) : (
+
e.stopPropagation()}
+ onLoad={() => updateImageState(image, true)}
+ alt='К сожалению, изображение не загрузилось'
+ className={styles.image_style}
+ />
+ )}
+
+ ))}
+
)}
+
)
@@ -0,0 +1 @@
+export { default as ImageModal } from './full-screen-modal'
@@ -0,0 +1,92 @@
+.close_block {
+ position: absolute;
+ z-index: 105;
+ cursor: pointer;
+ right: 25px;
+ top: 25px;
+}
+
+.arrow {
+ position: absolute;
+ z-index: 1205;
+ cursor: pointer;
+ top: 50%;
+
+ width: 50px;
+ height: 50px;
+
+ background: transparent;
+ border: none;
+ outline: none;
+
+ svg {
+ fill: white;
+ width: 50px;
+ height: 50px;
+ }
+
+ &_left {
+ transform: translateY(-50%) rotate(90deg);
+ left: -40px;
+ @media screen and (max-width: 768px) {
+ left: -3px;
+ }
+ }
+
+ &_right {
+ transform: translateY(-50%) rotate(-90deg);
+ right: -50px;
+
+ @media screen and (max-width: 768px) {
+ right: -10px;
+ }
+ }
+}
+
+.image_block {
+ position: absolute;
+ padding: 0 20px;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 105;
+ margin: auto;
+ width: fit-content;
+ height: fit-content;
+}
+
+.loader {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+}
+
+.image_style {
+ width: auto;
+ max-width: 70vw;
+ height: 100%;
+ object-fit: contain;
+
+ @media screen and (max-width: 1024px) {
+ width: auto;
+ max-width: 70vw;
+ height: auto;
+ }
+}
+
+.slide {
+ width: 80vw !important;
+ display: flex !important;
+ align-items: center;
+ justify-content: center;
+}
+
+.swiper {
+ position: relative;
+ width: 100%;
+ max-width: 80vw;
+ max-height: 1000px;
+ height: 90vh;
+}
@@ -0,0 +1 @@
+export * from './ui'
@@ -1,30 +0,0 @@
-.close_block{
- position: absolute;
- z-index: 105;
- cursor: pointer;
- right: 25px;
- top: 25px;
-}
-
-.image_block{
- position: absolute;
- padding: 0 20px;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- z-index: 105;
- margin: auto;
- width: fit-content;
- height: fit-content;
-}
-
-.image_style{
- position: relative;
- width: 100%;
- height: 100%;
- max-width: 960px;
- max-height: 700px;
- border-radius: 15px;
- object-fit: contain;
-}
\ No newline at end of file
@@ -4,7 +4,14 @@ import Link from 'next/link'
import styles from '@/src/shared/styles/chats-bot-pages.module.scss'
-export default function Title(props: any) {
+export interface TitleProps {
+ type: string
+ title: string
+ linkBack?: string
+ rightSlot?: React.ReactNode
+}
+
+export default function Title(props: TitleProps) {
return (
@@ -12,11 +19,19 @@ export default function Title(props: any) {
{' '}
{props.type} •{' '}
- {props.title}
+
+ {props.title}
+
-
- {props.title}
-
+
+
+ {props.title}
+
+
{props.rightSlot}
+
)
}
@@ -5,7 +5,7 @@ import axios from 'axios'
import { UniqInput } from '@/src/main/components/uniq_input'
import { useAppSelector } from '@/src/main/store/store'
import { ChatProps } from '@/src/shared/lib/types/model'
-import { ChatMessagesList } from '@/src/widgets/messages/chat-messages-list'
+import { ChatMessagesList } from '@/src/widgets/messages/ui/chat-messages-list'
import 'intro.js/introjs.css'
@@ -24,5 +24,5 @@
height: 25px;
border-radius: 7px;
border: var(--new-ui-ctrl-f-button-border);
- background: var(--new-ui-ctrl-f-button-bg);
+ background: var(--new-ui-ctrl-f-button-bg);
}
@@ -29,7 +29,14 @@ interface Props {
isLoader?: boolean
}
-export const Layout: React.FC = ({ children, device, isAuthPage = false, titlePage, title = titlePage, isLoader }) => {
+export const Layout: React.FC = ({
+ children,
+ device,
+ isAuthPage = false,
+ titlePage,
+ title = titlePage,
+ isLoader,
+}) => {
const { data: sessionData } = useSession()
const appState = useAppSelector((state) => state)
const dispatch = useAppDispatch()
@@ -81,7 +88,10 @@ export const Layout: React.FC = ({ children, device, isAuthPage = false,
targetDevice: device,
targetType: 'sidemenu',
})
- if (setting) setting?.value?.sidemenu_state === 'opened' ? setSidemenuDefaultOpen(true) : setSidemenuDefaultOpen(false)
+ if (setting)
+ setting?.value?.sidemenu_state === 'opened'
+ ? setSidemenuDefaultOpen(true)
+ : setSidemenuDefaultOpen(false)
}
}, [appState.settings.state])
@@ -17,10 +17,10 @@ export const store = configureStore({
balance: balanceSlice.reducer,
stepper: stepperSlice.reducer,
user: userSlice.reducer,
+ copy: copySlice.reducer,
notification: notificationSlice.reducer,
params: paramsStore.reducer,
settings: settingsSlice.reducer,
- copy: copySlice.reducer,
},
})
@@ -15,14 +15,21 @@
--border-color2: #e7e7e7;
--bg-audio: #f2f2fe;
- --new-ui-bg-app-color: #eff0f2;
- --new-ui-main-color: white;
- --new-ui-gray-color: #a4aab5;
- --new-ui-text-color: #2b2b42;
- --new-ui-border: 2px solid #eff0f2;
- --new-ui-btn-danger-bg: #ff23721a;
- --new-ui-ctrl-f-button-bg: #f9f9fc;
- --new-ui-ctrl-f-button-border: 1px solid #c4cbd8;
+ --new-ui-bg-app-color: #eff0f2;
+ --new-ui-main-color: white;
+ --new-ui-gray-color: #A4AAB5;
+ --new-ui-text-color: #2B2B42;
+ --new-ui-border: 2px solid #EFF0F2;
+ --new-ui-btn-danger-bg: #FF23721A;
+ --new-ui-ctrl-f-button-bg:#F9F9FC;
+ --new-ui-ctrl-f-button-border:1px solid #C4CBD8;
+
+ --copy-border: #EFF0F2;
+ --copy-color: #343437;
+
+ --cards-hover:#F9F9FF;
+ --choosen-tab: #FFFFFF;
+ --choosen-tab-color:#373737 ;
}
:root[data-theme='dark'] {
@@ -42,14 +49,21 @@
--border-color2: #2c2c2c;
--bg-audio: #303030;
- --new-ui-bg-app-color: #303035;
- --new-ui-border: 1px solid #40404e;
- --new-ui-main-color: #151518;
- --new-ui-gray-color: #a4aab5;
- --new-ui-text-color: white;
- --new-ui-btn-danger-bg: #ff23721a;
- --new-ui-ctrl-f-button-bg: #242428;
- --new-ui-ctrl-f-button-border: 1px solid #303035;
+ --new-ui-bg-app-color: #303035;
+ --new-ui-border: 1px solid #40404E;
+ --new-ui-main-color: #151518;
+ --new-ui-gray-color: #A4AAB5;
+ --new-ui-text-color: white;
+ --new-ui-btn-danger-bg: #FF23721A;
+ --new-ui-ctrl-f-button-bg:#242428;
+ --new-ui-ctrl-f-button-border:1px solid #303035;
+
+ --copy-border: #343437;
+ --copy-color:#EFF0F2;
+
+ --cards-hover:#303047;
+ --choosen-tab: #151518;
+ --choosen-tab-color: #FFFFFF;
}
* {
@@ -323,19 +337,24 @@ textarea {
}
.rdw-editor-toolbar {
- background-color: transparent !important;
- padding-bottom: 15px !important;
- border: none !important;
- border-bottom: 1px solid #eff0f2 !important;
+ background-color: transparent !important;
+ padding: 20px 0 !important;
+ border: none !important;
+ border-bottom: 1px solid var(--copy-border) !important;
+ border-top: 1px solid var(--copy-border) !important;
}
.rdw-dropdown-wrapper {
- background-color: transparent !important;
- border: 2px solid var(--new-ui-bg-app-color) !important;
- border-radius: 10px !important;
- padding: 10px !important;
- height: 36px !important;
- min-width: 40px !important;
+ background-color: transparent !important;
+ border: 2px solid var(--new-ui-bg-app-color) !important;
+ border-radius: 10px !important;
+ padding: 0 !important;
+ height: 36px !important;
+ min-width: 40px !important;
+}
+
+.rdw-dropdown-selectedtext{
+ padding: 0 16px 0 12px !important;
}
.rdw-dropdown-wrapper:hover {
@@ -352,22 +371,61 @@ textarea {
}
.rdw-dropdown-optionwrapper {
- width: 100% !important;
- margin-top: 15px !important;
- overflow: hidden;
- color: inherit !important;
- overflow-y: hidden !important;
+ border: none !important;
+ border-radius: 10px !important;
+ width: 100% !important;
+ margin-top: 12px !important;
+ overflow: hidden;
+ color: inherit !important;
+ background-color: var(--background-color-main) !important;
+ overflow-y: hidden !important;
}
.rdw-block-dropdown {
width: 150px !important;
}
.rdw-dropdown-optionwrapper > li {
- color: inherit !important;
+ color: var(--new-ui-text-color) !important;
+ padding: 0 16px 0 12px !important;
+}
+
+.rdw-dropdown-optionwrapper > li:hover{
+ background-color:var(--new-ui-gray-color) !important
+}
+
+.rdw-dropdownoption-active{
+ background: var(--new-ui-gray-color) !important
}
.rdw-dropdown-optionwrapper:hover {
- box-shadow: none;
- color: inherit !important;
+ border: none !important;
+ box-shadow: none !important;
+ color: inherit !important;
+}
+
+.rdw-dropdown-carettoclose{
+ border-radius: 5px !important;
+ border-bottom-color: var(--new-ui-gray-color) !important;
+}
+
+.rdw-dropdown-carettoopen{
+ border-radius: 5px !important;
+ border-top-color: var(--new-ui-gray-color) !important;
+}
+
+.rdw-text-align-wrapper{
+ margin: 0 !important;
+}
+
+.rdw-list-wrapper{
+ margin: 0 !important;
+}
+
+.rdw-history-wrapper{
+ margin: 0 !important;
+}
+
+.rdw-block-wrapper{
+ margin: 0 !important;
}
.border-bottom-1px-gray {
@@ -394,8 +452,9 @@ textarea {
}
.smallScroll::-webkit-scrollbar-track {
- background: initial;
- margin: 21px 0;
+ background: initial;
+ /*margin: 21px 0;*/
+ margin: 5px 0;
}
.smallScroll::-webkit-scrollbar-thumb {
@@ -413,8 +472,87 @@ textarea {
transition-duration: 250ms;
}
-.rotate-0 {
- transform: rotate(0deg);
- transition: all;
- transition-duration: 250ms;
+.rotate-0{
+ transform: rotate(0deg);
+ transition: all;
+ transition-duration: 250ms;
+}
+
+
+/*COPY*/
+.toolbarClassName{
+ align-items: center;
+ gap: 15px;
+}
+.wrapperClassName{
+
+}
+
+.editorClassName{
+ border: 1px solid transparent;
+ transition: border-color 0.3s;
+ cursor: text;
+}
+
+.editorClassName div:focus{
+ outline: none !important;
+ border-color: transparent !important;
+}
+
+.editorClassName div .public-DraftStyleDefault-block{
+ display: inline-block;
+ padding:0 1px;
+ margin: 0.5em 0 !important;
+}
+
+.public-DraftEditor-content{
+ overflow-y: scroll;
+ max-height: calc(75vh - 200px) ;
+
+ @media (max-width: 768px) {
+ max-height: calc(70vh - 200px) ;
+ }
+
+}
+
+
+.public-DraftEditor-content::-webkit-scrollbar{
+ height: 5px;
+ width: 2px;
+}
+
+.public-DraftEditor-content::-webkit-scrollbar-track {
+ background: initial;
+ margin: 21px 0;
+}
+
+.public-DraftEditor-content::-webkit-scrollbar-thumb {
+ background-color: rgba(217, 217, 217, 0.49);
+ border-radius: 5px;
+}
+
+.inline{
+ gap:3px;
+ margin: 0 !important;
+}
+
+.inline-btn{
+ width: 12px;
+ height: 25px !important;
+ margin: 0 !important;
+ padding: 0 !important;
+}
+
+.rdw-option-active{
+ background: rgba(229, 229, 229, 0.18) !important;
+ -webkit-box-shadow: inset 0 0 5px #c1c1c1 !important;
+ -moz-box-shadow: inset 0 0 5px #c1c1c1 !important;
+ box-shadow: inset 0 0 5px #c1c1c1 !important;
+ outline: none !important;
+}
+
+.copy-color{
+ color:var(--copy-color);
+ border-color: var(--copy-border);
+
}
@@ -0,0 +1,8 @@
+interface ApiKeyDTO {
+ created_at: string // ISO 8601 format
+ name: string
+ key: string
+ expires_at: string | null
+ user: User
+ token_limit: string
+}
@@ -0,0 +1,37 @@
+interface UserDTO {
+ uid: string
+ first_name: string
+ last_name: string
+ username: string
+ created_at: string // ISO 8601 format
+ email: string
+ is_active: boolean
+ is_superuser: boolean
+ is_staff: boolean
+ is_confirmed: boolean
+ is_subscribed_to_emails: boolean
+ show_balance: boolean
+ profile_picture_link: string
+ account_type: string
+ token: {
+ access: string
+ refresh: string
+ }
+ payment_plan: {
+ uid: string
+ plan: {
+ uid: string
+ title: string
+ price: string
+ tokens_per_plan: string
+ duration: string
+ accessed_models: string[]
+ }
+ last_payment_at: string // ISO 8601 date
+ next_payment_at: string // ISO 8601 date
+ current_token_balance: number
+ }
+ referral_code: string | null
+ is_social: boolean
+ social_auth: any[] // Assuming it can hold any type of objects
+}
@@ -62,6 +62,7 @@ const Page: React.FC = ({ deviceType, deviceOs }) => {
isTryRename,
setIsTryRename,
} = useChats(modelType)
+
const { messages, sendMessage, loading, getMessagesPagination, deleteMessage } = useModel(
currentChat,
showError,
@@ -71,14 +72,18 @@ const Page: React.FC = ({ deviceType, deviceOs }) => {
const includeParams = useAppSelector((state) => state.params.params)
const dispatch = useDispatch()
+ const { push } = useRouter()
+
const deleteMessageMemo = useCallback(deleteMessage, [currentChat, messages])
React.useEffect(() => {
if (data?.access) {
model_api.getBotParams(router.asPath.split('/')[2], data.access).then((res) => {
+ if (!res.title) return push('/404')
+
setBotParams(res)
setModelType(res.slug)
- if (res.versions.length !== 0) {
+ if (res.versions && res.versions.length !== 0) {
setVersion(res.versions[0].slug)
dispatch(
setParametres(
@@ -91,7 +96,7 @@ const Page: React.FC = ({ deviceType, deviceOs }) => {
)
)
)
- } else {
+ } else if (res.parameters) {
setVersion('')
dispatch(
setParametres(
@@ -211,7 +216,7 @@ const Page: React.FC = ({ deviceType, deviceOs }) => {
title={botParams?.title}
>
-
+
= ({ deviceType, deviceOs }) => {
createNewChat={createNewChat}
handleClickChatSetting={handleClickChatSetting}
/>
- {desktop && (
-
- {botParams?.versions && botParams.versions?.length !== 0 && (
- <>
-
- ВЕРСИИ
-
-
- >
- )}
+ {desktop && botParams && (
+
+ {botParams?.versions &&
+ botParams.versions?.length !== 0 && (
+ <>
+
+ ВЕРСИИ
+
+
+ >
+ )}
{botParams && botParams.parameters?.length > 0 && (
= ({ deviceType, deviceOs }) => {
onClose={hideMobileSettings}
>
- {botParams?.versions && botParams.versions?.length !== 0 && (
- <>
-
- ВЕРСИИ
-
-
- >
- )}
+ {botParams?.versions &&
+ botParams.versions?.length !== 0 && (
+ <>
+
+ ВЕРСИИ
+
+
+ >
+ )}
{botParams && botParams.parameters?.length > 0 ? (
<>
{
const deviceType = getTypeDevice(context)
@@ -41,237 +49,74 @@ export async function getServerSideProps(context: any): Promise<{ props: any }>
}
const Images: React.FC = ({ deviceType, deviceOs }) => {
- const [image, setImage] = React.useState(null)
- const desktop = deviceType === 'desktop'
- const ios = deviceOs === 'ios'
- const { error, showError } = useShowData()
- const [openFiltersMobile, setOpenFiltersMobile] = React.useState(false)
- const router = useRouter()
- const { data } = useSession()
- const [botParams, setBotParams] = React.useState(null)
- const [version, setVersion] = React.useState('')
- const [modelType, setModelType] = React.useState('')
- const [params, setParams] = React.useState(false)
- 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)
+ const { query } = useRouter()
- React.useEffect(() => {
- model_api
- .getBotParams(router.asPath.split('/')[2], data?.access)
- .then((res) => {
- setBotParams(res)
- setModelType(res.slug)
- if (res.versions.length !== 0) {
- setVersion(res.versions[0].slug)
- dispatch(
- setParametres(
- res.parameters.reduce(
- (a, v) =>
- v.versions.includes(res.versions[0].slug)
- ? { ...a, [v.key]: v.values.default }
- : { ...a },
- {}
- )
- )
- )
- } else {
- setVersion('')
- dispatch(
- setParametres(
- res.parameters.reduce(
- (a, v) => ({ ...a, [v.key]: v.values.default }),
- {}
- )
- )
- )
- }
- })
- .catch((err) => {})
- }, [data?.access, router.query])
-
- const onLoadImage = (event: React.ChangeEvent) => {
- if (event.target.files) {
- setImage(event.target.files[0])
- // showError('Файл успешно загружен, можете отправлять его!')
- }
- }
-
- const viewMobileSettings = () => {
- setOpenFiltersMobile(true)
- }
+ const {
+ botParams,
+ version,
+ modelType,
+ fetchBotParams,
+ resetParams,
+ setDefaultParams,
+ setVersion,
+ } = useImageBot(query.slug as string)
- const hideMobileSettings = () => {
- setOpenFiltersMobile(false)
- }
+ const { ios, desktop } = useThemeAndDevice(deviceType, deviceOs)
- const resetParams = () => {
- if (botParams) {
- dispatch(setParametres({}))
- if (botParams.versions.length !== 0) {
- setVersion(botParams.versions[0].slug)
- dispatch(
- setParametres(
- botParams.parameters.reduce(
- (a, v) =>
- v.versions.includes(botParams.versions[0].slug)
- ? { ...a, [v.key]: v.values.default }
- : { ...a },
- {}
- )
- )
- )
- } else {
- setVersion(botParams.slug)
- dispatch(
- setParametres(
- botParams.parameters.reduce(
- (a, v) => ({ ...a, [v.key]: v.values.default }),
- {}
- )
- )
- )
- }
- }
- }
+ const { error, showError } = useShowData()
- const setDefaultParams = () => {
- if (botParams) {
- dispatch(setParametres({}))
- if (version !== '') {
- dispatch(
- setParametres(
- botParams.parameters.reduce(
- (a, v) =>
- v.versions.includes(version)
- ? { ...a, [v.key]: v.values.default }
- : { ...a },
- {}
- )
- )
- )
- } else {
- dispatch(
- setParametres(
- botParams.parameters.reduce(
- (a, v) => ({ ...a, [v.key]: v.values.default }),
- {}
- )
- )
- )
- }
- }
- }
+ const router = useRouter()
- const onCreateImage = (input: string, required: (string | null)[]) => {
- if (required.includes('text') && (input === '' || input === null)) {
- showError('Введите сообщение!')
- return false
- }
- if (required.includes('image') && image === null) {
- showError('Прикрепите изображение!')
- return false
- }
- if (required.includes('zip') && image === null) {
- showError('Прикрепите архив!')
- return false
- }
- let data = {}
- if (version === '') {
- data = {
- ...includeParams,
- }
- } else {
- data = {
- version: version,
- ...includeParams,
- }
- }
+ const { data: session } = useSession()
- createImage({
- content: input,
- file: image,
- info: {
- ...data,
- },
- })
- return true
- }
+ const { openFiltersMobile, setOpenFiltersMobile, params, setParams, includeParams } =
+ useImagesBotFilters()
- React.useEffect(() => {
- if (isComplete) {
- if (!desktop) {
- const block = refScrollMobile.current
- if (block) {
- //@ts-ignore
- block.scrollTop = block.scrollHeight
- }
- } else {
- window.scroll(0, 0)
- }
- }
- }, [isComplete])
+ const {
+ refScrollMobile,
+ refScrollDesktop,
+ mobileScrollContainer,
+ onObserverMounted,
+ setLoading,
+ setMessages,
+ fetchMessages,
+ loading,
+ messages,
+ } = useImageBotPagination(deviceType)
- const handleMobileScroll = () => {
- setScrollBottom(refScrollMobile.current?.scrollHeight - refScrollMobile.current?.scrollTop - refScrollMobile.current?.clientHeight)
+ const { createImage, isComplete } = useImageBotCreateImage(
+ showError,
+ modelType,
+ deviceType,
+ setLoading,
+ setMessages
+ )
- if (refScrollMobile.current && messages?.length !== 0) {
- const { scrollTop, scrollHeight, clientHeight } = refScrollMobile.current
- if (scrollTop === 0) {
- if (getMessagesPagination) {
- setIsPaginating(true)
- getMessagesPagination(deviceType)
- }
- }
- }
- }
+ const { onCreateImage, onLoadImage, image, setImage } = useImagesUniqInput(
+ version,
+ includeParams,
+ createImage
+ )
- const handleScroll = () => {
- if (window.scrollY + window.innerHeight >= document.documentElement.scrollHeight) {
- setIsPaginating(true)
- getMessagesPagination(deviceType)
- }
+ async function onFetch() {
+ await Promise.all([fetchBotParams()])
}
- React.useEffect(() => {
- window.addEventListener('scroll', handleScroll)
- return () => {
- window.removeEventListener('scroll', handleScroll)
- }
- }, [])
-
- React.useEffect(() => {
- const block = deviceType === 'desktop' ? window : refScrollMobile.current
- if (block) {
- 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)
- }
- }
- setIsPaginating(false)
- }, [messages])
+ useEffect(() => {
+ onFetch()
+ onObserverMounted()
+ }, [session, router.query])
return (
-
+
+ }
+ title={'Модель'}
+ type={'Изображения'}
+ linkBack={'/images'}
+ />
+
= ({ deviceType, deviceOs }) => {
imageLoad={onLoadImage}
sendMessage={onCreateImage}
unpinImage={() => setImage(null)}
- viewMobileSettings={viewMobileSettings}
+ viewMobileSettings={() =>
+ setOpenFiltersMobile(true)
+ }
/>
)}
-
- >
- ) : (
-
- {scrollBottom > 500 && (
{
- const block = refScrollMobile.current
-
- block.scrollTo({
- top: block.scrollHeight,
- behavior: 'smooth', // добавляем плавную прокрутку
- })
- }}
- >
-
-
- )}
+ ref={refScrollDesktop}
+ >
+
+ >
+ ) : (
+
= ({ deviceType, deviceOs }) => {
: 'calc(200px + (400 - 200) * ((100vh - 400px) / (600 - 400)))',
overflowY: 'scroll',
overflowX: 'hidden',
+ position: 'relative',
}}
+ ref={mobileScrollContainer}
className={'smallScroll'}
- onScroll={handleMobileScroll}
>
+
@@ -379,7 +217,9 @@ const Images: React.FC = ({ deviceType, deviceOs }) => {
imageLoad={onLoadImage}
sendMessage={onCreateImage}
unpinImage={() => setImage(null)}
- viewMobileSettings={viewMobileSettings}
+ viewMobileSettings={() =>
+ setOpenFiltersMobile(true)
+ }
/>
)}
@@ -465,7 +305,7 @@ const Images: React.FC = ({ deviceType, deviceOs }) => {
/>
setOpenFiltersMobile(false)}
reset={resetParams}
/>
@@ -482,7 +322,10 @@ const Images: React.FC = ({ deviceType, deviceOs }) => {
)}
)}
-
+ setOpenFiltersMobile(false)}
+ >
{botParams?.versions && botParams.versions.length !== 0 ? (
<>
@@ -525,7 +368,7 @@ const Images: React.FC = ({ deviceType, deviceOs }) => {
params={botParams?.parameters}
/>
setOpenFiltersMobile(false)}
desktop={desktop}
reset={resetParams}
/>
@@ -19,6 +19,8 @@ axios.defaults.httpsAgent = new https.Agent({
rejectUnauthorized: false,
})
+axios.defaults.validateStatus = (status) => status < 500
+
const inter = Raleway({ subsets: ['latin'] })
function App({ Component, pageProps: { session, ...pageProps } }: AppProps) {
@@ -31,6 +31,7 @@ export default function Document() {
+