Binary files /dev/null and b/public/voice-clone/add-voice.png differ Binary files /dev/null and b/public/voice-clone/message-input-mobile.png differ Binary files /dev/null and b/public/voice-clone/message-preview-mobile.png differ Binary files /dev/null and b/public/voice-clone/my-generations.png differ Binary files /dev/null and b/public/voice-clone/parameters-panel.png differ Binary files /dev/null and b/public/voice-clone/step-1.png differ Binary files /dev/null and b/public/voice-clone/step-2.png differ Binary files /dev/null and b/public/voice-clone/voice-selector-mobile.png differ @@ -0,0 +1,3 @@ +.card { + background: linear-gradient(to top left, rgba(42, 40, 188, 0.2), transparent 52%), #1a1a1e; +} @@ -0,0 +1,27 @@ +import * as React from 'react' +import Image from 'next/image' + +import shell from './voice-clone-card-shell.module.scss' +import fig from './voice-clone-figure.module.scss' +import styles from './add-voices-card.module.scss' + +export function AddVoicesCard() { + return ( +
+

+ Добавляйте новые голоса +

+
+
+ Карточка добавления голоса +
+
+
+ ) +} @@ -0,0 +1,96 @@ +.grid { + display: grid; + grid-template-columns: 203px 78px 405px; + grid-template-rows: auto auto; + row-gap: 16px; + align-items: start; +} + +.cellFigure1 { + grid-column: 1; + grid-row: 1; + align-self: center; +} + +.cellArrow { + grid-column: 2; + grid-row: 1; + place-self: center; +} + +.cellFigure2 { + grid-column: 3; + grid-row: 1; + align-self: center; +} + +.cellText1 { + grid-column: 1; + grid-row: 2; + min-width: 0; +} + +.cellText2 { + grid-column: 3; + grid-row: 2; + min-width: 0; +} + +.stepFigure { + margin: 0; + border-radius: 12px; + overflow: hidden; + line-height: 0; + background: rgba(0, 0, 0, 0.2); +} + +.stepFigureNarrow { + width: 203px; + height: 146px; +} + +.stepFigureWide { + width: 405px; + height: 148px; +} + +.stepImage { + display: block; + width: 100%; + height: 100%; + object-fit: contain; +} + +.arrowWrap { + display: flex; + align-items: center; + justify-content: center; + color: #a4aab5; + + svg { + display: block; + } +} + +.stepTitle { + margin: 0; + font-size: 20px; + font-weight: 700; + line-height: 1.2; + letter-spacing: -0.01em; + color: #fff; +} + +.stepText { + margin: 8px 0 0; + max-width: 220px; + font-size: 14px; + font-weight: 500; + line-height: 1.35; + letter-spacing: -0.01em; + color: #a4aab5; +} + +.cellText2 .stepText { + max-width: 405px; +} @@ -0,0 +1,71 @@ +import * as React from 'react' +import Image from 'next/image' + +import shell from './voice-clone-card-shell.module.scss' +import styles from './how-to-model-card.module.scss' + +function StepArrow() { + return ( + + + + + ) +} + +export function HowToModelCard() { + return ( +
+

+ Как работать с моделью +

+ +
+
+
+ Выбор шаблона голоса или добавление своего +
+ +
+ +
+ +
+ Ввод текста для озвучки и кнопка запуска генерации +
+ +
+

Шаг 1.

+

Выберите один из шаблонов голоса или создайте свой

+
+ +
+

Шаг 2.

+

+ Введите текст, который нужно озвучить и нажмите на кнопку – запустится процесс генерации +

+
+
+
+
+ ) +} @@ -0,0 +1,4 @@ +export { HowToModelCard } from './how-to-model-card' +export { SoundParamsCard } from './sound-params-card' +export { AddVoicesCard } from './add-voices-card' +export { ReuseQueryCard } from './reuse-query-card' @@ -0,0 +1,3 @@ +.card { + background: linear-gradient(to top right, #2b2b42, #18181b); +} @@ -0,0 +1,27 @@ +import * as React from 'react' +import Image from 'next/image' + +import shell from './voice-clone-card-shell.module.scss' +import fig from './voice-clone-figure.module.scss' +import styles from './reuse-query-card.module.scss' + +export function ReuseQueryCard() { + return ( +
+

+ Переиспользуйте запрос по клику +

+
+
+ Раздел «Мои генерации» +
+
+
+ ) +} @@ -0,0 +1,3 @@ +.card { + background: linear-gradient(to bottom right, #2b2b42, #18181b); +} @@ -0,0 +1,30 @@ +import * as React from 'react' +import Image from 'next/image' + +import shell from './voice-clone-card-shell.module.scss' +import fig from './voice-clone-figure.module.scss' +import styles from './sound-params-card.module.scss' + +export function SoundParamsCard() { + return ( +
+

+ Меняйте параметры звучания +

+

+ Экспериментируйте с настройками, чтобы добиться идеального звучания +

+
+
+ Панель параметров +
+
+
+ ) +} @@ -0,0 +1,79 @@ +$purple-fade: rgba(42, 40, 188, 0.9) 0%, rgba(42, 40, 188, 0.4) 32%, rgba(42, 40, 188, 0.1) 55%, transparent 72%; + +.shell { + position: relative; + overflow: hidden; + border-radius: 16px; + padding: 25px; + width: 100%; + background: #1d1d21; +} + +.shellFill { + height: 100%; + display: flex; + flex-direction: column; +} + +.shellFill .body { + flex: 1 1 auto; + display: flex; + flex-direction: column; + min-height: 0; +} + +.shellCornerGlow::before, +.shellCornerGlow::after { + content: ''; + position: absolute; + z-index: 0; + pointer-events: none; + width: min(100%, 520px); + height: 75%; + min-height: 200px; + filter: blur(48px); + opacity: 0.72; +} + +.shellCornerGlow::before { + top: -12%; + right: -8%; + background: radial-gradient(ellipse 80% 70% at 100% 0%, $purple-fade); +} + +.shellCornerGlow::after { + bottom: -12%; + left: -8%; + background: radial-gradient(ellipse 80% 70% at 0% 100%, $purple-fade); +} + +.title { + position: relative; + z-index: 1; + margin: 0 0 20px; + font-size: 28px; + font-weight: 600; + line-height: 1; + letter-spacing: -0.01em; + color: #fff; +} + +.shell:has(.description) .title { + margin-bottom: 12px; +} + +.description { + position: relative; + z-index: 1; + margin: 0 0 20px; + font-size: 14px; + font-weight: 500; + line-height: 1.35; + letter-spacing: -0.01em; + color: #a4aab5; +} + +.body { + position: relative; + z-index: 1; +} @@ -0,0 +1,17 @@ +.figure { + margin: 0; + border-radius: 12px; + overflow: hidden; + line-height: 0; + background: rgba(0, 0, 0, 0.2); +} + +.figureFull { + max-width: 100%; +} + +.image { + display: block; + width: 100%; + height: auto; +} @@ -0,0 +1,3 @@ +export { default as VoiceClonePage } from './voice-clone-page' +export { VoiceCloneCardsGrid } from './voice-clone-cards-grid' +export { VoiceCloneMobile } from './voice-clone-mobile' @@ -0,0 +1,50 @@ +.root { + display: flex; + flex-direction: column; + gap: 20px; + width: 100%; + max-width: 1130px; + overflow-x: auto; +} + +.row { + display: flex; + flex-wrap: nowrap; + align-items: stretch; + gap: 20px; + min-width: 0; +} + +.mainSlot { + flex: 1 1 0; + min-width: 0; + max-width: 736px; + display: flex; +} + +.sideSlot { + flex: 0 1 374px; + width: 374px; + max-width: 100%; + min-width: 280px; + display: flex; +} + +.rowBottom .mainSlot { + flex: 502 1 0; + max-width: none; +} + +.rowBottom .sideSlot { + flex: 608 1 0; + width: auto; + min-width: 0; +} + +.mainSlot > *, +.sideSlot > * { + flex: 1; + width: 100%; + min-width: 0; + min-height: 0; +} @@ -0,0 +1,28 @@ +import * as React from 'react' + +import { AddVoicesCard, HowToModelCard, ReuseQueryCard, SoundParamsCard } from './cards' + +import styles from './voice-clone-cards-grid.module.scss' + +export function VoiceCloneCardsGrid() { + return ( +
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ ) +} @@ -0,0 +1,58 @@ +.root { + width: 100%; +} + +.title { + margin: 0 0 25px 0; + font-weight: 700; + font-size: 18.27px; + line-height: 100%; + letter-spacing: -0.01em; + color: #fff; +} + +.steps { + display: flex; + flex-direction: column; + gap: 25px; + margin-top: 25px; +} + +.step { + position: relative; +} + +.stepBadge { + position: absolute; + z-index: 1; + top: -8px; + left: 5px; + display: inline-flex; + align-items: center; + justify-content: center; + height: 29px; + padding: 7px 10px; + border-radius: 95px; + font-weight: 800; + font-size: 12.79px; + line-height: 1; + letter-spacing: -0.01em; + color: #fff; + background: #8280ff; + box-shadow: 0 4px 4px rgba(0, 0, 0, 0.25); +} + +.stepImageWrap { + margin: 0; + line-height: 0; +} + +.stepImage { + display: block; + height: auto; + width: 100%; +} + +.footerInputWrap { + margin-top: 28px; +} @@ -0,0 +1,58 @@ +import * as React from 'react' +import { Box } from '@mui/material' +import Image from 'next/image' + +import styles from './voice-clone-mobile.module.scss' +import { ModelInput } from '#/features/model-input' + +export function VoiceCloneMobile() { + const steps = [ + { label: 'Шаг 1', src: '/voice-clone/message-input-mobile.png', alt: 'Ввод сообщения' }, + { label: 'Шаг 2', src: '/voice-clone/voice-selector-mobile.png', alt: 'Выбор голоса' }, + { label: 'Шаг 3', src: '/voice-clone/message-preview-mobile.png', alt: 'Предпросмотр сообщения' }, + ] + const inputTypes = [{ type: 'text', required: true, versions: [] }] + + return ( + + +
+

+ Добавляйте свой голос +
+ или выбирайте из пресетов +

+ +
+ {steps.map((step) => ( +
+
{step.label}
+
+ {step.alt} +
+
+ ))} +
+ +
+ false} + styles='chats' + viewMobileSettings={() => {}} + currentVersion='' + input_types={inputTypes} + /> +
+
+
+
+ ) +} @@ -0,0 +1,33 @@ +import * as React from 'react' +import { Box, Typography } from '@mui/material' + +import { NextPageWithLayout } from '#/pages/_app' +import { getDeviceType } from '#/shared/lib/helpers' + +import { VoiceCloneCardsGrid } from './voice-clone-cards-grid' +import { VoiceCloneMobile } from './voice-clone-mobile' + +export const VoiceClonePage: NextPageWithLayout = () => { + const device = getDeviceType() + const desktop = device === 'desktop' + + return ( + + {desktop && ( + <> + + Войсклон + + + + + + + )} + + {!desktop && } + + ) +} + +export default VoiceClonePage @@ -33,6 +33,7 @@ import { useDeviceType } from '#/shared/lib/hooks' import { AddVoiceCta } from '../add-voice-cta' import { AddVoicePlate } from '../add-voice-plate' +import { VoiceCloneCardsGrid, VoiceCloneMobile } from '../voice-clone-empty-state' import { VoiceCloneSelect } from '../voice-clone-select' const VoiceCloningModelPage: NextPageWithLayout = () => { @@ -212,6 +213,11 @@ const VoiceCloningModelPage: NextPageWithLayout = () => { token: session?.access, enabled: !!modelType && !!session?.access && scope === 'playground', }) + const hasGenerations = (messages?.length ?? 0) > 0 + const mobileScrollAreaHeight = `calc(100dvh - 116px - 61px - 15px ${botParams?.blocked ? '- 17px' : '- 23.5px'})` + const compactPlaygroundEmpty = scope === 'playground' && !hasGenerations + /** Меню + отступы layout + шапка страницы + табы — чтобы основной блок доходил до низа экрана без серой полосы */ + const mobileVoiceCompactMinHeight = 'calc(100dvh - 200px)' return ( <> @@ -255,6 +261,9 @@ const VoiceCloningModelPage: NextPageWithLayout = () => { marginBottom: desktop ? 0 : 2, width: desktop ? '97%' : '100%', marginTop: desktop ? 3 : '15px', + ...(!desktop && compactPlaygroundEmpty + ? { minHeight: mobileVoiceCompactMinHeight, justifyContent: 'flex-start' } + : {}), }} > { marginLeft: 0, display: 'flex', flexDirection: desktop ? 'column' : 'column-reverse', + ...(!desktop && compactPlaygroundEmpty ? { flex: 1, minHeight: 0, alignSelf: 'stretch' } : {}), }} > {desktop ? ( @@ -322,7 +332,16 @@ const VoiceCloningModelPage: NextPageWithLayout = () => { /> - setPrompt(content)} /> + {hasGenerations ? ( + setPrompt(content)} + /> + ) : ( + + )} { ) : ( - +
{
-
+
{ >
- setPrompt(content)} /> + {hasGenerations ? ( + setPrompt(content)} + /> + ) : ( + + )}
@@ -378,7 +430,16 @@ const VoiceCloningModelPage: NextPageWithLayout = () => {
- + {botParams && ( { )} - + {desktop && ( {botParams?.versions && botParams.versions.length !== 0 ? (