@@ -7,15 +7,23 @@ export interface IShortModel { slug: string image: string blocked: boolean + tags: IModelTag[] actual_stat: { generation_time: string tokens_cost: string } } +export interface IModelTag { + title: string + icon: string + color: string +} + export interface IModel { uid: string title: string + blocked: boolean description: string slug: string image: string @@ -23,6 +31,7 @@ export interface IModel { parameters: IModelParams[] versions: IModelVersions[] inputs: IModelInputs[] + tags: IModelTag[] } export interface IModelParams { name: string @@ -4,19 +4,34 @@ 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 ( - + {' '} {props.type} •{' '} -  {props.title} + +  {props.title} + - - {props.title} - +
+ + {props.title} + +
{props.rightSlot}
+
) } @@ -30,6 +30,18 @@ } } +@keyframes fadein { + 0% { + opacity: 0; + } + 50% { + opacity: 0; + } + 100% { + opacity: 1; + } +} + .blocked { display: flex; align-items: center; @@ -39,14 +51,40 @@ right: 20px; background-color: white; border-radius: 100px; - padding-right: 20px; - padding: 8px 12px; + // padding-right: 20px; + width: 50px; + height: 50px; + transition: width 0.3s ease-in-out; - span { + &__icon { + } + + &:hover { + width: 60%; + padding: 12px; + // height: unset; + + .blocked__icon { + animation: fadein 0.6s ease-in-out; + } + + .blocked__text { + opacity: 1; + position: static; + animation: fadein 0.6s ease-in-out; + display: block; + } + } + + &__text { + position: absolute; color: #ff2372; font-weight: 500; padding-left: 10px; font-size: 14px; + opacity: 0; + display: none; + min-width: max-content !important; } } @@ -7,6 +7,9 @@ import BlockedSvg from '@/src/assets/svg/blocked.svg?react' import styles from './card.module.scss' import { useThemeAndDevice } from '@/src/shared/lib/hooks' +import { c } from '@/src/shared/lib/helpers' +import { IModelTag } from '@/src/entities/model-entity' +import { SvgIcon } from '@/src/shared/ui/svg' export type CardProps = { title: string @@ -15,10 +18,11 @@ export type CardProps = { uid: string blocked?: boolean slug: string + tags?: IModelTag[] accessed_models?: string[] | null } -const ChatCard = ({ text, icon, title, uid, slug, accessed_models, blocked }: CardProps) => { +const ChatCard = ({ text, icon, title, uid, slug, accessed_models, blocked, tags }: CardProps) => { const link = useMemo(() => { return accessed_models && !accessed_models.includes(slug) ? '/account?scope=subscribe' @@ -41,17 +45,51 @@ const ChatCard = ({ text, icon, title, uid, slug, accessed_models, blocked }: Ca {text}
+ {tags && + tags.map((tag, index) => ( +
+ + + + {tag.title} + +
+ ))} + {blocked ? (
- + Модель недоступна
@@ -23,6 +23,7 @@ function Chat({ deleteMessage, modelTitle, currentVersion, + blocked, deviceOs, }: ChatProps) { const desktop = device === 'desktop' @@ -98,6 +99,7 @@ function Chat({ desktop={desktop} sendMessage={sendMessage} image={file} + blocked={blocked} unpinImage={clearImage} imageLoad={onLoadImage} /> @@ -26,6 +26,7 @@ interface Input { // setImage: React.Dispatch> styles: 'images' | 'chats' | 'audio' input_types?: IModelInputs[] + blocked?: boolean viewMobileSettings: () => void currentVersion: string resendValue?: string @@ -43,6 +44,7 @@ export const UniqInput: FC = ({ viewMobileSettings, currentVersion, resendValue, + blocked, }: Input) => { const theme = useAppSelector((state) => state.theme.theme) const [disabled, setDisabled] = React.useState(true) @@ -94,7 +96,7 @@ export const UniqInput: FC = ({ = ({ }, '& ::placeholder': { fontSize: '16px', + color: theme === 'light' ? '#bbbbbb !important' : 'white !important', }, '& .Mui-disabled': { - webkitTextFillColor: - theme === 'light' ? '#bbbbbb !important' : '#646464 !important', + WebkitTextFillColor: theme === 'light' ? '#bbbbbb !important' : '#7d7d7d !important', fontSize: '16px', letterSpacing: '0.05px', fontWeight: '600', @@ -154,7 +156,7 @@ export const UniqInput: FC = ({ } }} // onPaste={(e:ClipboardEvent) => handlePaste(e)} - value={disabled ? 'Ввод текста недоступен для этой модели' : value} + value={disabled || blocked ? 'Ввод текста недоступен для этой модели' : value} onChange={(e) => { setValue(e.target.value) }} @@ -191,7 +193,7 @@ export const UniqInput: FC = ({ /> )} - {!disabled && ( + {!disabled && !blocked && ( { const deviceType = getTypeDevice(context) @@ -50,6 +55,8 @@ const Page: React.FC = ({ deviceType, deviceOs }) => { const { data } = useSession() const router = useRouter() + const { theme } = useThemeAndDevice() + const { chats, currentChat, @@ -211,7 +218,97 @@ const Page: React.FC = ({ deviceType, deviceOs }) => { title={botParams?.title} > - + <Box sx={{ display: 'flex', alignItems: 'center', gap: '10px' }}> + <Title + title={botParams?.title ? botParams?.title : ''} + rightSlot={ + <Box + sx={{ + display: 'flex', + gap: '10px', + width: '100%', + alignItems: 'center', + overflowX: 'scroll', + paddingTop: deviceType === 'mobile' ? '10px' : '0px', + }} + > + {botParams?.blocked && ( + <div + className={styles.blocked} + style={{ + background: + theme === 'light' + ? '#FF2372' + : '#FFFFFF', + }} + > + <BlockedSvg + width={23} + height={23} + color={ + theme === 'light' + ? '#FFFFFF' + : '#FF2372' + } + /> + <span + style={{ + color: + theme === 'light' + ? '#FFFFFF' + : '#FF2372', + }} + > + Модель недоступна + </span> + </div> + )} + + {botParams && + botParams.tags.map((tag) => ( + <div + className={styles.blocked} + style={{ + background: + theme === 'light' + ? tag.color + : '#FFFFFF', + + color: + theme === 'light' + ? '#FFFFFF' + : tag.color, + }} + > + <SvgIcon + color={ + theme === 'light' + ? '#FFFFFF' + : tag.color + } + width={23} + height={23} + url={tag.icon} + /> + + <span + style={{ + color: + theme === 'light' + ? '#FFFFFF' + : tag.color, + }} + > + {tag.title} + </span> + </div> + ))} + </Box> + } + type={'Чат-боты'} + linkBack={'/chat-bot'} + /> + </Box> <Box className={styles.main}> <Box className={styles.chatWindow}> <AllChatWindow @@ -219,6 +316,7 @@ const Page: React.FC<any> = ({ deviceType, deviceOs }) => { input_types={botParams?.inputs} setFile={setFile} file={file} + blocked={botParams?.blocked} getMessagesPagination={getMessagesPagination} sendMessage={onSendMessage} deleteMessage={deleteMessageMemo} @@ -247,27 +345,31 @@ const Page: React.FC<any> = ({ deviceType, deviceOs }) => { handleClickChatSetting={handleClickChatSetting} /> {desktop && ( - <Stack className='pd-30 bg-color-block border-radius-main' spacing={2}> - {botParams?.versions && botParams.versions?.length !== 0 && ( - <> - <Typography - sx={{ - color: '#A4AAB5', - fontWeight: '600', - fontSize: '14px', - letterSpacing: '0.1px', - }} - > - ВЕРСИИ - </Typography> - <ChatSelect - setDefaultParams={setDefaultParams} - value={version} - list={botParams.versions} - setValue={setVersion} - /> - </> - )} + <Stack + className='pd-30 bg-color-block border-radius-main' + spacing={2} + > + {botParams?.versions && + botParams.versions?.length !== 0 && ( + <> + <Typography + sx={{ + color: '#A4AAB5', + fontWeight: '600', + fontSize: '14px', + letterSpacing: '0.1px', + }} + > + ВЕРСИИ + </Typography> + <ChatSelect + setDefaultParams={setDefaultParams} + value={version} + list={botParams.versions} + setValue={setVersion} + /> + </> + )} {botParams && botParams.parameters?.length > 0 && ( <Box display={'flex'} @@ -344,27 +446,28 @@ const Page: React.FC<any> = ({ deviceType, deviceOs }) => { onClose={hideMobileSettings} > <Stack spacing={1} padding={2.4}> - {botParams?.versions && botParams.versions?.length !== 0 && ( - <> - <Typography - sx={{ - color: '#A4AAB5', - fontWeight: '600', - fontSize: '14px', - letterSpacing: '0.1px', - margin: '20px 0px 0px !important', - }} - > - ВЕРСИИ - </Typography> - <ChatSelect - setDefaultParams={setDefaultParams} - value={version} - list={botParams.versions} - setValue={setVersion} - /> - </> - )} + {botParams?.versions && + botParams.versions?.length !== 0 && ( + <> + <Typography + sx={{ + color: '#A4AAB5', + fontWeight: '600', + fontSize: '14px', + letterSpacing: '0.1px', + margin: '20px 0px 0px !important', + }} + > + ВЕРСИИ + </Typography> + <ChatSelect + setDefaultParams={setDefaultParams} + value={version} + list={botParams.versions} + setValue={setVersion} + /> + </> + )} {botParams && botParams.parameters?.length > 0 ? ( <> <Typography @@ -52,6 +52,7 @@ const Page: React.FC<any> = ({ deviceType }) => { accessed_models={user.payment_plan.plan.accessed_models} uid={item.uid} key={idx} + tags={item.tags} blocked={item.blocked} title={item.title} icon={item.image} @@ -50,6 +50,7 @@ export const Images: React.FC<any> = ({ deviceType }) => { uid={item.uid} key={idx} slug={item.slug} + tags={item.tags} blocked={item.blocked} text={item.description} title={item.title} @@ -1,8 +1,7 @@ import axios from 'axios' -import { IModel } from '@/src/shared/api/models/models' import { API_URL } from '@/src/shared/lib/constants' -import { IShortModel } from '@/src/entities/model-entity' +import { IModel, IShortModel } from '@/src/entities/model-entity' const model_api = { async getBots(token?: string): Promise<IShortModel[]> { @@ -29,6 +29,18 @@ } } +@keyframes fadein { + 0% { + opacity: 0; + } + 50% { + opacity: 0; + } + 100% { + opacity: 1; + } +} + .blocked { display: flex; align-items: center; @@ -38,16 +50,43 @@ right: 20px; background-color: white; border-radius: 100px; - padding-right: 20px; - padding: 8px 12px; + // padding-right: 20px; + width: 50px; + height: 50px; + transition: width 0.3s ease-in-out; - span { + &__icon { + } + + &:hover { + width: 60%; + padding: 12px; + // height: unset; + + .blocked__icon { + animation: fadein 0.6s ease-in-out; + } + + .blocked__text { + opacity: 1; + position: static; + animation: fadein 0.6s ease-in-out; + display: block; + } + } + + &__text { + position: absolute; color: #ff2372; font-weight: 500; padding-left: 10px; font-size: 14px; + opacity: 0; + display: none; + min-width: max-content !important; } } + .locked { display: flex; align-items: center; @@ -7,6 +7,9 @@ import BlockedSvg from '@/src/assets/svg/blocked.svg?react' import styles from './card.module.scss' import { useThemeAndDevice } from '../lib/hooks' +import { IModelTag } from '@/src/entities/model-entity' +import { SvgIcon } from '../ui/svg' +import { c } from '../lib/helpers' interface IProps { text: string @@ -14,11 +17,12 @@ interface IProps { title: string slug: string uid: string + tags?: IModelTag[] blocked: boolean accessed_models?: string[] | null } -export const Card = ({ text, icon, title, blocked, slug, accessed_models }: IProps) => { +export const Card = ({ text, icon, title, blocked, slug, accessed_models, tags }: IProps) => { const link = useMemo(() => { return accessed_models && !accessed_models.includes(slug) ? '/account?scope=subscribe' @@ -45,17 +49,51 @@ export const Card = ({ text, icon, title, blocked, slug, accessed_models }: IPro <Typography className={styles.text}>{text}</Typography> </Box> + {tags && + tags.map((tag, index) => ( + <div + key={index} + className={styles.blocked} + style={{ + background: theme === 'light' ? tag.color : '#FFFFFF', + color: theme === 'light' ? '#FFFFFF' : tag.color, + marginRight: blocked ? (index + 1) * 60 : 0, + }} + > + <SvgIcon + className={styles.blocked__icon} + color={theme === 'light' ? '#FFFFFF' : tag.color} + width={23} + height={23} + url={tag.icon} + /> + + <span + className={c(styles.blocked__text)} + style={{ + color: theme === 'light' ? '#FFFFFF' : tag.color, + }} + > + {tag.title} + </span> + </div> + ))} + {blocked ? ( <div className={styles.blocked} style={{ background: theme === 'light' ? '#FF2372' : '#FFFFFF' }} > <BlockedSvg + className={styles.blocked__icon} width={23} height={23} color={theme === 'light' ? '#FFFFFF' : '#FF2372'} /> - <span style={{ color: theme === 'light' ? '#FFFFFF' : '#FF2372' }}> + <span + className={c(styles.blocked__text)} + style={{ color: theme === 'light' ? '#FFFFFF' : '#FF2372' }} + > Модель недоступна </span> </div> @@ -33,6 +33,7 @@ export type ChatProps<T> = { deviceOs?: DeviceOs modelType: string messages: Message[] + blocked?: boolean sendMessage: (message: string, required: (string | null)[]) => boolean openMobileFilters: () => void setting?: T @@ -1,44 +1,68 @@ .main { - width: 100%; - display: flex; - justify-content: flex-start; - gap: 20px; - margin-bottom: 24px; - - .chatWindow { - width: 70%; - } - - .settings { - width: 25%; - } - - @media (max-width:768px) { - width: 100%; - display: flex; - - flex-direction: column-reverse; - - .chatWindow { - width: 100%; - } - - .settings { - width: 100%; - } - } + width: 100%; + display: flex; + justify-content: flex-start; + gap: 20px; + margin-bottom: 24px; + + .chatWindow { + width: 70%; + } + + .settings { + width: 25%; + } + + @media (max-width: 768px) { + width: 100%; + display: flex; + + flex-direction: column-reverse; + + .chatWindow { + width: 100%; + } + + .settings { + width: 100%; + } + } } -.wrapModelTitle{ - margin-bottom: 20px; - margin-top: 15px; - @media (max-width:768px) { - margin: 0; - margin-top: 15px; - } - .bigTitle { - @media (max-width:768px) { - display: none; - overflow: hidden; - } - } +.wrapModelTitle { + margin-bottom: 20px; + margin-top: 15px; + @media (max-width: 768px) { + margin: 0; + margin-top: 15px; + } + .bigTitle { + @media (max-width: 768px) { + display: none; + overflow: hidden; + } + } } + + +.blocked { + display: flex; + align-items: center; + justify-content: center; + background-color: white; + border-radius: 100px; + padding-right: 20px; + padding: 8px 12px; + width: max-content; + + span { + color: #ff2372; + font-weight: 500; + padding-left: 10px; + font-size: 14px; + width: max-content; + } +} + +.icon{ + +} \ No newline at end of file @@ -0,0 +1,35 @@ +import axios from 'axios' +import { useState, useEffect, HtmlHTMLAttributes } from 'react' + +export interface SvgIconProps extends HtmlHTMLAttributes<HTMLDivElement> { + width?: string | number + height?: string | number + color?: string + url: string +} + +export function SvgIcon({ url, color, width, height, ...props }: SvgIconProps) { + const [svgContent, setSvgContent] = useState<string>('') + + async function onFetch() { + const response = await axios.get(url) + + if (response.status === 200) { + setSvgContent(response.data) + } + } + + useEffect(() => { + onFetch() + }, []) + + return ( + <div + {...props} + style={{ minWidth: width, minHeight: height }} + dangerouslySetInnerHTML={{ + __html: `${svgContent}`, + }} + ></div> + ) +} @@ -0,0 +1 @@ +export * from './dynamic-svg' \ No newline at end of file @@ -0,0 +1 @@ +export * from './ui' \ No newline at end of file @@ -15,3 +15,26 @@ z-index: 100; } } + +.blocked { + display: flex; + align-items: center; + justify-content: center; + background-color: white; + border-radius: 100px; + padding-right: 20px; + padding: 8px 12px; + width: max-content; + + span { + color: #ff2372; + font-weight: 500; + padding-left: 10px; + font-size: 14px; + width: max-content; + } +} + +.icon{ + +} \ No newline at end of file @@ -20,8 +20,10 @@ import { useRouter } from 'next/router' import { useEffect } from 'react' import { c, getDeviceType, getOs } from '@/src/shared/lib/helpers' import { Device, DeviceOs } from '@/src/shared/lib/types/entities' +import BlockedSvg from '@/src/assets/svg/blocked.svg?react' import styles from './image-model.module.scss' +import { SvgIcon } from '@/src/shared/ui/svg' export interface ImageModelPageProps { deviceType: Device @@ -41,7 +43,7 @@ const ImageModelPage = ({ deviceType, deviceOs }: ImageModelPageProps) => { setVersion, } = useImageBot(query.slug as string) - const { ios, desktop } = useThemeAndDevice(deviceType, deviceOs) + const { ios, desktop, theme } = useThemeAndDevice(deviceType, deviceOs) const { error, showError } = useShowData() @@ -84,7 +86,7 @@ const ImageModelPage = ({ deviceType, deviceOs }: ImageModelPageProps) => { useEffect(() => { if (!session) return - console.log('useEffect') + console.log('useEffect') onFetch() onObserverMounted() }, [session?.access]) @@ -96,6 +98,84 @@ const ImageModelPage = ({ deviceType, deviceOs }: ImageModelPageProps) => { title={botParams ? botParams.title : 'Загрузка...'} type={'Изображения'} linkBack={'/images'} + rightSlot={ + <Box + sx={{ + display: 'flex', + gap: '10px', + width: '100%', + alignItems: 'center', + overflowX: 'scroll', + paddingTop: deviceType === 'mobile' ? '10px' : '0px', + }} + > + {botParams?.blocked && ( + <div + className={styles.blocked} + style={{ + background: + theme === 'light' ? '#FF2372' : '#FFFFFF', + }} + > + <BlockedSvg + width={23} + height={23} + color={theme === 'light' ? '#FFFFFF' : '#FF2372'} + /> + <span + style={{ + color: + theme === 'light' + ? '#FFFFFF' + : '#FF2372', + }} + > + Модель недоступна + </span> + </div> + )} + + {botParams && + botParams.tags.map((tag) => ( + <div + className={styles.blocked} + style={{ + background: + theme === 'light' + ? tag.color + : '#FFFFFF', + + color: + theme === 'light' + ? '#FFFFFF' + : tag.color, + }} + > + <SvgIcon + color={ + theme === 'light' + ? '#FFFFFF' + : tag.color + } + width={23} + height={23} + url={tag.icon} + /> + + <span + style={{ + color: + theme === 'light' + ? '#FFFFFF' + : tag.color, + }} + > + {tag.title} + </span> + </div> + ))} + </Box> + } /> </Box> <Box