diff --git a/demo/src/Components/SearchPage/index.stories.tsx b/demo/src/Components/SearchPage/index.stories.tsx index 354eafad..5614d5ab 100644 --- a/demo/src/Components/SearchPage/index.stories.tsx +++ b/demo/src/Components/SearchPage/index.stories.tsx @@ -1,17 +1,25 @@ +/* eslint-disable no-console */ + import React, {useState} from 'react'; import {ISearchItem, SearchPage} from '@diplodoc/components'; import mockData from './data'; +import generativeSearchData from './searchData'; // eslint-disable-next-line no-console, @typescript-eslint/no-explicit-any const log = (...message: any[]) => console.log(...message); type Args = { Mobile: string; + GenerativeSearchLoading: boolean; + GenerativeSearchError: boolean; }; const SearchPageDemo = (args: Args) => { const isMobile = args['Mobile']; + const generativeSearchLoading = args['GenerativeSearchLoading']; + const generativeSearchError = args['GenerativeSearchError']; + const [page, setPage] = useState(1); const [items, setItems] = useState(getItems(page, mockData)); @@ -35,6 +43,21 @@ const SearchPageDemo = (args: Args) => { relevantOnClick={(item) => log('Click on like button', item)} itemsPerPage={2} totalItems={mockData.length} + generativeSearchData={generativeSearchData} + generativeSearchLoading={generativeSearchLoading} + generativeSearchError={generativeSearchError} + generativeExpandOnClick={(answer) => + console.log('Click on generative answer expand', answer) + } + generativeSourceOnClick={(link) => + console.log('Click on generative answer source', link) + } + generativeIrrelevantOnClick={(answer) => + console.log('Click on generative answer dislike button', answer) + } + generativeRelevantOnClick={(answer) => + console.log('Click on generative answer like button', answer) + } /> ); @@ -47,6 +70,12 @@ export default { Mobile: { control: 'boolean', }, + GenerativeSearchLoading: { + control: 'boolean', + }, + GenerativeSearchError: { + control: 'boolean', + }, }, }; diff --git a/demo/src/Components/SearchPage/searchData.ts b/demo/src/Components/SearchPage/searchData.ts new file mode 100644 index 00000000..75f41215 --- /dev/null +++ b/demo/src/Components/SearchPage/searchData.ts @@ -0,0 +1,26 @@ +export default { + message: { + content: + '

Чтобы работать с YFM, можно следовать таким рекомендациям:

