diff --git a/package-lock.json b/package-lock.json index 76a3896393ae..3e66af816790 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,6 +55,7 @@ "overlayscrollbars": "^2.16.0", "overlayscrollbars-react": "^0.5.6", "prismjs": "^1.29.0", + "rate-limiter-flexible": "^11.2.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-i18next": "^14.0.5", @@ -18886,6 +18887,12 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/rate-limiter-flexible": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-11.2.0.tgz", + "integrity": "sha512-L0eIK+BmFMi6NcvmtEg7RSswOFKi9MMD5RhBIypFfOve+G6Jl1Xbb8qEHgK3uRzNtkOFYF0L9f7P4rSf2PnUVw==", + "license": "ISC" + }, "node_modules/raw-body": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", diff --git a/package.json b/package.json index 1013eb11600b..e9ff83d55c2a 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "overlayscrollbars": "^2.16.0", "overlayscrollbars-react": "^0.5.6", "prismjs": "^1.29.0", + "rate-limiter-flexible": "^11.2.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-i18next": "^14.0.5", diff --git a/src/blocks/CustomHeader/CustomHeader.tsx b/src/blocks/CustomHeader/CustomHeader.tsx index aba6766a5347..4d82e79df24a 100644 --- a/src/blocks/CustomHeader/CustomHeader.tsx +++ b/src/blocks/CustomHeader/CustomHeader.tsx @@ -1,9 +1,10 @@ -import {ChevronRight} from '@gravity-ui/icons'; +import {ChevronRight, SparklesFill} from '@gravity-ui/icons'; import {Animatable, AnimateBlock, Col, Grid, Row, YFMWrapper} from '@gravity-ui/page-constructor'; import {Button, ButtonProps, Flex, Icon, IconData, Text} from '@gravity-ui/uikit'; import React from 'react'; import ReactTimeAgo from 'react-time-ago'; +import {GeneratePlaygroundModal} from '../../components/GeneratePlaygroundModal'; import {Link} from '../../components/Link'; import {useLocale} from '../../hooks/useLocale'; import {block} from '../../utils'; @@ -62,6 +63,7 @@ export const CustomHeader: React.FC = ({ banner, }) => { const locale = useLocale(); + const [isGenerateModalOpen, setGenerateModalOpen] = React.useState(false); const showNewsBlock = news && news.items && news.items.length > 0; const showBannerBlock = banner?.content; @@ -127,8 +129,25 @@ export const CustomHeader: React.FC = ({ ); })} + ) : null} + setGenerateModalOpen(false)} + /> diff --git a/src/blocks/GeneratePlaygroundBlock/GeneratePlaygroundBlock.scss b/src/blocks/GeneratePlaygroundBlock/GeneratePlaygroundBlock.scss new file mode 100644 index 000000000000..306c191bdc7a --- /dev/null +++ b/src/blocks/GeneratePlaygroundBlock/GeneratePlaygroundBlock.scss @@ -0,0 +1,109 @@ +@use '~@gravity-ui/page-constructor/styles/variables.scss' as pcVariables; +@use '../../variables.scss'; + +$block: '.#{variables.$ns}generate-playground-block'; + +#{$block} { + padding: 80px 0; + + &__inner { + max-width: 720px; + margin: 0 auto; + padding: 0 24px; + display: flex; + flex-direction: column; + gap: 24px; + } + + &__title { + text-align: center; + } + + &__input-area { + position: relative; + padding: 8px; + } + + &__overlay { + position: absolute; + inset: 0; + z-index: 10; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 12px; + border-radius: 8px; + background: var(--g-color-base-background); + opacity: 0.92; + } + + &__actions { + display: flex; + gap: 12px; + justify-content: center; + + @media (max-width: map-get(pcVariables.$gridBreakpoints, 'sm') - 1) { + flex-direction: column; + } + } + + &__input-wrap { + display: flex; + gap: 12px; + align-items: flex-start; + margin-bottom: 20px; + + @media (max-width: map-get(pcVariables.$gridBreakpoints, 'sm') - 1) { + flex-direction: column; + } + } + + &__textarea-wrap { + position: relative; + flex: 1; + } + + &__textarea { + width: 100%; + } + + &__counter { + position: absolute; + right: 8px; + bottom: 8px; + pointer-events: none; + } + + &__button { + flex-shrink: 0; + + @media (max-width: map-get(pcVariables.$gridBreakpoints, 'sm') - 1) { + width: 100%; + } + } + + &__examples { + display: flex; + flex-wrap: wrap; + gap: 8px; + justify-content: center; + } + + &__example-chip { + padding: 6px 12px; + border-radius: 100px; + border: 1px solid var(--g-color-line-generic); + background: transparent; + color: var(--g-color-text-secondary); + font-size: 13px; + line-height: 18px; + cursor: pointer; + transition: border-color 0.15s ease, color 0.15s ease; + + &:hover { + border-color: var(--g-color-line-brand); + color: var(--g-color-text-primary); + } + } +} diff --git a/src/blocks/GeneratePlaygroundBlock/GeneratePlaygroundBlock.tsx b/src/blocks/GeneratePlaygroundBlock/GeneratePlaygroundBlock.tsx new file mode 100644 index 000000000000..cd342d95caa4 --- /dev/null +++ b/src/blocks/GeneratePlaygroundBlock/GeneratePlaygroundBlock.tsx @@ -0,0 +1,120 @@ +import {Play, SparklesFill} from '@gravity-ui/icons'; +import {AnimateBlock} from '@gravity-ui/page-constructor'; +import {Button, Icon, Spin, Text, TextArea} from '@gravity-ui/uikit'; +import React from 'react'; + +import {AiDisclaimer} from '../../components/GeneratePlaygroundModal/AiDisclaimer'; +import {LoadingText} from '../../components/GeneratePlaygroundModal/LoadingText'; +import { + EXAMPLES, + LOADING_TEXTS, + MAX_LENGTH, +} from '../../components/GeneratePlaygroundModal/constants'; +import {useGeneratePlayground} from '../../components/GeneratePlaygroundModal/hooks/useGeneratePlayground'; +import {openStackblitzFromGenerated} from '../../components/MDXRenderer/Sandbox/stackblitz'; +import {block} from '../../utils'; +import {CustomBlock} from '../constants'; + +import './GeneratePlaygroundBlock.scss'; + +const b = block('generate-playground-block'); + +export type GeneratePlaygroundBlockModel = { + type: CustomBlock.GeneratePlayground; + animated?: boolean; +}; + +export const GeneratePlaygroundBlock: React.FC = ({animated}) => { + const {input, setInput, loading, result, error, isOverLimit, handleGenerate, reset} = + useGeneratePlayground(); + + return ( + +
+ + Попробуйте прямо сейчас + + {result ? ( + <> +
+ + +
+ + + ) : ( +
+
+
+