', + role: 'assistant', + }, + links: [ + 'https://diplodoc.com/docs/ru/quickstart', + 'https://diplodoc.com/docs/ru/syntax/base', + 'https://diplodoc.com/docs/ru/tools/docs/build', + 'https://diplodoc.com/docs/ru/index-yfm', + 'https://diplodoc.com/docs/en/how-it-work', + ], + titles: [ + 'Быстрый старт | Diplodoc', + 'Базовая разметка | Diplodoc', + 'Сборка | Diplodoc', + 'Yandex Flavored Markdown | Diplodoc', + 'Main scenarios for Diplodoc usage | Diplodoc', + ], + final_search_query: 'как работать с yfm', + is_answer_rejected: false, + is_bullet_answer: false, + search_reqid: '1722674507173832-17567698128095587952-ngp6et3xflkdijxp-BAL', + reqid: '1722674506477130-4942193876903362238-balancer-l7leveler-kubr-yp-sas-214-BAL', +}; diff --git a/demo/src/Components/SearchSuggest/index.stories.tsx b/demo/src/Components/SearchSuggest/index.stories.tsx index b0c8d778..11978b44 100644 --- a/demo/src/Components/SearchSuggest/index.stories.tsx +++ b/demo/src/Components/SearchSuggest/index.stories.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import type {FC, PropsWithChildren} from 'react'; import type {SearchProvider, SearchResult} from '@diplodoc/components'; @@ -57,6 +58,9 @@ const SearchSuggestDemo = () => { provider={provider} onFocus={() => setSearch(true)} onBlur={() => setSearch(false)} + generativeSuggestOnClick={(link) => + console.log(`Clicked on generative search banner going to ${link}`) + } /> ); diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss new file mode 100644 index 00000000..fa2a3b0a --- /dev/null +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss @@ -0,0 +1,307 @@ +@import '../../styles/variables'; +@import '../../styles/mixins'; + +.generative-search-answer { + $block: &; + + &__loader { + border-radius: 8px; + display: flex; + gap: 8px; + align-items: center; + padding: 26px; + overflow: hidden; + position: relative; + } + + &__container { + border-radius: 28px; + background-color: var(--g-color-second-background); + padding: 15px; + position: relative; + overflow: hidden; + + &_expanded { + max-height: 300px; + overflow: hidden; + } + } + + &__gradient-background { + position: absolute; + left: -414px; + top: -344.46px; + width: 1433.29px; + height: 629.38px; + background: radial-gradient( + 35.52% 35.54% at 54.12% 47.69%, + rgba(153, 181, 251, 0.4) 23.5%, + rgba(159, 186, 251, 0.4) 40%, + var(--g-color-second-background-300) 100% + ); + pointer-events: none; + transform: rotate(3.74deg); + } + + &__gradient-background-second { + position: absolute; + top: -83px; + left: -451.54px; + width: 1435.43px; + height: 1024.26px; + background: radial-gradient( + 35.52% 35.54% at 54.12% 47.69%, + rgba(117, 132, 170, 0.3) 0%, + rgba(159, 186, 251, 0.15) 40%, + var(--g-color-second-background-300) 100% + ); + + pointer-events: none; + transform: rotate(9.73deg); + } + + &__header { + padding: 10px; + display: flex; + flex-direction: column; + row-gap: 4px; + z-index: 1; + position: relative; + + > h4 { + color: var(--g-color-text-primary); + font-size: 18px; + font-weight: 700; + line-height: 16px; + margin: 0; + } + > p { + color: var(--g-color-text-primary); + font-size: 12px; + font-weight: 400; + line-height: 16px; + margin: 0; + } + } + + &__yandexgpt-logo { + font-size: 12px; + font-weight: 500; + line-height: 16px; + padding-left: 4px; + display: inline-flex; + column-gap: 2px; + align-items: center; + vertical-align: middle; + } + + &__content { + background-color: var(--g-color-neutral-background); + display: block; + padding: 19px 20px; + border-radius: 26px; + position: relative; + z-index: 1; + margin-bottom: 28px; + box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.25); + + a { + text-decoration: none; + background-color: rgba(239, 242, 249, 1); + color: rgba(167, 169, 174, 1); + + border-radius: 4px; + width: 18px; + height: 18px; + + display: inline-flex; + align-items: center; + justify-content: center; + } + + a + a { + margin-left: 4px; + } + + strong, + h1, + h2, + h3, + h4, + h5 { + font-size: 14px; + font-weight: 700; + line-height: 22px; + } + + p, + li { + font-size: 14px; + font-weight: 500; + line-height: 22px; + } + } + + &__content_theme_dark { + a { + background-color: rgba(176, 176, 176, 1); + color: rgba(95, 94, 94, 1); + } + } + + &__sources { + position: relative; + z-index: 1; + display: flex; + flex-direction: column; + gap: 17px; + margin-bottom: 28px; + margin-top: 35px; + + > h3 { + font-size: 14px; + font-weight: 400; + line-height: 16px; + margin: 0; + } + } + + &__carousel-wrapper { + position: relative; + } + + &__carousel { + display: flex; + overflow-x: hidden; + gap: 8px; + margin: -15px; + padding: 15px; + } + + &__controls { + position: absolute; + width: 100%; + top: 50px; + transform: translateY(-50%); + } + + &__chevron-left { + position: absolute; + left: 0; + } + + &__chevron-right { + position: absolute; + right: 0; + } + + &__card { + box-sizing: border-box; + position: relative; + display: flex; + + flex-direction: column; + justify-content: space-between; + + padding: 10px; + border-radius: 16px; + + min-width: 134px; + max-width: 134px; + min-height: 112px; + + box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.25); + background-color: var(--g-color-neutral-background); + + p { + font-size: 12px; + font-weight: 400; + line-height: 16px; + margin: 0; + + max-width: 100%; + max-height: calc(16px * 3); + overflow: hidden; + word-break: break-all; + } + + a { + @include link(); + color: rgba(85, 126, 236, 1); + font-size: 12px; + font-weight: 400; + line-height: 16px; + + display: block; + max-width: 100%; + max-height: calc(16px * 2); + overflow: hidden; + word-break: break-all; + margin-right: 29px; + } + } + + &__card-number { + position: absolute; + bottom: 7px; + right: 14px; + + font-size: 10px; + font-weight: 400; + line-height: 22px; + color: rgba(181, 181, 181, 1); + } + + &__disclaimer { + margin-top: 15px; + padding: 10px; + } + + &__rating-container { + display: flex; + gap: 8px; + min-height: 45px; + } + + &__rating-button { + border: 1px solid rgba(181, 181, 181, 1); + + &:active { + border-color: rgba(95, 94, 94, 1); + } + } + + &__expanded-bottom { + display: flex; + justify-content: center; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 93px; + z-index: 1; + background: linear-gradient( + 0deg, + var(--g-color-base-background) -0.93%, + rgba(241, 244, 249, 0.01) 93.43% + ); + align-items: end; + } + + &__toggle-button { + background-color: rgba(255, 255, 255, 1); + box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.25); + position: absolute; + bottom: 19px; + border: 1px solid transparent; + z-index: 1; + + &:hover { + border-color: rgba(174, 174, 178, 1); + } + + &:active { + background-color: rgba(247, 247, 247, 1); + } + } +} diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx new file mode 100644 index 00000000..953805f2 --- /dev/null +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx @@ -0,0 +1,323 @@ +import React, {ReactNode, useEffect, useState} from 'react'; +import {ChevronDown, ChevronLeft, ChevronRight, ThumbsDown, ThumbsUp} from '@gravity-ui/icons'; +import {Button, Icon, useTheme} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; + +import {useTranslation} from '../../hooks'; +import {useCarousel} from '../../hooks/useCarousel'; +import {simplifyUrl} from '../../utils'; +import {HTML} from '../HTML'; + +import {YandexGPTLogo} from './YandexGPTLogo'; +import './GenerativeSearchAnswer.scss'; + +const b = block('generative-search-answer'); + +interface Message { + content: string; + role: string; +} + +export interface GenerativeSearchOnClickProps { + generativeExpandOnClick?: (answer: IGenerativeSearch) => void; + generativeSourceOnClick?: (link: string) => void; + generativeIrrelevantOnClick?: (answer: IGenerativeSearch) => void; + generativeRelevantOnClick?: (answer: IGenerativeSearch) => void; +} + +export interface IGenerativeSearch { + message: Message; + links: string[]; + titles: string[]; + final_search_query: string; + is_answer_rejected: boolean; + is_bullet_answer: boolean; + search_reqid: string; + reqid: string; +} + +interface IGenerativeSearchSource { + links: string[]; + titles: string[]; +} + +const GenerativeSearchDisclaimer: React.FC = () => { + const {t} = useTranslation('generative-search'); + + return ( +
+

{t('generative-search_disclaimer')}

+
+ ); +}; + +interface GenerativeSearchSourceAnalytics { + generativeSourceOnClick?: (link: string) => void; +} + +type GenerativeSearchSourceProps = IGenerativeSearchSource & GenerativeSearchSourceAnalytics; + +const GenerativeSearchSource: React.FC = ({ + links, + titles, + generativeSourceOnClick, +}) => { + const { + containerRef, + showPrevButton, + showNextButton, + nextSlide, + prevSlide, + updateButtonsVisibility, + } = useCarousel(); + + const {t} = useTranslation('generative-search'); + + return ( +
+

{t('generative-search_sources_title')}

+ +
+
+ {links.map((link, index) => ( +
+

{titles[index]}

+ + generativeSourceOnClick + ? generativeSourceOnClick(link) + : undefined + } + href={link} + target="_blank" + rel="noopener noreferrer" + > + {simplifyUrl(link)} + + {index + 1} +
+ ))} +
+ +
+ {showPrevButton && ( + + left + + )} + {showNextButton && ( + + right + + )} +
+
+
+ ); +}; + +const GenerativSearchHeader = () => { + const {t} = useTranslation('generative-search'); + + return ( +
+

{t('generative-search_header_title')}

+

+ {t('generative-search_header_text')} + + + YandexGPT + +

+
+ ); +}; + +interface IGenerativeSearchWrapper { + children: ReactNode; + isExpanded?: boolean; +} + +const GenerativeSearchWrapperBlock: React.FC = ({ + children, + isExpanded, +}) => { + const theme = useTheme(); + + return ( + <> +
+
+ + {children} + {theme === 'dark' &&
} +
+ + ); +}; + +const GenerativeSearchErrorBlock = () => { + const {t} = useTranslation('generative-search'); + + return ( + +
+

{t('generative-search_something_went_wrong')}

+
+
+ ); +}; + +const GenerativeSearchLoadingBlock = () => { + const {t} = useTranslation('generative-search'); + + return ( +
+
+ +

{t('generative-search_loading_title')}

+
+ ); +}; + +interface IGenerativeSearchI { + generativeSearchData: IGenerativeSearch; + generativeSearchLoading: boolean; + generativeSearchError: boolean; +} +export type GenerativeSearchProps = IGenerativeSearchI & GenerativeSearchOnClickProps; + +const GenerativeSearchAnswer: React.FC = ({ + generativeSearchData, + generativeSearchLoading, + generativeSearchError, + generativeExpandOnClick, + generativeSourceOnClick, + generativeIrrelevantOnClick, + generativeRelevantOnClick, +}) => { + const [isSubmitted, setIsSubmitted] = useState(false); + const [isExpanded, setIsExpanded] = useState(false); + const theme = useTheme(); + + useEffect(() => { + setIsSubmitted(false); + setIsExpanded(false); + }, [generativeSearchData]); + + const {t} = useTranslation('generative-search'); + + const handleRatingClick = () => { + setIsSubmitted(!isSubmitted); + }; + + const toggleExpand = () => { + setIsExpanded(!isExpanded); + }; + + const handleSourceInTextClick: React.MouseEventHandler = (e) => { + const target = e.target as HTMLElement; + if (target.tagName === 'A' && generativeSourceOnClick) { + generativeSourceOnClick((target as HTMLAnchorElement).href); + } + }; + + if (generativeSearchLoading) { + return ; + } + + if (generativeSearchError) { + return ; + } + + const {message, links, titles} = generativeSearchData; + + const {content} = message; + + return ( +
+ +
+ {content} +
+ + {Boolean(links.length) && ( + + )} + +
+ {isSubmitted ? ( +

{t('generative-search_feedback_answer')}

+ ) : ( + <> + + + + )} +
+ {!isExpanded && Boolean(links.length) && ( +
+ +
+ )} +
+ +
+ ); +}; + +export default GenerativeSearchAnswer; diff --git a/src/components/GenerativeSearchAnswer/YandexGPTLogo.tsx b/src/components/GenerativeSearchAnswer/YandexGPTLogo.tsx new file mode 100644 index 00000000..efc288be --- /dev/null +++ b/src/components/GenerativeSearchAnswer/YandexGPTLogo.tsx @@ -0,0 +1,35 @@ +import React from 'react'; + +interface IYandexGPTLogo { + className?: string; + width?: number; + height?: number; + fill?: string; +} + +export const YandexGPTLogo: React.FC = ({ + className, + width = '10', + height = '10', + fill = 'black', +}) => { + return ( + + + + + ); +}; diff --git a/src/components/GenerativeSearchAnswer/index.ts b/src/components/GenerativeSearchAnswer/index.ts new file mode 100644 index 00000000..ab7364ab --- /dev/null +++ b/src/components/GenerativeSearchAnswer/index.ts @@ -0,0 +1,2 @@ +export {default as GenerativeSearchAnswer} from './GenerativeSearchAnswer'; +export * from './GenerativeSearchAnswer'; diff --git a/src/components/SearchPage/SearchPage.tsx b/src/components/SearchPage/SearchPage.tsx index 3c83d571..3d214b7c 100644 --- a/src/components/SearchPage/SearchPage.tsx +++ b/src/components/SearchPage/SearchPage.tsx @@ -3,6 +3,7 @@ import {Button, Loader, TextInput} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; import {useTranslation} from '../../hooks'; +import {GenerativeSearchAnswer, GenerativeSearchProps} from '../GenerativeSearchAnswer'; import {Paginator, PaginatorProps} from '../Paginator'; import {ISearchItem, SearchItem, SearchOnClickProps} from '../SearchItem'; @@ -35,7 +36,11 @@ interface SearchPageProps extends Loading { type RenderFoundProps = SearchPageProps & SearchOnClickProps & PaginatorProps; -type SearchPageInnerProps = SearchPageProps & SearchOnClickProps & InputProps & PaginatorProps; +type SearchPageInnerProps = SearchPageProps & + SearchOnClickProps & + InputProps & + PaginatorProps & + GenerativeSearchProps; const FoundBlock: React.FC = ({ items, @@ -54,6 +59,7 @@ const FoundBlock: React.FC = ({ return (

{t('search_request-query')}

+
{items.map((item: ISearchItem) => ( = ({loading}) => { return loading ? ( ) : ( -
-

{t('search_not-found-title')}

-
{t('search_not-found-text')}
-
+ <> +
+

{t('search_not-found-title')}

+
{t('search_not-found-text')}
+
+ ); }; @@ -145,6 +153,13 @@ const SearchPage = ({ irrelevantOnClick, relevantOnClick, loading, + generativeSearchData, + generativeSearchLoading, + generativeSearchError, + generativeExpandOnClick, + generativeSourceOnClick, + generativeIrrelevantOnClick, + generativeRelevantOnClick, }: SearchPageInnerProps) => { const inputRef = useRef(null); const [currentQuery, setCurrentQuery] = useState(query); @@ -161,6 +176,19 @@ const SearchPage = ({ }} />
+
+ +
{items?.length && query ? ( void; +} + +const SuggestGenerative: React.FC = ({link, generativeSuggestOnClick}) => { + const {t} = useTranslation('search-suggest'); + + return ( + (generativeSuggestOnClick ? generativeSuggestOnClick(link) : undefined)} + > +
+ +
+

{t('search-suggest-generative_title')}

+

{t('search-suggest-generative_subtitle')}

+
+
+ + ); +}; + const SuggestLoader = memo(() => { return (
@@ -45,25 +70,38 @@ type SuggestListProps = { fromKeyboard?: boolean, ) => boolean | void; onChangeActive: (index?: number) => void; + queryLink: string; + generativeSuggestOnClick?: (link: string) => void; }; const SuggestList = memo( forwardRef, SuggestListProps>((props, ref) => { - const {id, items, renderItem, onItemClick, onChangeActive} = props; + const { + id, + items, + renderItem, + onItemClick, + onChangeActive, + queryLink, + generativeSuggestOnClick, + } = props; return ( - + <> + + + ); }), ); @@ -74,11 +112,11 @@ type SuggestProps = { id: string; query: string; provider: SearchProvider; -} & Omit; +} & Omit; export const Suggest = memo( forwardRef, SuggestProps>((props, ref) => { - const {query, provider} = props; + const {query, provider, generativeSuggestOnClick} = props; const [items, suggest] = useProvider(provider); useEffect(() => suggest(query), [query, suggest]); @@ -91,15 +129,24 @@ export const Suggest = memo( return ; } + const queryLink = provider.link(`/search?query=${query}`); + if (Array.isArray(items) && !items.length) { - return ; + return ( + <> + + + + ); } return ( ); }), diff --git a/src/components/SearchSuggest/index.scss b/src/components/SearchSuggest/index.scss index bff85603..6388215b 100644 --- a/src/components/SearchSuggest/index.scss +++ b/src/components/SearchSuggest/index.scss @@ -149,4 +149,47 @@ } } } + + &__generative-search { + @include link(); + text-decoration: none; + + padding: 20px; + border: 1px solid transparent; + border-bottom: 1px solid var(--g-color-line-generic); + + display: flex; + align-items: flex-start; + gap: 8px; + + &:hover { + border: 1px solid rgba(88, 86, 214, 1); + cursor: pointer; + } + + &:active { + border: 1px solid rgba(88, 86, 214, 1); + } + } + + &__generative-search-text { + display: inline-block; + vertical-align: top; + + > h1 { + color: var(--g-color-text-link); + font-size: 18px; + font-weight: 700; + line-height: 22px; + margin: 0; + } + > p { + color: var(--g-color-text-secondary); + + font-size: 12px; + font-weight: 400; + line-height: 16px; + margin: 0; + } + } } diff --git a/src/components/SearchSuggest/index.tsx b/src/components/SearchSuggest/index.tsx index 6c3f2565..e6be0742 100644 --- a/src/components/SearchSuggest/index.tsx +++ b/src/components/SearchSuggest/index.tsx @@ -38,6 +38,7 @@ export interface SearchSuggestProps { onFocus?: () => void; onBlur?: () => void; endContent?: React.ReactNode; + generativeSuggestOnClick?: (link: string) => void; } export interface SearchSuggestApi { @@ -47,7 +48,8 @@ export interface SearchSuggestApi { } export const SearchSuggest = forwardRef((props, api) => { - const {provider, className, placeholder, endContent, containerClass} = props; + const {provider, className, placeholder, endContent, containerClass, generativeSuggestOnClick} = + props; const href = useRef(null); const input = useRef(null); const suggest = useRef>(null); @@ -137,6 +139,7 @@ export const SearchSuggest = forwardRef((p renderItem={SuggestItem} onItemClick={onSubmit} onChangeActive={setActive} + {...generativeSuggestOnClick} /> )} diff --git a/src/hooks/useCarousel.tsx b/src/hooks/useCarousel.tsx new file mode 100644 index 00000000..8f2c417b --- /dev/null +++ b/src/hooks/useCarousel.tsx @@ -0,0 +1,54 @@ +import {useEffect, useRef, useState} from 'react'; + +export const useCarousel = () => { + const containerRef = useRef(null); + const [showPrevButton, setShowPrevButton] = useState(false); + const [showNextButton, setShowNextButton] = useState(false); + + const updateButtonsVisibility = () => { + if (containerRef.current) { + const {scrollLeft, scrollWidth, clientWidth} = containerRef.current; + setShowPrevButton(scrollLeft > 0); + setShowNextButton(scrollLeft < scrollWidth - clientWidth); + } + }; + + const nextSlide = () => { + if (containerRef.current) { + containerRef.current.scrollBy({ + left: containerRef.current.clientWidth / 3, + behavior: 'smooth', + }); + setTimeout(updateButtonsVisibility, 300); + } + }; + + const prevSlide = () => { + if (containerRef.current) { + containerRef.current.scrollBy({ + left: -containerRef.current.clientWidth / 3, + behavior: 'smooth', + }); + setTimeout(updateButtonsVisibility, 300); + } + }; + + useEffect(() => { + updateButtonsVisibility(); + const handleResize = () => { + updateButtonsVisibility(); + }; + + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); + + return { + containerRef, + showPrevButton, + showNextButton, + nextSlide, + prevSlide, + updateButtonsVisibility, + }; +}; diff --git a/src/i18n/en.json b/src/i18n/en.json index 17c9638d..5d342062 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -112,10 +112,24 @@ "search_mark_dislike": "dislike", "search_mark-result-text": "Thank you for your rating!" }, + "generative-search": { + "generative-search_expand": "Expand", + "generative-search_sources_title": "Sources", + "generative-search_bad_response": "Bad answer", + "generative-search_good_response": "Good answer", + "generative-search_loading_title": "Quick answer...", + "generative-search_feedback_answer": "Thank you for contributing in technology improvement", + "generative-search_something_went_wrong": "Something went wrong. Please try again or refresh the page.", + "generative-search_header_title": "Quick answer", + "generative-search_header_text": "Generated by", + "generative-search_disclaimer": "The answer was generated by YandexGPT based on the service documentation. There may be inaccuracies in it, you can verify the information by referring to the sources." + }, "search-suggest": { "search-suggest_all-results": "All results", "search-suggest_not-found": "No results found for «{{query}}» request", "search-suggest_error-text": "Something went wrong. Try again.", + "search-suggest-generative_title": "Click to get a quick answer", + "search-suggest-generative_subtitle": "generated by YandexGPT", "search-item_type-all": "All", "search-item_type-doc": "Documentation", "search-item_type-main": "Main" diff --git a/src/i18n/ru.json b/src/i18n/ru.json index 386a17a2..97d8bfeb 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -112,10 +112,24 @@ "search_mark_dislike": "вообще не про то", "search_mark-result-text": "Спасибо за оценку!" }, + "generative-search": { + "generative-search_expand": "Развернуть", + "generative-search_sources_title": "Источники", + "generative-search_bad_response": "Плохой ответ", + "generative-search_good_response": "Хороший ответ", + "generative-search_loading_title": "Быстрый ответ...", + "generative-search_feedback_answer": "Спасибо, что помогаете делать технологию лучше", + "generative-search_something_went_wrong": "Что-то пошло не так. Попробуйте ещё раз или обновите страницу.", + "generative-search_header_title": "Быстрый ответ", + "generative-search_header_text": "Создан с помощью нейросети", + "generative-search_disclaimer": "Ответ сформирован нейросетью YandexGPT на основе документации сервиса. В нём могут быть неточности, проверить информацию можно поссылкам на источники." + }, "search-suggest": { "search-suggest_all-results": "Все результаты", "search-suggest_not-found": "По запросу «{{query}}» ничего не найдено.", "search-suggest_error-text": "Что-то пошло не так. Попробуйте ещё раз.", + "search-suggest-generative_title": "Нажмите, чтобы получить быстрый ответ", + "search-suggest-generative_subtitle": "С помощью нейросети YandexGPT", "search-item_type-all": "Всё", "search-item_type-doc": "Документация", "search-item_type-main": "Главная" diff --git a/src/themes/common/index.scss b/src/themes/common/index.scss index d8cc549a..504f6710 100644 --- a/src/themes/common/index.scss +++ b/src/themes/common/index.scss @@ -25,7 +25,11 @@ &_theme_light { @include g-colors-private-light; + --g-color-neutral-background: rgb(255, 255, 255); + --g-color-base-fill-color: var(--g-color-text-primary); --g-color-base-background: rgb(255, 255, 255); + --g-color-second-background: rgba(241, 244, 249, 1); + --g-color-second-background-300: rgba(241, 244, 249, 0.3); --g-color-base-brand: var(--g-color-private-blue-550-solid); --g-color-base-brand-hover: var(--g-color-private-blue-600-solid); --g-color-base-selection: var(--g-color-private-blue-100); @@ -59,7 +63,11 @@ --dc-text-highlight-selected: var(--g-color-text-inverted-primary); + --g-color-neutral-background: rgba(41, 42, 45); + --g-color-base-fill-color: var(--g-color-text-primary); --g-color-base-background: rgb(45, 44, 51); + --g-color-second-background: rgba(32, 33, 36); + --g-color-second-background-300: rgba(32, 33, 36, 0.3); --g-color-base-brand: var(--g-color-private-blue-550-solid); --g-color-base-brand-hover: var(--g-color-private-blue-600-solid); --g-color-base-selection: var(--g-color-private-blue-150); diff --git a/src/utils/index.ts b/src/utils/index.ts index 09041043..f5fe8f0a 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -137,3 +137,8 @@ export function getPageType({ return DocumentType.Base; } + +export const simplifyUrl = (url: string) => { + const match = url.match(/^https?:\/\/[^/]+(\/.*)$/); + return match ? `...${match[1]}` : url; +};