From 8798eaddb43b5ccde78609e7430e8a66a5c0d430 Mon Sep 17 00:00:00 2001 From: Alexander Minkin Date: Sat, 3 Aug 2024 13:28:38 +0300 Subject: [PATCH 01/30] feat: add component for generative search answer --- .../GenerativeSearchAnswer/GenerativeSearchAnswer.scss | 0 .../GenerativeSearchAnswer/GenerativeSearchAnswer.tsx | 9 +++++++++ src/components/GenerativeSearchAnswer/index.ts | 1 + src/components/SearchPage/SearchPage.tsx | 4 ++++ 4 files changed, 14 insertions(+) create mode 100644 src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss create mode 100644 src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx create mode 100644 src/components/GenerativeSearchAnswer/index.ts diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx new file mode 100644 index 00000000..68fcab42 --- /dev/null +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx @@ -0,0 +1,9 @@ +import React from 'react'; + +import './GenerativeSearchAnswer.scss'; + +const GenerativeSearchAnswer: React.FC = () => { + return <>Нейросеть подумала и не додумала; +}; + +export default GenerativeSearchAnswer; diff --git a/src/components/GenerativeSearchAnswer/index.ts b/src/components/GenerativeSearchAnswer/index.ts new file mode 100644 index 00000000..cc7c44be --- /dev/null +++ b/src/components/GenerativeSearchAnswer/index.ts @@ -0,0 +1 @@ +export {default as GenerativeSearchAnswer} from './GenerativeSearchAnswer'; diff --git a/src/components/SearchPage/SearchPage.tsx b/src/components/SearchPage/SearchPage.tsx index 6e2a1496..4b36199e 100644 --- a/src/components/SearchPage/SearchPage.tsx +++ b/src/components/SearchPage/SearchPage.tsx @@ -4,6 +4,7 @@ import {Button, Loader, TextInput} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; import {useTranslation} from '../../hooks'; +import {GenerativeSearchAnswer} from '../GenerativeSearchAnswer'; import {Paginator, PaginatorProps} from '../Paginator'; import {ISearchItem, SearchItem, SearchOnClickProps} from '../SearchItem'; @@ -162,6 +163,9 @@ const SearchPage = ({ }} /> +
+ +
{items?.length && query ? ( Date: Sat, 3 Aug 2024 13:57:13 +0300 Subject: [PATCH 02/30] feat(SearchPage): move genanswer component under title in blocks --- src/components/SearchPage/SearchPage.tsx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/components/SearchPage/SearchPage.tsx b/src/components/SearchPage/SearchPage.tsx index 4b36199e..332e9d78 100644 --- a/src/components/SearchPage/SearchPage.tsx +++ b/src/components/SearchPage/SearchPage.tsx @@ -56,6 +56,9 @@ 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')}
+
+ ); }; @@ -163,9 +171,6 @@ const SearchPage = ({ }} />
-
- -
{items?.length && query ? ( Date: Sat, 3 Aug 2024 14:09:49 +0300 Subject: [PATCH 03/30] feat: add mocks for searchPage storybook --- demo/src/Components/SearchPage/searchData.ts | 26 ++++++++++++++++++++ src/components/SearchPage/SearchPage.tsx | 20 ++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 demo/src/Components/SearchPage/searchData.ts diff --git a/demo/src/Components/SearchPage/searchData.ts b/demo/src/Components/SearchPage/searchData.ts new file mode 100644 index 00000000..2edbb033 --- /dev/null +++ b/demo/src/Components/SearchPage/searchData.ts @@ -0,0 +1,26 @@ +export default { + message: { + content: + 'Чтобы работать с YFM, можно следовать таким рекомендациям:\n\n* **Создайте проект**. [1] Он состоит из нескольких конфигурационных файлов и страниц с контентом. [1]\n\n* **Запустите сборку проекта**. [1][3] Для этого используйте инструмент Builder в командной строке. [1] Укажите обязательные ключи запуска: **input (–i) — путь до директории проекта, output (–o) — путь до директории для выходных данных (статических HTML)**. [1][3] Пример команды: `yfm -i ./input-folder -o ./ouput-folder`. [1]\n\n* **Настройте сборку в YFM**. [3] Для этого при выполнении команды yfm укажите ключ запуска **--output-format=md**. [3] Сборка в YFM позволяет использовать вставки и условия видимости разделов, условия отображения контента и подстановки переменных. [3]\n\n* **Используйте готовый проект**. [1] Проекты в формате HTML можно использовать локально или разместить на хостинге, в GitHub Pages или S3. [1][4]', + 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/src/components/SearchPage/SearchPage.tsx b/src/components/SearchPage/SearchPage.tsx index 332e9d78..71bc1205 100644 --- a/src/components/SearchPage/SearchPage.tsx +++ b/src/components/SearchPage/SearchPage.tsx @@ -16,6 +16,22 @@ interface Loading { loading?: boolean; } +interface Message { + content: string; + role: string; +} + +interface ISearchData { + message: Message; + links: string[]; + titles: string[]; + final_search_query: string; + is_answer_rejected: boolean; + is_bullet_answer: boolean; + search_reqid: string; + reqid: string; +} + interface InputProps { query: string; onSubmit: (query: string) => void; @@ -33,6 +49,7 @@ interface SearchPageProps extends Loading { page: number; isMobile?: boolean; loading?: boolean; + searchData: ISearchData; } type RenderFoundProps = SearchPageProps & SearchOnClickProps & PaginatorProps; @@ -52,7 +69,6 @@ const FoundBlock: React.FC = ({ isMobile, }) => { const {t} = useTranslation('search'); - return (

{t('search_request-query')}

@@ -155,6 +171,7 @@ const SearchPage = ({ irrelevantOnClick, relevantOnClick, loading, + searchData, }: SearchPageInnerProps) => { const inputRef = useRef(null); const [currentQuery, setCurrentQuery] = useState(query); @@ -185,6 +202,7 @@ const SearchPage = ({ onPageChange, irrelevantOnClick, relevantOnClick, + searchData, }} /> ) : ( From 186a999659ed017e83de013b16af98031b71d642 Mon Sep 17 00:00:00 2001 From: Uyama Date: Sat, 3 Aug 2024 15:52:51 +0300 Subject: [PATCH 04/30] feat(demo): add mocks --- demo/src/Components/SearchPage/index.stories.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/demo/src/Components/SearchPage/index.stories.tsx b/demo/src/Components/SearchPage/index.stories.tsx index cfd6de80..d36c92b3 100644 --- a/demo/src/Components/SearchPage/index.stories.tsx +++ b/demo/src/Components/SearchPage/index.stories.tsx @@ -1,8 +1,11 @@ +/* eslint-disable no-console */ + import React, {useState} from 'react'; import {ISearchItem, SearchPage} from '@diplodoc/components'; import mockData from './data'; +import searchData from './searchData'; const SearchPageDemo = (args) => { const isMobile = args['Mobile']; @@ -29,6 +32,7 @@ const SearchPageDemo = (args) => { relevantOnClick={(item) => console.log('Click on like button', item)} itemsPerPage={2} totalItems={mockData.length} + searchData={searchData} />
); From 4e603819f1360acdd473525d2d43fdc5237f42f9 Mon Sep 17 00:00:00 2001 From: Alexander Minkin Date: Sat, 3 Aug 2024 17:36:12 +0300 Subject: [PATCH 05/30] feat(generative-search): add carcas to component and initial styling --- .../GenerativeSearchAnswer.scss | 39 ++++++++++ .../GenerativeSearchAnswer.tsx | 71 ++++++++++++++++++- .../GenerativeSearchAnswer/YandexGPTLogo.tsx | 22 ++++++ src/i18n/en.json | 4 ++ src/i18n/ru.json | 4 ++ 5 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 src/components/GenerativeSearchAnswer/YandexGPTLogo.tsx diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss index e69de29b..b6614172 100644 --- a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss @@ -0,0 +1,39 @@ +.generative-search-answer { + &__container { + --g-color-base-generic: #f1f4f9; + border-radius: 28px; + } + &__header { + top: 10px; + left: 27px; + display: flex; + flex-direction: column; + row-gap: 4px; + position: relative; + + > h4 { + color: var(--g-color-text-primary); + font-size: 18px; + font-weight: 700; + line-height: 22px; + 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; + } +} diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx index 68fcab42..698e972b 100644 --- a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx @@ -1,9 +1,78 @@ import React from 'react'; +import {Card} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; + +import {useTranslation} from '../../hooks'; + +import {YandexGPTLogo} from './YandexGPTLogo'; + import './GenerativeSearchAnswer.scss'; +const b = block('generative-search-answer'); + const GenerativeSearchAnswer: React.FC = () => { - return <>Нейросеть подумала и не додумала; + const {t} = useTranslation('generative-search'); + return ( + <> + +
+

{t('generative-search_title')}

+

+ Создан с помощью нейросети + + + YandexGPT + +

+
+ +

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

+
    +
  • +

    + Создайте проект. [1] Он состоит из нескольких + конфигурационных файлов и страниц с контентом. [1] +

    +
  • +
  • +

    + Запустите сборку проекта. [1][3] Для этого + используйте инструмент Builder в командной строке. [1] Укажите + обязательные ключи запуска:{' '} + + input (–i) — путь до директории проекта, output (–o) — путь до + директории для выходных данных (статических HTML) + + . [1][3] Пример команды:{' '} + yfm -i ./input-folder -o ./ouput-folder. [1] +

    +
  • +
  • +

    + Настройте сборку в YFM. [3] Для этого при + выполнении команды yfm укажите ключ запуска{' '} + --output-format=md. [3] Сборка в YFM позволяет + использовать вставки и условия видимости разделов, условия + отображения контента и подстановки переменных. [3] +

    +
  • +
  • +

    + Используйте готовый проект. [1] Проекты в формате + HTML можно использовать локально или разместить на хостинге, в + GitHub Pages или S3. [1][4] +

    +
  • +
+
+
+
Источники
+
+
+
{t('generative-search_disclaimer')}
+ + ); }; export default GenerativeSearchAnswer; diff --git a/src/components/GenerativeSearchAnswer/YandexGPTLogo.tsx b/src/components/GenerativeSearchAnswer/YandexGPTLogo.tsx new file mode 100644 index 00000000..338cab5a --- /dev/null +++ b/src/components/GenerativeSearchAnswer/YandexGPTLogo.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +export const YandexGPTLogo: React.FC = () => { + return ( + + + + + ); +}; diff --git a/src/i18n/en.json b/src/i18n/en.json index f7773a8a..35b79a0f 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -111,6 +111,10 @@ "search_mark_dislike": "dislike", "search_mark-result-text": "Thank you for your rating!" }, + "generative-search": { + "generative-search_title": "Quick answer", + "generative-search_disclaimer": "The response is generated by the Yandex GPT neural network based on the service documentation. There may be inaccuracies in it, you can check the information using links to sources." + }, "paginator": { "next": "Next page", "prev": "Previous page" diff --git a/src/i18n/ru.json b/src/i18n/ru.json index 79756887..3ea6dfe9 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -111,6 +111,10 @@ "search_mark_dislike": "вообще не про то", "search_mark-result-text": "Спасибо за оценку!" }, + "generative-search": { + "generative-search_title": "Быстрый ответ", + "generative-search_disclaimer": "Ответ сформирован нейросетью YandexGPT на основе документации сервиса. В нём могут быть неточности, проверить информацию можно по ссылкам на источники." + }, "paginator": { "next": "Следующая страница", "prev": "Предыдущая страница" From 317ee162269845f097c0300f551f163a16d07596 Mon Sep 17 00:00:00 2001 From: Uyama Date: Sat, 3 Aug 2024 19:17:30 +0300 Subject: [PATCH 06/30] feat(generative search): add carousel --- .../GenerativeSearchAnswer.scss | 47 +++++++++++++++++++ .../GenerativeSearchAnswer.tsx | 37 ++++++++++++++- 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss index b6614172..c6bce62d 100644 --- a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss @@ -36,4 +36,51 @@ column-gap: 2px; align-items: center; } + &__carousel { + display: flex; + gap: 8px; + margin-bottom: 10px; + } + + &__card { + display: flex; + box-sizing: border-box; + flex-direction: column; + justify-content: space-between; + padding: 10px; + border-radius: 16px; + width: 140px; + height: 112px; + box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.25); + background-color: rgba(255, 255, 255, 1); + } + + &__title { + font-size: 12px; + font-weight: 400; + line-height: 16px; + } + + &__link { + color: rgba(85, 126, 236, 1); + font-size: 12px; + font-weight: 400; + line-height: 16px; + display: box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + } + + &__card-wrapper { + display: flex; + gap: 13px; + } + + &__card-number { + font-size: 10px; + font-weight: 400; + line-height: 22px; + color: rgba(181, 181, 181, 1); + } } diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx index 698e972b..30c3ce41 100644 --- a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import {Card} from '@gravity-ui/uikit'; +import {Card, Link, Text} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; import {useTranslation} from '../../hooks'; @@ -11,6 +11,27 @@ import './GenerativeSearchAnswer.scss'; const b = block('generative-search-answer'); +const titles = [ + 'Быстрый старт | Diplodoc', + 'Базовая разметка | Diplodoc', + 'Сборка | Diplodoc', + 'Yandex Flavored Markdown | Diplodoc', + 'Main scenarios for Diplodoc usage | Diplodoc', +]; + +const links = [ + '.../docs/ru/quickstart', + '.../docs/ru/syntax/base', + '.../docs/ru/tools/docs/build', + '.../docs/ru/index-yfm', + '.../docs/en/how-it-work', +]; + +const combinedData = titles.map((title, index) => ({ + title, + url: links[index], +})); + const GenerativeSearchAnswer: React.FC = () => { const {t} = useTranslation('generative-search'); return ( @@ -68,6 +89,20 @@ const GenerativeSearchAnswer: React.FC = () => {
Источники
+
+ {combinedData.map((item, index) => ( +
+ {item.title} + +
+ + {item.url} + + {index + 1} +
+
+ ))} +
{t('generative-search_disclaimer')}
From 5ae8d3283952903b87388335ad595cd6cc9d0720 Mon Sep 17 00:00:00 2001 From: Alexander Minkin Date: Sun, 4 Aug 2024 11:03:17 +0300 Subject: [PATCH 07/30] revert: remove i18n attempts for now not needed for feature MVP --- .../GenerativeSearchAnswer/GenerativeSearchAnswer.tsx | 10 +++++----- src/i18n/en.json | 4 ---- src/i18n/ru.json | 4 ---- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx index 30c3ce41..d1cf4139 100644 --- a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx @@ -3,8 +3,6 @@ import React from 'react'; import {Card, Link, Text} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; -import {useTranslation} from '../../hooks'; - import {YandexGPTLogo} from './YandexGPTLogo'; import './GenerativeSearchAnswer.scss'; @@ -33,12 +31,11 @@ const combinedData = titles.map((title, index) => ({ })); const GenerativeSearchAnswer: React.FC = () => { - const {t} = useTranslation('generative-search'); return ( <>
-

{t('generative-search_title')}

+

Быстрый поиск

Создан с помощью нейросети @@ -105,7 +102,10 @@ const GenerativeSearchAnswer: React.FC = () => {

-
{t('generative-search_disclaimer')}
+
+ Ответ сформирован нейросетью YandexGPT на основе документации сервиса. В нём могут + быть неточности, проверить информацию можно поссылкам на источники. +
); }; diff --git a/src/i18n/en.json b/src/i18n/en.json index 35b79a0f..f7773a8a 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -111,10 +111,6 @@ "search_mark_dislike": "dislike", "search_mark-result-text": "Thank you for your rating!" }, - "generative-search": { - "generative-search_title": "Quick answer", - "generative-search_disclaimer": "The response is generated by the Yandex GPT neural network based on the service documentation. There may be inaccuracies in it, you can check the information using links to sources." - }, "paginator": { "next": "Next page", "prev": "Previous page" diff --git a/src/i18n/ru.json b/src/i18n/ru.json index 3ea6dfe9..79756887 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -111,10 +111,6 @@ "search_mark_dislike": "вообще не про то", "search_mark-result-text": "Спасибо за оценку!" }, - "generative-search": { - "generative-search_title": "Быстрый ответ", - "generative-search_disclaimer": "Ответ сформирован нейросетью YandexGPT на основе документации сервиса. В нём могут быть неточности, проверить информацию можно по ссылкам на источники." - }, "paginator": { "next": "Следующая страница", "prev": "Предыдущая страница" From cca8484ed85e8686add0bcda94875d47c545fc81 Mon Sep 17 00:00:00 2001 From: Alexander Minkin Date: Sun, 4 Aug 2024 17:50:07 +0300 Subject: [PATCH 08/30] feat(package.json): add markdown-it as dependency --- package-lock.json | 133 ++++++++++++++++++++++++++++++++++++++++++++-- package.json | 2 + 2 files changed, 131 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index bf578a91..a1ce70d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "langs": "^2.0.0", "lodash": "^4.17.21", "mark.ts": "^1.0.5", + "markdown-it": "^13.0.2", "react-gtm-module": "^2.0.11", "react-hotkeys-hook": "^3.3.1", "react-i18next": "11.15.6", @@ -32,6 +33,7 @@ "@gravity-ui/tsconfig": "^1.0.0", "@types/langs": "^2.0.1", "@types/lodash": "4.14.179", + "@types/markdown-it": "^14.1.2", "@types/react": "^18.2.21", "@types/react-dom": "^18.2.7", "@types/react-gtm-module": "^2.0.3", @@ -2050,12 +2052,34 @@ "integrity": "sha512-0QcCmVJ71tim3pxYDlKqV3ZxwKiC3oTKcH3NFq6sNpAK3rQkqDYheNjHsDxiwZGQbSvsfEd/+lDcWkwRd4oBDw==", "dev": true }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true + }, "node_modules/@types/lodash": { "version": "4.14.179", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.179.tgz", "integrity": "sha512-uwc1x90yCKqGcIOAT6DwOSuxnrAbpkdPsUOZtwrXb4D/6wZs+6qG7QnIawDuZWg0sWpxl+ltIKCaLoMlna678w==", "dev": true }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true + }, "node_modules/@types/minimist": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", @@ -2542,8 +2566,7 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/aria-query": { "version": "5.3.0", @@ -5425,6 +5448,14 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/linkify-it": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", + "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "dependencies": { + "uc.micro": "^1.0.1" + } + }, "node_modules/lint-staged": { "version": "12.5.0", "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.5.0.tgz", @@ -5799,6 +5830,32 @@ "resolved": "https://registry.npmjs.org/mark.ts/-/mark.ts-1.0.5.tgz", "integrity": "sha512-wi27jiU8LDo2ApTTzpqFLG8HBYYVe5L4btQuDeJX4/DNCY5sN+AsbXgmRZW4vByAit9U5nK9l9UPT/vbS8Gz0w==" }, + "node_modules/markdown-it": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.2.tgz", + "integrity": "sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "~3.0.1", + "linkify-it": "^4.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/mathml-tag-names": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", @@ -5815,6 +5872,11 @@ "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", "dev": true }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" + }, "node_modules/memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", @@ -8918,6 +8980,11 @@ "node": ">=14.17" } }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -10228,12 +10295,34 @@ "integrity": "sha512-0QcCmVJ71tim3pxYDlKqV3ZxwKiC3oTKcH3NFq6sNpAK3rQkqDYheNjHsDxiwZGQbSvsfEd/+lDcWkwRd4oBDw==", "dev": true }, + "@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true + }, "@types/lodash": { "version": "4.14.179", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.179.tgz", "integrity": "sha512-uwc1x90yCKqGcIOAT6DwOSuxnrAbpkdPsUOZtwrXb4D/6wZs+6qG7QnIawDuZWg0sWpxl+ltIKCaLoMlna678w==", "dev": true }, + "@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "requires": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true + }, "@types/minimist": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", @@ -10571,8 +10660,7 @@ "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "aria-query": { "version": "5.3.0", @@ -12648,6 +12736,14 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "linkify-it": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", + "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "requires": { + "uc.micro": "^1.0.1" + } + }, "lint-staged": { "version": "12.5.0", "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.5.0.tgz", @@ -12924,6 +13020,25 @@ "resolved": "https://registry.npmjs.org/mark.ts/-/mark.ts-1.0.5.tgz", "integrity": "sha512-wi27jiU8LDo2ApTTzpqFLG8HBYYVe5L4btQuDeJX4/DNCY5sN+AsbXgmRZW4vByAit9U5nK9l9UPT/vbS8Gz0w==" }, + "markdown-it": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.2.tgz", + "integrity": "sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==", + "requires": { + "argparse": "^2.0.1", + "entities": "~3.0.1", + "linkify-it": "^4.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "dependencies": { + "entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==" + } + } + }, "mathml-tag-names": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", @@ -12936,6 +13051,11 @@ "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", "dev": true }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" + }, "memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", @@ -15009,6 +15129,11 @@ "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, "unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", diff --git a/package.json b/package.json index fcbc3c0a..7c25dd90 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "langs": "^2.0.0", "lodash": "^4.17.21", "mark.ts": "^1.0.5", + "markdown-it": "^13.0.2", "react-gtm-module": "^2.0.11", "react-hotkeys-hook": "^3.3.1", "react-i18next": "11.15.6", @@ -101,6 +102,7 @@ "@gravity-ui/tsconfig": "^1.0.0", "@types/langs": "^2.0.1", "@types/lodash": "4.14.179", + "@types/markdown-it": "^14.1.2", "@types/react": "^18.2.21", "@types/react-dom": "^18.2.7", "@types/react-gtm-module": "^2.0.3", From 41b7d103041e39646123668b849e7fc173a67957 Mon Sep 17 00:00:00 2001 From: Uyama Date: Sun, 4 Aug 2024 18:50:14 +0300 Subject: [PATCH 09/30] feat(mardkown): add markdown parser --- .../GenerativeSearchAnswer.scss | 52 +++++- .../GenerativeSearchAnswer.tsx | 168 +++++++++--------- .../GenerativeSearchAnswer/index.ts | 1 + src/components/SearchPage/SearchPage.tsx | 43 ++--- 4 files changed, 149 insertions(+), 115 deletions(-) diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss index c6bce62d..9bc46dd2 100644 --- a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss @@ -2,10 +2,12 @@ &__container { --g-color-base-generic: #f1f4f9; border-radius: 28px; + background-color: var(--g-color-base-generic); + padding: 15px; } + &__header { - top: 10px; - left: 27px; + padding: 10px; display: flex; flex-direction: column; row-gap: 4px; @@ -42,6 +44,47 @@ margin-bottom: 10px; } + &__content { + background-color: rgba(255, 255, 255, 1); + padding: 19px 20px 8px; + + a { + text-decoration: none; + background-color: rgba(239, 242, 249, 1); + border-radius: 4px; + width: 18px; + height: 18px; + display: inline-flex; + align-items: center; + justify-content: center; + color: rgba(167, 169, 174, 1); + } + + a + a { + margin-left: 4px; + } + + strong { + font-size: 14px; + font-weight: 700; + line-height: 22px; + } + + p { + font-size: 12px; + font-weight: 500; + line-height: 16px; + } + } + + &__sources { + > h5 { + font-size: 14px; + font-weight: 400; + line-height: 16px; + } + } + &__card { display: flex; box-sizing: border-box; @@ -83,4 +126,9 @@ line-height: 22px; color: rgba(181, 181, 181, 1); } + + &__disclaimer { + margin-top: 15px; + padding: 10px; + } } diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx index d1cf4139..c149e2f7 100644 --- a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx @@ -2,105 +2,101 @@ import React from 'react'; import {Card, Link, Text} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; +import MarkdownIt from 'markdown-it'; import {YandexGPTLogo} from './YandexGPTLogo'; import './GenerativeSearchAnswer.scss'; +const md = new MarkdownIt(); + const b = block('generative-search-answer'); -const titles = [ - 'Быстрый старт | Diplodoc', - 'Базовая разметка | Diplodoc', - 'Сборка | Diplodoc', - 'Yandex Flavored Markdown | Diplodoc', - 'Main scenarios for Diplodoc usage | Diplodoc', -]; +interface Message { + content: string; + role: string; +} + +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; +} -const links = [ - '.../docs/ru/quickstart', - '.../docs/ru/syntax/base', - '.../docs/ru/tools/docs/build', - '.../docs/ru/index-yfm', - '.../docs/en/how-it-work', -]; +interface IGenerativeSearchSource { + links: string[]; + titles: string[]; +} + +const replaceReferencesWithLinks = (parsedHtml: string, links: string[]) => { + return parsedHtml.replace(/\[(\d+)\]/g, (match, index) => { + const link = links[index - 1]; + return link ? `${index}` : match; + }); +}; -const combinedData = titles.map((title, index) => ({ - title, - url: links[index], -})); +const GenerativeSearchSource: React.FC = ({links, titles}) => { + const combinedSearchInfo = links.map((link, index) => ({url: link, title: titles[index]})); -const GenerativeSearchAnswer: React.FC = () => { return ( - <> - -
-

Быстрый поиск

-

- Создан с помощью нейросети - - - YandexGPT - -

-
- -

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

-
    -
  • -

    - Создайте проект. [1] Он состоит из нескольких - конфигурационных файлов и страниц с контентом. [1] -

    -
  • -
  • -

    - Запустите сборку проекта. [1][3] Для этого - используйте инструмент Builder в командной строке. [1] Укажите - обязательные ключи запуска:{' '} - - input (–i) — путь до директории проекта, output (–o) — путь до - директории для выходных данных (статических HTML) - - . [1][3] Пример команды:{' '} - yfm -i ./input-folder -o ./ouput-folder. [1] -

    -
  • -
  • -

    - Настройте сборку в YFM. [3] Для этого при - выполнении команды yfm укажите ключ запуска{' '} - --output-format=md. [3] Сборка в YFM позволяет - использовать вставки и условия видимости разделов, условия - отображения контента и подстановки переменных. [3] -

    -
  • -
  • -

    - Используйте готовый проект. [1] Проекты в формате - HTML можно использовать локально или разместить на хостинге, в - GitHub Pages или S3. [1][4] -

    -
  • -
-
-
-
Источники
-
- {combinedData.map((item, index) => ( -
- {item.title} +
+
Источники
+
+ {combinedSearchInfo.map((item, index) => ( +
+ {item.title} -
- - {item.url} - - {index + 1} -
-
- ))} +
+ + {item.url} + + {index + 1} +
-
+ ))} +
+
+ ); +}; + +const GenerativeSearchAnswer: React.FC = ({ + message, + links, + titles, + // final_search_query, + // is_answer_rejected, + // is_bullet_answer, + // search_reqid, + // reqid, +}) => { + const {content} = message; + const parserdContent = md.render(content); + const updatedHtmlContent = replaceReferencesWithLinks(parserdContent, links); + + return ( + <> + + +
+

Быстрый поиск

+

+ Создан с помощью нейросети + + + YandexGPT + +

+
+ +
+ + +
Ответ сформирован нейросетью YandexGPT на основе документации сервиса. В нём могут diff --git a/src/components/GenerativeSearchAnswer/index.ts b/src/components/GenerativeSearchAnswer/index.ts index cc7c44be..ab7364ab 100644 --- a/src/components/GenerativeSearchAnswer/index.ts +++ b/src/components/GenerativeSearchAnswer/index.ts @@ -1 +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 71bc1205..4a2eaa21 100644 --- a/src/components/SearchPage/SearchPage.tsx +++ b/src/components/SearchPage/SearchPage.tsx @@ -4,7 +4,7 @@ import {Button, Loader, TextInput} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; import {useTranslation} from '../../hooks'; -import {GenerativeSearchAnswer} from '../GenerativeSearchAnswer'; +import {GenerativeSearchAnswer, IGenerativeSearch} from '../GenerativeSearchAnswer'; import {Paginator, PaginatorProps} from '../Paginator'; import {ISearchItem, SearchItem, SearchOnClickProps} from '../SearchItem'; @@ -16,22 +16,6 @@ interface Loading { loading?: boolean; } -interface Message { - content: string; - role: string; -} - -interface ISearchData { - message: Message; - links: string[]; - titles: string[]; - final_search_query: string; - is_answer_rejected: boolean; - is_bullet_answer: boolean; - search_reqid: string; - reqid: string; -} - interface InputProps { query: string; onSubmit: (query: string) => void; @@ -49,12 +33,19 @@ interface SearchPageProps extends Loading { page: number; isMobile?: boolean; loading?: boolean; - searchData: ISearchData; +} + +interface GenerativeSearchProps { + generativeSearchData: IGenerativeSearch; } type RenderFoundProps = SearchPageProps & SearchOnClickProps & PaginatorProps; -type SearchPageInnerProps = SearchPageProps & SearchOnClickProps & InputProps & PaginatorProps; +type SearchPageInnerProps = SearchPageProps & + SearchOnClickProps & + InputProps & + PaginatorProps & + GenerativeSearchProps; const FoundBlock: React.FC = ({ items, @@ -69,12 +60,11 @@ const FoundBlock: React.FC = ({ isMobile, }) => { const {t} = useTranslation('search'); + return (

{t('search_request-query')}

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

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

{t('search_not-found-text')}
@@ -171,7 +158,7 @@ const SearchPage = ({ irrelevantOnClick, relevantOnClick, loading, - searchData, + generativeSearchData, }: SearchPageInnerProps) => { const inputRef = useRef(null); const [currentQuery, setCurrentQuery] = useState(query); @@ -188,6 +175,9 @@ const SearchPage = ({ }} />
+
+ +
{items?.length && query ? ( ) : ( From 97109331b82307c0e2df13eb2a13a6fdbe213aa1 Mon Sep 17 00:00:00 2001 From: Uyama Date: Wed, 7 Aug 2024 18:19:34 +0300 Subject: [PATCH 10/30] chore(mocks): changed mock value name --- demo/src/Components/SearchPage/index.stories.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/src/Components/SearchPage/index.stories.tsx b/demo/src/Components/SearchPage/index.stories.tsx index d36c92b3..22dda504 100644 --- a/demo/src/Components/SearchPage/index.stories.tsx +++ b/demo/src/Components/SearchPage/index.stories.tsx @@ -5,7 +5,7 @@ import React, {useState} from 'react'; import {ISearchItem, SearchPage} from '@diplodoc/components'; import mockData from './data'; -import searchData from './searchData'; +import generativeSearchData from './searchData'; const SearchPageDemo = (args) => { const isMobile = args['Mobile']; @@ -32,7 +32,7 @@ const SearchPageDemo = (args) => { relevantOnClick={(item) => console.log('Click on like button', item)} itemsPerPage={2} totalItems={mockData.length} - searchData={searchData} + generativeSearchData={generativeSearchData} />
); From 55914bc290326ffab0ff6dd34e11b051d4aa5a40 Mon Sep 17 00:00:00 2001 From: Uyama Date: Sat, 10 Aug 2024 19:27:21 +0300 Subject: [PATCH 11/30] feat: add generative search different reactions --- .../GenerativeSearchAnswer.scss | 100 +++++++++- .../GenerativeSearchAnswer.tsx | 185 ++++++++++++++---- 2 files changed, 248 insertions(+), 37 deletions(-) diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss index 9bc46dd2..bde9307c 100644 --- a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss @@ -1,9 +1,43 @@ .generative-search-answer { + &__loader { + height: 74px; + border-radius: 8px; + display: flex; + gap: 8px; + align-items: center; + padding: 26px; + overflow: hidden; + position: relative; + } + &__container { --g-color-base-generic: #f1f4f9; border-radius: 28px; background-color: var(--g-color-base-generic); 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%, + rgba(255, 255, 255, 0.168) 100% + ); + pointer-events: none; + transform: rotate(3.74deg); } &__header { @@ -11,7 +45,7 @@ display: flex; flex-direction: column; row-gap: 4px; - position: relative; + z-index: 5; > h4 { color: var(--g-color-text-primary); @@ -46,7 +80,10 @@ &__content { background-color: rgba(255, 255, 255, 1); - padding: 19px 20px 8px; + padding: 19px 20px; + border-radius: 26px; + position: relative; + z-index: 5; a { text-decoration: none; @@ -78,6 +115,12 @@ } &__sources { + display: flex; + flex-direction: column; + gap: 17px; + margin-bottom: 28px; + margin-top: 43px; + > h5 { font-size: 14px; font-weight: 400; @@ -102,6 +145,12 @@ font-size: 12px; font-weight: 400; line-height: 16px; + + display: box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + text-overflow: ellipsis; + white-space: normal; } &__link { @@ -109,6 +158,7 @@ font-size: 12px; font-weight: 400; line-height: 16px; + display: box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; @@ -131,4 +181,50 @@ margin-top: 15px; padding: 10px; } + + &__rating-container { + display: flex; + gap: 8px; + min-height: 35px; + } + + &__rating-button { + border: 1px solid rgba(181, 181, 181, 1); + background-color: transparent; + + &:hover { + background-color: rgba(255, 255, 255, 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: 10; + background: linear-gradient(0deg, #eff2f8 -0.93%, rgba(241, 244, 249, 0.03) 93.43%); + } + + &__toggle-button { + background-color: rgba(255, 255, 255, 1); + position: absolute; + bottom: 19px; + border: 1px solid transparent; + + &: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 index c149e2f7..223ddb1d 100644 --- a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx @@ -1,6 +1,7 @@ -import React from 'react'; +import React, {ReactNode, useState} from 'react'; -import {Card, Link, Text} from '@gravity-ui/uikit'; +import {ChevronDown, ThumbsDown, ThumbsUp} from '@gravity-ui/icons'; +import {Button, Icon, Link, Text} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; import MarkdownIt from 'markdown-it'; @@ -40,12 +41,23 @@ const replaceReferencesWithLinks = (parsedHtml: string, links: string[]) => { }); }; +const GenerativeSearchDisclaimer: React.FC = () => { + return ( +
+ + Ответ сформирован нейросетью YandexGPT на основе документации сервиса. В нём могут + быть неточности, проверить информацию можно поссылкам на источники. + +
+ ); +}; + const GenerativeSearchSource: React.FC = ({links, titles}) => { const combinedSearchInfo = links.map((link, index) => ({url: link, title: titles[index]})); return (
-
Источники
+ Источники
{combinedSearchInfo.map((item, index) => (
@@ -64,45 +76,148 @@ const GenerativeSearchSource: React.FC = ({links, title ); }; -const GenerativeSearchAnswer: React.FC = ({ - message, - links, - titles, - // final_search_query, - // is_answer_rejected, - // is_bullet_answer, - // search_reqid, - // reqid, +const GenerativSearchHeader = () => { + return ( +
+

Быстрый ответ

+

+ Создан с помощью нейросети + + + YandexGPT + +

+
+ ); +}; + +interface IGenerativeSearchWrapper { + children: ReactNode; + isExpanded?: boolean; +} + +const GenerativeSearchWrapperBlock: React.FC = ({ + children, + isExpanded, +}) => { + return ( + <> +
+
+ + {children} +
+ + ); +}; + +const GenerativeSearchWithoutContentBlock = () => { + return ( + +
+

Не удалось найти информацию

+

Сформулируйте запрос иначе или спросите что-нибудь ещё.

+
+
+ ); +}; + +const GenerativeSearchErrorBlock = () => { + return ( + +
+

Что-то пошло не так. Попробуйте ещё раз или обновите страницу.

+
+
+ ); +}; + +interface IGenerativeSearchI { + generativeSearchData: IGenerativeSearch; + generativeSearchLoading: boolean; + generativeSearchError: boolean; +} + +const GenerativeSearchAnswer: React.FC = ({ + generativeSearchData, + generativeSearchLoading, + generativeSearchError, }) => { + const [isSubmitted, setIsSubmitted] = useState(false); + const [isExpanded, setIsExpanded] = useState(false); + + const handleRatingClick = () => { + setIsSubmitted(!isSubmitted); + }; + + const toggleExpand = () => { + setIsExpanded(!isExpanded); + }; + + if (generativeSearchLoading) { + return ; + } + + if (generativeSearchError) { + return ; + } + + const {message, links, titles} = generativeSearchData; + const {content} = message; const parserdContent = md.render(content); const updatedHtmlContent = replaceReferencesWithLinks(parserdContent, links); return ( - <> - - -
-

Быстрый поиск

-

- Создан с помощью нейросети - - - YandexGPT - -

+
+ +
+ +
+ {isSubmitted ? ( + Спасибо, что помогаете делать технологию лучше + ) : ( + <> + + + + )} +
+ {!isExpanded && ( +
+
- -
- - - - -
- Ответ сформирован нейросетью YandexGPT на основе документации сервиса. В нём могут - быть неточности, проверить информацию можно поссылкам на источники. -
- + )} + + +
); }; From 86999b4eda3ecad2ab97545f8f9009c05062f3cb Mon Sep 17 00:00:00 2001 From: Uyama Date: Sun, 11 Aug 2024 14:58:33 +0300 Subject: [PATCH 12/30] fix(generative search): props drilling --- demo/src/Components/SearchPage/index.stories.tsx | 16 ++++++++++++++++ src/components/SearchPage/SearchPage.tsx | 15 ++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/demo/src/Components/SearchPage/index.stories.tsx b/demo/src/Components/SearchPage/index.stories.tsx index 22dda504..80e16713 100644 --- a/demo/src/Components/SearchPage/index.stories.tsx +++ b/demo/src/Components/SearchPage/index.stories.tsx @@ -9,6 +9,10 @@ import generativeSearchData from './searchData'; const SearchPageDemo = (args) => { const isMobile = args['Mobile']; + const generativeSearchLoading = args['GenerativeSearchLoading']; + const generativeSearchError = args['GenerativeSearchError']; + const generativeSearchNoData = args['GenerativeSearchNoData']; + const [page, setPage] = useState(1); const [items, setItems] = useState(getItems(page, mockData)); @@ -33,6 +37,9 @@ const SearchPageDemo = (args) => { itemsPerPage={2} totalItems={mockData.length} generativeSearchData={generativeSearchData} + generativeSearchLoading={generativeSearchLoading} + generativeSearchError={generativeSearchError} + generativeSearchNoData={generativeSearchNoData} />
); @@ -45,6 +52,15 @@ export default { Mobile: { control: 'boolean', }, + GenerativeSearchLoading: { + control: 'boolean', + }, + GenerativeSearchError: { + control: 'boolean', + }, + generativeSearchNoData: { + control: 'boolean', + }, }, }; diff --git a/src/components/SearchPage/SearchPage.tsx b/src/components/SearchPage/SearchPage.tsx index 4a2eaa21..85a621f5 100644 --- a/src/components/SearchPage/SearchPage.tsx +++ b/src/components/SearchPage/SearchPage.tsx @@ -4,6 +4,7 @@ import {Button, Loader, TextInput} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; import {useTranslation} from '../../hooks'; +import {ChatBot} from '../ChatBot'; import {GenerativeSearchAnswer, IGenerativeSearch} from '../GenerativeSearchAnswer'; import {Paginator, PaginatorProps} from '../Paginator'; import {ISearchItem, SearchItem, SearchOnClickProps} from '../SearchItem'; @@ -37,6 +38,9 @@ interface SearchPageProps extends Loading { interface GenerativeSearchProps { generativeSearchData: IGenerativeSearch; + generativeSearchLoading: boolean; + generativeSearchError: boolean; + generativeSearchNoData: boolean; } type RenderFoundProps = SearchPageProps & SearchOnClickProps & PaginatorProps; @@ -159,6 +163,8 @@ const SearchPage = ({ relevantOnClick, loading, generativeSearchData, + generativeSearchLoading, + generativeSearchError, }: SearchPageInnerProps) => { const inputRef = useRef(null); const [currentQuery, setCurrentQuery] = useState(query); @@ -176,7 +182,13 @@ const SearchPage = ({ />
- +
{items?.length && query ? ( @@ -198,6 +210,7 @@ const SearchPage = ({ )}
+
); }; From 73f2765fc04b021b8217560bb2206b77b0a5f215 Mon Sep 17 00:00:00 2001 From: Uyama Date: Sun, 11 Aug 2024 16:41:14 +0300 Subject: [PATCH 13/30] feat (chatbot): add components of chatbot --- src/components/ChatBot/ChatBot.scss | 144 ++++++++++++++++++ src/components/ChatBot/ChatBot.tsx | 128 ++++++++++++++++ src/components/ChatBot/ChatBotIcon.tsx | 48 ++++++ src/components/ChatBot/CloseIcon.tsx | 38 +++++ src/components/ChatBot/index.ts | 2 + .../GenerativeSearchAnswer/YandexGPTLogo.tsx | 23 ++- 6 files changed, 378 insertions(+), 5 deletions(-) create mode 100644 src/components/ChatBot/ChatBot.scss create mode 100644 src/components/ChatBot/ChatBot.tsx create mode 100644 src/components/ChatBot/ChatBotIcon.tsx create mode 100644 src/components/ChatBot/CloseIcon.tsx create mode 100644 src/components/ChatBot/index.ts diff --git a/src/components/ChatBot/ChatBot.scss b/src/components/ChatBot/ChatBot.scss new file mode 100644 index 00000000..27fea38d --- /dev/null +++ b/src/components/ChatBot/ChatBot.scss @@ -0,0 +1,144 @@ +@import '../../styles/variables'; +@import '../../styles/mixins'; + +.dc-chat-bot { + &__chat-bot-icon { + position: fixed; + bottom: 20px; + right: 25px; + + &:hover { + cursor: pointer; + } + } + + &__modal { + box-sizing: border-box; + position: fixed; + top: 8px; + bottom: 60px; + right: 25px; + width: 377px; + height: calc(100vh - 68px); + display: flex; + align-items: center; + justify-content: center; + z-index: 50; + opacity: 0; + visibility: hidden; + + &_open { + opacity: 1; + visibility: visible; + } + } + + &__content { + box-sizing: border-box; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgb(255, 255, 255); + border-radius: 24px 24px 4px; + border: 1.5px solid rgba(243, 238, 249, 1); + + display: flex; + flex-direction: column; + color: rgb(0, 0, 0); + } + + &__header { + padding: 24px; + background-color: rgba(243, 238, 249, 1); + display: flex; + align-items: center; + justify-content: space-between; + box-shadow: 0px 2px 6px 2px rgba(0, 0, 0, 0.15); + } + + &__header-wrapper { + display: flex; + align-items: center; + gap: 3px; + } + + &__header-title { + > h1 { + font-size: 14px; + font-weight: 500; + line-height: 16px; + margin: 0; + } + + > p { + font-size: 12px; + font-weight: 400; + line-height: 15px; + margin: 0; + } + } + + &__header-icons-wrapper { + display: flex; + gap: 8px; + } + + &__message-container { + padding: 24px; + display: flex; + flex-direction: column; + gap: 32px; + flex: 1 1 0%; + overflow-y: auto; + } + + &__message-card { + background-color: rgba(240, 231, 250, 1); + padding: 12px; + + &_role_assistant { + border-radius: 16px 16px 16px 4px; + margin-right: 20px; + } + + &_role_user { + border-radius: 16px 16px 4px; + margin-left: 20px; + } + } + + &__footer-input-wrapper { + display: flex; + gap: 8px; + padding: 24px; + border: 1px solid rgba(243, 238, 249, 1); + + > input { + flex: 1 1 0%; + border: none; + + &:focus { + border: hidden; + outline: none; + } + } + } + + &__footer-send-message { + :hover { + cursor: pointer; + } + } + + &__footer-disclaimer { + background-color: rgba(243, 238, 249, 1); + padding: 24px 10px; + + > p { + font-size: 8px; + font-weight: 400; + line-height: 10px; + margin: 0; + } + } +} diff --git a/src/components/ChatBot/ChatBot.tsx b/src/components/ChatBot/ChatBot.tsx new file mode 100644 index 00000000..8ef96f8c --- /dev/null +++ b/src/components/ChatBot/ChatBot.tsx @@ -0,0 +1,128 @@ +import React, {useState} from 'react'; + +import {ArrowShapeRight, FileText, Person} from '@gravity-ui/icons'; +import block from 'bem-cn-lite'; +import MarkdownIt from 'markdown-it'; + +import {YandexGPTLogo} from '../GenerativeSearchAnswer/YandexGPTLogo'; + +import ChatBotIcon from './ChatBotIcon'; + +import './ChatBot.scss'; + +const md = new MarkdownIt(); +const b = block('dc-chat-bot'); + +interface IChatMessage { + content: string; + role: string; +} + +interface ChatBotProps { + messages?: IChatMessage[]; +} + +const ChatMessage: React.FC = ({content, role}) => { + const parserdContent = md.render(content); + + return ( +
+
+
+ ); +}; + +const messagesMocks = [ + { + content: + 'Привет! Я твой помощник, работаю на основе нейросети YandexGPT. Задавай мне вопросы по работе этого сервиса, и я быстро отвечу. Кратко и по делу. Если вдруг мой ответ тебе не поможет, я подскажу, как связаться с поддержкой. Давай начнём?', + role: 'assistant', + }, + { + content: 'Как работать с YFM', + role: 'user', + }, + { + content: + 'Чтобы работать с YFM, можно следовать таким рекомендациям:\n\n* **Создайте проект**. [1] Он состоит из нескольких конфигурационных файлов и страниц с контентом. [1]\n\n* **Запустите сборку проекта**. [1][3] Для этого используйте инструмент Builder в командной строке. [1] Укажите обязательные ключи запуска: **input (–i) — путь до директории проекта, output (–o) — путь до директории для выходных данных (статических HTML)**. [1][3] Пример команды: `yfm -i ./input-folder -o ./ouput-folder`. [1]\n\n* **Настройте сборку в YFM**. [3] Для этого при выполнении команды yfm укажите ключ запуска **--output-format=md**. [3] Сборка в YFM позволяет использовать вставки и условия видимости разделов, условия отображения контента и подстановки переменных. [3]\n\n* **Используйте готовый проект**. [1] Проекты в формате HTML можно использовать локально или разместить на хостинге, в GitHub Pages или S3. [1][4]', + role: 'assistant', + }, +]; + +const ChatBot: React.FC = () => { + const [isOpen, setIsOpen] = useState(false); + const [isHover, setHover] = useState(false); + const [inputValue, setInputValue] = useState(''); + + const handleInputChange = (event: React.ChangeEvent) => { + setInputValue(event.target.value); + }; + + const handleSendMessage = () => { + setInputValue(''); + }; + + return ( +
+ {isOpen && ( +
+
+
+
+ +
+

Бот-помощник

+

24/7 на связи с вами

+
+
+
+ + +
+
+
+ {messagesMocks.map((message, index) => ( + + ))} +
+
+
+ + +
+
+

+ Ответы в чат-боте формируются нейросетью YandexGPT на основе + документации сервиса В них могут быть неточности, проверить + информацию можно в источниках. +

+
+
+
+
+ )} + + +
+ ); +}; + +export default ChatBot; diff --git a/src/components/ChatBot/ChatBotIcon.tsx b/src/components/ChatBot/ChatBotIcon.tsx new file mode 100644 index 00000000..c92a30c5 --- /dev/null +++ b/src/components/ChatBot/ChatBotIcon.tsx @@ -0,0 +1,48 @@ +import React from 'react'; + +interface IChatBotIcon { + handleHover: (value: boolean) => void; + handleClick: (updater: (value: boolean) => boolean) => void; + isHovered: boolean; + className?: string; +} + +const ChatBotIcon: React.FC = ({handleClick, handleHover, isHovered, className}) => { + return ( + handleClick((value) => !value)} + onMouseEnter={() => handleHover(true)} + onMouseLeave={() => handleHover(false)} + width="36" + height="33" + viewBox="0 0 36 33" + fill="none" + xmlns="http://www.w3.org/2000/svg" + className={className} + > + + + + + + + + + + + + ); +}; + +export default ChatBotIcon; diff --git a/src/components/ChatBot/CloseIcon.tsx b/src/components/ChatBot/CloseIcon.tsx new file mode 100644 index 00000000..8fe01ca5 --- /dev/null +++ b/src/components/ChatBot/CloseIcon.tsx @@ -0,0 +1,38 @@ +import React from 'react'; + +interface ICloseIcon { + classname?: string; +} + +const CloseIcon: React.FC = () => { + return ( + + + + + + + + + + + + ); +}; + +export default CloseIcon; diff --git a/src/components/ChatBot/index.ts b/src/components/ChatBot/index.ts new file mode 100644 index 00000000..6925ce4c --- /dev/null +++ b/src/components/ChatBot/index.ts @@ -0,0 +1,2 @@ +export {default as ChatBot} from './ChatBot'; +export * from './ChatBot'; diff --git a/src/components/GenerativeSearchAnswer/YandexGPTLogo.tsx b/src/components/GenerativeSearchAnswer/YandexGPTLogo.tsx index 338cab5a..467312cd 100644 --- a/src/components/GenerativeSearchAnswer/YandexGPTLogo.tsx +++ b/src/components/GenerativeSearchAnswer/YandexGPTLogo.tsx @@ -1,21 +1,34 @@ import React from 'react'; -export const YandexGPTLogo: React.FC = () => { +interface IYandexGPTLogo { + className?: string; + width?: number; + height?: number; + fill?: string; +} + +export const YandexGPTLogo: React.FC = ({ + className, + width = '10', + height = '11', + fill = 'black', +}) => { return ( ); From 05a90989e9a8f5b7fdc8a50fcaae7b9935f4bc9f Mon Sep 17 00:00:00 2001 From: Uyama Date: Mon, 12 Aug 2024 22:15:01 +0300 Subject: [PATCH 14/30] chore(chatbot): remove unnecessary component ChatBot --- src/components/ChatBot/ChatBot.scss | 144 ----------------------- src/components/ChatBot/ChatBot.tsx | 128 -------------------- src/components/ChatBot/ChatBotIcon.tsx | 48 -------- src/components/ChatBot/CloseIcon.tsx | 38 ------ src/components/ChatBot/index.ts | 2 - src/components/SearchPage/SearchPage.tsx | 2 - 6 files changed, 362 deletions(-) delete mode 100644 src/components/ChatBot/ChatBot.scss delete mode 100644 src/components/ChatBot/ChatBot.tsx delete mode 100644 src/components/ChatBot/ChatBotIcon.tsx delete mode 100644 src/components/ChatBot/CloseIcon.tsx delete mode 100644 src/components/ChatBot/index.ts diff --git a/src/components/ChatBot/ChatBot.scss b/src/components/ChatBot/ChatBot.scss deleted file mode 100644 index 27fea38d..00000000 --- a/src/components/ChatBot/ChatBot.scss +++ /dev/null @@ -1,144 +0,0 @@ -@import '../../styles/variables'; -@import '../../styles/mixins'; - -.dc-chat-bot { - &__chat-bot-icon { - position: fixed; - bottom: 20px; - right: 25px; - - &:hover { - cursor: pointer; - } - } - - &__modal { - box-sizing: border-box; - position: fixed; - top: 8px; - bottom: 60px; - right: 25px; - width: 377px; - height: calc(100vh - 68px); - display: flex; - align-items: center; - justify-content: center; - z-index: 50; - opacity: 0; - visibility: hidden; - - &_open { - opacity: 1; - visibility: visible; - } - } - - &__content { - box-sizing: border-box; - width: 100%; - height: 100%; - overflow: auto; - background-color: rgb(255, 255, 255); - border-radius: 24px 24px 4px; - border: 1.5px solid rgba(243, 238, 249, 1); - - display: flex; - flex-direction: column; - color: rgb(0, 0, 0); - } - - &__header { - padding: 24px; - background-color: rgba(243, 238, 249, 1); - display: flex; - align-items: center; - justify-content: space-between; - box-shadow: 0px 2px 6px 2px rgba(0, 0, 0, 0.15); - } - - &__header-wrapper { - display: flex; - align-items: center; - gap: 3px; - } - - &__header-title { - > h1 { - font-size: 14px; - font-weight: 500; - line-height: 16px; - margin: 0; - } - - > p { - font-size: 12px; - font-weight: 400; - line-height: 15px; - margin: 0; - } - } - - &__header-icons-wrapper { - display: flex; - gap: 8px; - } - - &__message-container { - padding: 24px; - display: flex; - flex-direction: column; - gap: 32px; - flex: 1 1 0%; - overflow-y: auto; - } - - &__message-card { - background-color: rgba(240, 231, 250, 1); - padding: 12px; - - &_role_assistant { - border-radius: 16px 16px 16px 4px; - margin-right: 20px; - } - - &_role_user { - border-radius: 16px 16px 4px; - margin-left: 20px; - } - } - - &__footer-input-wrapper { - display: flex; - gap: 8px; - padding: 24px; - border: 1px solid rgba(243, 238, 249, 1); - - > input { - flex: 1 1 0%; - border: none; - - &:focus { - border: hidden; - outline: none; - } - } - } - - &__footer-send-message { - :hover { - cursor: pointer; - } - } - - &__footer-disclaimer { - background-color: rgba(243, 238, 249, 1); - padding: 24px 10px; - - > p { - font-size: 8px; - font-weight: 400; - line-height: 10px; - margin: 0; - } - } -} diff --git a/src/components/ChatBot/ChatBot.tsx b/src/components/ChatBot/ChatBot.tsx deleted file mode 100644 index 8ef96f8c..00000000 --- a/src/components/ChatBot/ChatBot.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import React, {useState} from 'react'; - -import {ArrowShapeRight, FileText, Person} from '@gravity-ui/icons'; -import block from 'bem-cn-lite'; -import MarkdownIt from 'markdown-it'; - -import {YandexGPTLogo} from '../GenerativeSearchAnswer/YandexGPTLogo'; - -import ChatBotIcon from './ChatBotIcon'; - -import './ChatBot.scss'; - -const md = new MarkdownIt(); -const b = block('dc-chat-bot'); - -interface IChatMessage { - content: string; - role: string; -} - -interface ChatBotProps { - messages?: IChatMessage[]; -} - -const ChatMessage: React.FC = ({content, role}) => { - const parserdContent = md.render(content); - - return ( -
-
-
- ); -}; - -const messagesMocks = [ - { - content: - 'Привет! Я твой помощник, работаю на основе нейросети YandexGPT. Задавай мне вопросы по работе этого сервиса, и я быстро отвечу. Кратко и по делу. Если вдруг мой ответ тебе не поможет, я подскажу, как связаться с поддержкой. Давай начнём?', - role: 'assistant', - }, - { - content: 'Как работать с YFM', - role: 'user', - }, - { - content: - 'Чтобы работать с YFM, можно следовать таким рекомендациям:\n\n* **Создайте проект**. [1] Он состоит из нескольких конфигурационных файлов и страниц с контентом. [1]\n\n* **Запустите сборку проекта**. [1][3] Для этого используйте инструмент Builder в командной строке. [1] Укажите обязательные ключи запуска: **input (–i) — путь до директории проекта, output (–o) — путь до директории для выходных данных (статических HTML)**. [1][3] Пример команды: `yfm -i ./input-folder -o ./ouput-folder`. [1]\n\n* **Настройте сборку в YFM**. [3] Для этого при выполнении команды yfm укажите ключ запуска **--output-format=md**. [3] Сборка в YFM позволяет использовать вставки и условия видимости разделов, условия отображения контента и подстановки переменных. [3]\n\n* **Используйте готовый проект**. [1] Проекты в формате HTML можно использовать локально или разместить на хостинге, в GitHub Pages или S3. [1][4]', - role: 'assistant', - }, -]; - -const ChatBot: React.FC = () => { - const [isOpen, setIsOpen] = useState(false); - const [isHover, setHover] = useState(false); - const [inputValue, setInputValue] = useState(''); - - const handleInputChange = (event: React.ChangeEvent) => { - setInputValue(event.target.value); - }; - - const handleSendMessage = () => { - setInputValue(''); - }; - - return ( -
- {isOpen && ( -
-
-
-
- -
-

Бот-помощник

-

24/7 на связи с вами

-
-
-
- - -
-
-
- {messagesMocks.map((message, index) => ( - - ))} -
-
-
- - -
-
-

- Ответы в чат-боте формируются нейросетью YandexGPT на основе - документации сервиса В них могут быть неточности, проверить - информацию можно в источниках. -

-
-
-
-
- )} - - -
- ); -}; - -export default ChatBot; diff --git a/src/components/ChatBot/ChatBotIcon.tsx b/src/components/ChatBot/ChatBotIcon.tsx deleted file mode 100644 index c92a30c5..00000000 --- a/src/components/ChatBot/ChatBotIcon.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; - -interface IChatBotIcon { - handleHover: (value: boolean) => void; - handleClick: (updater: (value: boolean) => boolean) => void; - isHovered: boolean; - className?: string; -} - -const ChatBotIcon: React.FC = ({handleClick, handleHover, isHovered, className}) => { - return ( - handleClick((value) => !value)} - onMouseEnter={() => handleHover(true)} - onMouseLeave={() => handleHover(false)} - width="36" - height="33" - viewBox="0 0 36 33" - fill="none" - xmlns="http://www.w3.org/2000/svg" - className={className} - > - - - - - - - - - - - - ); -}; - -export default ChatBotIcon; diff --git a/src/components/ChatBot/CloseIcon.tsx b/src/components/ChatBot/CloseIcon.tsx deleted file mode 100644 index 8fe01ca5..00000000 --- a/src/components/ChatBot/CloseIcon.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; - -interface ICloseIcon { - classname?: string; -} - -const CloseIcon: React.FC = () => { - return ( - - - - - - - - - - - - ); -}; - -export default CloseIcon; diff --git a/src/components/ChatBot/index.ts b/src/components/ChatBot/index.ts deleted file mode 100644 index 6925ce4c..00000000 --- a/src/components/ChatBot/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export {default as ChatBot} from './ChatBot'; -export * from './ChatBot'; diff --git a/src/components/SearchPage/SearchPage.tsx b/src/components/SearchPage/SearchPage.tsx index 85a621f5..48f9a0d4 100644 --- a/src/components/SearchPage/SearchPage.tsx +++ b/src/components/SearchPage/SearchPage.tsx @@ -4,7 +4,6 @@ import {Button, Loader, TextInput} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; import {useTranslation} from '../../hooks'; -import {ChatBot} from '../ChatBot'; import {GenerativeSearchAnswer, IGenerativeSearch} from '../GenerativeSearchAnswer'; import {Paginator, PaginatorProps} from '../Paginator'; import {ISearchItem, SearchItem, SearchOnClickProps} from '../SearchItem'; @@ -210,7 +209,6 @@ const SearchPage = ({ )}
-
); }; From 1f02a65d8cc096ab6631fd62906233e82017602c Mon Sep 17 00:00:00 2001 From: Uyama Date: Wed, 14 Aug 2024 20:23:43 +0300 Subject: [PATCH 15/30] feat(carousel): add carousel --- .../GenerativeSearchAnswer.scss | 126 +++++++++------ .../GenerativeSearchAnswer.tsx | 145 ++++++++++++------ .../GenerativeSearchAnswer/useCarousel.tsx | 54 +++++++ 3 files changed, 235 insertions(+), 90 deletions(-) create mode 100644 src/components/GenerativeSearchAnswer/useCarousel.tsx diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss index bde9307c..f99debbb 100644 --- a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss @@ -1,6 +1,7 @@ .generative-search-answer { + $block: &; + &__loader { - height: 74px; border-radius: 8px; display: flex; gap: 8px; @@ -11,7 +12,7 @@ } &__container { - --g-color-base-generic: #f1f4f9; + --g-color-base-generic: rgba(241, 244, 249, 1); border-radius: 28px; background-color: var(--g-color-base-generic); padding: 15px; @@ -34,7 +35,7 @@ 35.52% 35.54% at 54.12% 47.69%, rgba(153, 181, 251, 0.4) 23.5%, rgba(159, 186, 251, 0.4) 40%, - rgba(255, 255, 255, 0.168) 100% + var(--g-color-base-generic) 100% ); pointer-events: none; transform: rotate(3.74deg); @@ -53,6 +54,7 @@ font-weight: 700; line-height: 22px; margin: 0; + z-index: 5; } > p { color: var(--g-color-text-primary); @@ -60,6 +62,7 @@ font-weight: 400; line-height: 16px; margin: 0; + z-index: 5; } } @@ -72,18 +75,15 @@ column-gap: 2px; align-items: center; } - &__carousel { - display: flex; - gap: 8px; - margin-bottom: 10px; - } &__content { - background-color: rgba(255, 255, 255, 1); + background-color: var(--g-color-base-background); + display: block; padding: 19px 20px; border-radius: 26px; position: relative; z-index: 5; + margin-bottom: 28px; a { text-decoration: none; @@ -119,58 +119,94 @@ flex-direction: column; gap: 17px; margin-bottom: 28px; - margin-top: 43px; + margin-top: 35px; - > h5 { + > h3 { font-size: 14px; font-weight: 400; line-height: 16px; + margin: 0; } } - &__card { + &__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; - width: 140px; - height: 112px; - box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.25); - background-color: rgba(255, 255, 255, 1); - } - &__title { - font-size: 12px; - font-weight: 400; - line-height: 16px; + min-width: 140px; + min-height: 112px; - display: box; - -webkit-line-clamp: 3; - -webkit-box-orient: vertical; - text-overflow: ellipsis; - white-space: normal; - } + box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.25); + background-color: var(--g-color-base-background); - &__link { - color: rgba(85, 126, 236, 1); - font-size: 12px; - font-weight: 400; - line-height: 16px; + p { + font-size: 12px; + font-weight: 400; + line-height: 16px; + margin: 0; - display: box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; - } + max-width: 100%; + max-height: calc(16px * 3); + overflow: hidden; + word-break: break-all; + } - &__card-wrapper { - display: flex; - gap: 13px; + a { + 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; @@ -185,7 +221,7 @@ &__rating-container { display: flex; gap: 8px; - min-height: 35px; + min-height: 45px; } &__rating-button { @@ -193,7 +229,7 @@ background-color: transparent; &:hover { - background-color: rgba(255, 255, 255, 1); + background-color: var(--g-color-base-background); } &:active { @@ -210,7 +246,11 @@ width: 100%; height: 93px; z-index: 10; - background: linear-gradient(0deg, #eff2f8 -0.93%, rgba(241, 244, 249, 0.03) 93.43%); + background: linear-gradient( + 0deg, + var(--g-color-base-background) -0.93%, + rgba(241, 244, 249, 0.03) 93.43% + ); } &__toggle-button { diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx index 223ddb1d..4ef8a627 100644 --- a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx @@ -1,16 +1,18 @@ import React, {ReactNode, useState} from 'react'; -import {ChevronDown, ThumbsDown, ThumbsUp} from '@gravity-ui/icons'; -import {Button, Icon, Link, Text} from '@gravity-ui/uikit'; +import {ChevronDown, ChevronLeft, ChevronRight, ThumbsDown, ThumbsUp} from '@gravity-ui/icons'; +import {Button, Icon} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; -import MarkdownIt from 'markdown-it'; + +import {useTranslation} from '../../hooks'; +import {simplifyUrl} from '../../utils'; +import {HTML} from '../HTML'; import {YandexGPTLogo} from './YandexGPTLogo'; +import {useCarousel} from './useCarousel'; import './GenerativeSearchAnswer.scss'; -const md = new MarkdownIt(); - const b = block('generative-search-answer'); interface Message { @@ -34,54 +36,84 @@ interface IGenerativeSearchSource { titles: string[]; } -const replaceReferencesWithLinks = (parsedHtml: string, links: string[]) => { - return parsedHtml.replace(/\[(\d+)\]/g, (match, index) => { - const link = links[index - 1]; - return link ? `${index}` : match; - }); -}; - const GenerativeSearchDisclaimer: React.FC = () => { + const {t} = useTranslation('generative-search'); + return (
- - Ответ сформирован нейросетью YandexGPT на основе документации сервиса. В нём могут - быть неточности, проверить информацию можно поссылкам на источники. - +

{t('generative-search_disclaimer')}

); }; const GenerativeSearchSource: React.FC = ({links, titles}) => { - const combinedSearchInfo = links.map((link, index) => ({url: link, title: titles[index]})); + const { + containerRef, + showPrevButton, + showNextButton, + nextSlide, + prevSlide, + updateButtonsVisibility, + } = useCarousel(); + + const {t} = useTranslation('generative-search'); return (
- Источники -
- {combinedSearchInfo.map((item, index) => ( -
- {item.title} - -
- - {item.url} - - {index + 1} +

{t('generative-search_sources_title')}

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

{titles[index]}

+ + {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 @@ -112,26 +144,42 @@ const GenerativeSearchWrapperBlock: React.FC = ({ }; const GenerativeSearchWithoutContentBlock = () => { + const {t} = useTranslation('generative-search'); + return (

-

Не удалось найти информацию

-

Сформулируйте запрос иначе или спросите что-нибудь ещё.

+

{t('generative-search_not_found_title')}

+

{t('generative-search_not_found_text')}

); }; 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; @@ -144,7 +192,9 @@ const GenerativeSearchAnswer: React.FC = ({ generativeSearchError, }) => { const [isSubmitted, setIsSubmitted] = useState(false); - const [isExpanded, setIsExpanded] = useState(false); + const [isExpanded, setIsExpanded] = useState(true); + + const {t} = useTranslation('generative-search'); const handleRatingClick = () => { setIsSubmitted(!isSubmitted); @@ -155,7 +205,7 @@ const GenerativeSearchAnswer: React.FC = ({ }; if (generativeSearchLoading) { - return ; + ; } if (generativeSearchError) { @@ -165,20 +215,21 @@ const GenerativeSearchAnswer: React.FC = ({ const {message, links, titles} = generativeSearchData; const {content} = message; - const parserdContent = md.render(content); - const updatedHtmlContent = replaceReferencesWithLinks(parserdContent, links); + + if (content.startsWith('Не удалось найти информацию')) { + return ; + } return (
-
+
+ {content} +
{isSubmitted ? ( - Спасибо, что помогаете делать технологию лучше +

{t('generative-search_feedback_answer')}

) : ( <> )} @@ -210,7 +261,7 @@ const GenerativeSearchAnswer: React.FC = ({ className={b('toggle-button')} onClick={toggleExpand} > - Развернуть + {t('generative-search_expand')}
diff --git a/src/components/GenerativeSearchAnswer/useCarousel.tsx b/src/components/GenerativeSearchAnswer/useCarousel.tsx new file mode 100644 index 00000000..494e9805 --- /dev/null +++ b/src/components/GenerativeSearchAnswer/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(true); + + 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, + }; +}; From a3b6ec004b1c2c601513fa473251a8abcc3c1ea3 Mon Sep 17 00:00:00 2001 From: Uyama Date: Wed, 14 Aug 2024 20:34:41 +0300 Subject: [PATCH 16/30] chore(misc): minor updates to various components --- demo/src/Components/SearchPage/searchData.ts | 2 +- .../GenerativeSearchAnswer.tsx | 2 +- src/components/SearchSuggest/Suggest.tsx | 50 ++++++++++++++----- src/components/SearchSuggest/index.scss | 38 ++++++++++++++ .../useCarousel.tsx | 0 src/i18n/en.json | 16 ++++++ src/i18n/ru.json | 16 ++++++ src/utils/index.ts | 5 ++ 8 files changed, 114 insertions(+), 15 deletions(-) rename src/{components/GenerativeSearchAnswer => hooks}/useCarousel.tsx (100%) diff --git a/demo/src/Components/SearchPage/searchData.ts b/demo/src/Components/SearchPage/searchData.ts index 2edbb033..75f41215 100644 --- a/demo/src/Components/SearchPage/searchData.ts +++ b/demo/src/Components/SearchPage/searchData.ts @@ -1,7 +1,7 @@ export default { message: { content: - 'Чтобы работать с YFM, можно следовать таким рекомендациям:\n\n* **Создайте проект**. [1] Он состоит из нескольких конфигурационных файлов и страниц с контентом. [1]\n\n* **Запустите сборку проекта**. [1][3] Для этого используйте инструмент Builder в командной строке. [1] Укажите обязательные ключи запуска: **input (–i) — путь до директории проекта, output (–o) — путь до директории для выходных данных (статических HTML)**. [1][3] Пример команды: `yfm -i ./input-folder -o ./ouput-folder`. [1]\n\n* **Настройте сборку в YFM**. [3] Для этого при выполнении команды yfm укажите ключ запуска **--output-format=md**. [3] Сборка в YFM позволяет использовать вставки и условия видимости разделов, условия отображения контента и подстановки переменных. [3]\n\n* **Используйте готовый проект**. [1] Проекты в формате HTML можно использовать локально или разместить на хостинге, в GitHub Pages или S3. [1][4]', + '

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

  • Создайте проект. 1 Он состоит из нескольких конфигурационных файлов и страниц с контентом. 1

  • Запустите сборку проекта. 13 Для этого используйте инструмент Builder в командной строке. 1 Укажите обязательные ключи запуска: input (–i) — путь до директории проекта, output (–o) — путь до директории для выходных данных (статических HTML). 13 Пример команды: yfm -i ./input-folder -o ./ouput-folder. 1

  • Настройте сборку в YFM. 3 Для этого при выполнении команды yfm укажите ключ запуска --output-format=md. 3 Сборка в YFM позволяет использовать вставки и условия видимости разделов, условия отображения контента и подстановки переменных. 3

  • Используйте готовый проект. 1 Проекты в формате HTML можно использовать локально или разместить на хостинге, в GitHub Pages или S3. 14

', role: 'assistant', }, links: [ diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx index 4ef8a627..219665d9 100644 --- a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx @@ -5,11 +5,11 @@ import {Button, Icon} 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 {useCarousel} from './useCarousel'; import './GenerativeSearchAnswer.scss'; diff --git a/src/components/SearchSuggest/Suggest.tsx b/src/components/SearchSuggest/Suggest.tsx index 978b3736..255c0419 100644 --- a/src/components/SearchSuggest/Suggest.tsx +++ b/src/components/SearchSuggest/Suggest.tsx @@ -6,6 +6,7 @@ import block from 'bem-cn-lite'; import pick from 'lodash/pick'; import {useTranslation} from '../../hooks'; +import {YandexGPTLogo} from '../GenerativeSearchAnswer/YandexGPTLogo'; import type {SearchProvider, SearchSuggestItem} from './types'; import {useProvider} from './useProvider'; @@ -14,6 +15,20 @@ import './index.scss'; const b = block('dc-search-suggest'); +const SuggestGenerative = () => { + const {t} = useTranslation('search-suggest'); + + return ( +
+ +
+

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

+

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

+
+
+ ); +}; + const SuggestLoader = memo(() => { return (
@@ -53,18 +68,21 @@ const SuggestList = memo( const {id, items, renderItem, onItemClick, onChangeActive} = props; return ( - + <> + + + ); }), ); @@ -93,7 +111,13 @@ export const Suggest = memo( } 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..446a4ca4 100644 --- a/src/components/SearchSuggest/index.scss +++ b/src/components/SearchSuggest/index.scss @@ -149,4 +149,42 @@ } } } + + &__generative-search { + background-color: #f1f4f9; + padding: 20px; + border: 1px solid transparent; + + 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); + background-color: rgba(243, 238, 249, 1); + } + } + + &__generative-search-text { + display: inline-block; + vertical-align: top; + + > h1 { + font-size: 18px; + font-weight: 700; + line-height: 22px; + margin: 0; + } + > p { + font-size: 12px; + font-weight: 400; + line-height: 16px; + margin: 0; + } + } } diff --git a/src/components/GenerativeSearchAnswer/useCarousel.tsx b/src/hooks/useCarousel.tsx similarity index 100% rename from src/components/GenerativeSearchAnswer/useCarousel.tsx rename to src/hooks/useCarousel.tsx diff --git a/src/i18n/en.json b/src/i18n/en.json index 17c9638d..05a583d1 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -112,10 +112,26 @@ "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_not_found_title": "No results found.", + "generative-search_not_found_text": "Please rephrase your query or ask something else.", + "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..d6807384 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -112,10 +112,26 @@ "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_not_found_title": "Не удалось найти информацию", + "generative-search_not_found_text": "Сформулируйте запрос иначе или спросите что-нибудь ещё.", + "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/utils/index.ts b/src/utils/index.ts index 1fcf2d9d..7e9d7b1d 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -138,3 +138,8 @@ export function getPageType({ return DocumentType.Base; } + +export const simplifyUrl = (url: string) => { + const match = url.match(/^https?:\/\/[^/]+(\/.*)$/); + return match ? `...${match[1]}` : url; +}; From bc6ca71808ebb20bcc492b63fea51b14b0b8018e Mon Sep 17 00:00:00 2001 From: Alexander Minkin Date: Wed, 14 Aug 2024 21:33:07 +0300 Subject: [PATCH 17/30] fix(generative-search): add missing return --- .../GenerativeSearchAnswer/GenerativeSearchAnswer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx index 219665d9..145c021b 100644 --- a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx @@ -205,7 +205,7 @@ const GenerativeSearchAnswer: React.FC = ({ }; if (generativeSearchLoading) { - ; + return ; } if (generativeSearchError) { From 72a8b67da1d585db85553305901e6948b40140fc Mon Sep 17 00:00:00 2001 From: Alexander Minkin Date: Thu, 15 Aug 2024 01:07:49 +0300 Subject: [PATCH 18/30] feat(generative search): add event handlers for some events --- .../GenerativeSearchAnswer.tsx | 68 ++++++++++++++++--- src/components/SearchPage/SearchPage.tsx | 17 ++--- 2 files changed, 69 insertions(+), 16 deletions(-) diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx index 145c021b..eec849d7 100644 --- a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx @@ -20,6 +20,13 @@ interface Message { 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[]; @@ -46,7 +53,17 @@ const GenerativeSearchDisclaimer: React.FC = () => { ); }; -const GenerativeSearchSource: React.FC = ({links, titles}) => { +interface GenerativeSearchSourceAnalytics { + generativeSourceOnClick?: (link: string) => void; +} + +type GenerativeSearchSourceProps = IGenerativeSearchSource & GenerativeSearchSourceAnalytics; + +const GenerativeSearchSource: React.FC = ({ + links, + titles, + generativeSourceOnClick, +}) => { const { containerRef, showPrevButton, @@ -71,7 +88,15 @@ const GenerativeSearchSource: React.FC = ({links, title {links.map((link, index) => (

{titles[index]}

- + + generativeSourceOnClick + ? generativeSourceOnClick(link) + : undefined + } + href={link} + > {simplifyUrl(link)} {index + 1} @@ -185,11 +210,16 @@ interface IGenerativeSearchI { generativeSearchLoading: boolean; generativeSearchError: boolean; } +export type GenerativeSearchProps = IGenerativeSearchI & GenerativeSearchOnClickProps; -const GenerativeSearchAnswer: React.FC = ({ +const GenerativeSearchAnswer: React.FC = ({ generativeSearchData, generativeSearchLoading, generativeSearchError, + generativeExpandOnClick, + generativeSourceOnClick, + generativeIrrelevantOnClick, + generativeRelevantOnClick, }) => { const [isSubmitted, setIsSubmitted] = useState(false); const [isExpanded, setIsExpanded] = useState(true); @@ -204,6 +234,13 @@ const GenerativeSearchAnswer: React.FC = ({ 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 ; } @@ -223,10 +260,10 @@ const GenerativeSearchAnswer: React.FC = ({ return (
-
+
{content}
- +
{isSubmitted ? (

{t('generative-search_feedback_answer')}

@@ -236,7 +273,12 @@ const GenerativeSearchAnswer: React.FC = ({ view="outlined" size="l" className={b('rating-button')} - onClick={handleRatingClick} + onClick={() => { + if (generativeRelevantOnClick) { + generativeRelevantOnClick(generativeSearchData); + } + handleRatingClick(); + }} > {t('generative-search_good_response')} @@ -245,7 +287,12 @@ const GenerativeSearchAnswer: React.FC = ({ view="outlined" size="l" className={b('rating-button')} - onClick={handleRatingClick} + onClick={() => { + if (generativeIrrelevantOnClick) { + generativeIrrelevantOnClick(generativeSearchData); + } + handleRatingClick(); + }} > {t('generative-search_bad_response')} @@ -259,7 +306,12 @@ const GenerativeSearchAnswer: React.FC = ({ view="normal-contrast" size="l" className={b('toggle-button')} - onClick={toggleExpand} + onClick={() => { + if (generativeExpandOnClick) { + generativeExpandOnClick(generativeSearchData); + } + toggleExpand(); + }} > {t('generative-search_expand')} diff --git a/src/components/SearchPage/SearchPage.tsx b/src/components/SearchPage/SearchPage.tsx index 48f9a0d4..f36cc5eb 100644 --- a/src/components/SearchPage/SearchPage.tsx +++ b/src/components/SearchPage/SearchPage.tsx @@ -4,7 +4,7 @@ import {Button, Loader, TextInput} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; import {useTranslation} from '../../hooks'; -import {GenerativeSearchAnswer, IGenerativeSearch} from '../GenerativeSearchAnswer'; +import {GenerativeSearchAnswer, GenerativeSearchProps} from '../GenerativeSearchAnswer'; import {Paginator, PaginatorProps} from '../Paginator'; import {ISearchItem, SearchItem, SearchOnClickProps} from '../SearchItem'; @@ -35,13 +35,6 @@ interface SearchPageProps extends Loading { loading?: boolean; } -interface GenerativeSearchProps { - generativeSearchData: IGenerativeSearch; - generativeSearchLoading: boolean; - generativeSearchError: boolean; - generativeSearchNoData: boolean; -} - type RenderFoundProps = SearchPageProps & SearchOnClickProps & PaginatorProps; type SearchPageInnerProps = SearchPageProps & @@ -164,6 +157,10 @@ const SearchPage = ({ generativeSearchData, generativeSearchLoading, generativeSearchError, + generativeExpandOnClick, + generativeSourceOnClick, + generativeIrrelevantOnClick, + generativeRelevantOnClick, }: SearchPageInnerProps) => { const inputRef = useRef(null); const [currentQuery, setCurrentQuery] = useState(query); @@ -186,6 +183,10 @@ const SearchPage = ({ generativeSearchData, generativeSearchLoading, generativeSearchError, + generativeExpandOnClick, + generativeSourceOnClick, + generativeIrrelevantOnClick, + generativeRelevantOnClick, }} />
From 4d32a6bbef1d6b8465d0194e7b45fc84682ad5b6 Mon Sep 17 00:00:00 2001 From: Alexander Minkin Date: Thu, 15 Aug 2024 01:28:29 +0300 Subject: [PATCH 19/30] feat(SearchSuggest): add generativeSuggestOnClick handler for monitoring --- src/components/SearchSuggest/Suggest.tsx | 48 ++++++++++++++++++------ src/components/SearchSuggest/index.tsx | 5 ++- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/components/SearchSuggest/Suggest.tsx b/src/components/SearchSuggest/Suggest.tsx index 255c0419..9e932ec1 100644 --- a/src/components/SearchSuggest/Suggest.tsx +++ b/src/components/SearchSuggest/Suggest.tsx @@ -15,17 +15,27 @@ import './index.scss'; const b = block('dc-search-suggest'); -const SuggestGenerative = () => { +interface SuggestGenerativeProps { + link: string; + generativeSuggestOnClick?: (link: string) => void; +} + +const SuggestGenerative: React.FC = ({link, generativeSuggestOnClick}) => { const {t} = useTranslation('search-suggest'); return ( -
- - + ); }; @@ -61,15 +71,25 @@ 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 ( <> - + ; +} & 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]); @@ -110,11 +130,13 @@ export const Suggest = memo( return ; } + const queryLink = provider.link(`/search?query=${query}`); + if (Array.isArray(items) && !items.length) { // <> return ( <> - + ); @@ -124,7 +146,9 @@ export const Suggest = memo( ); }), diff --git a/src/components/SearchSuggest/index.tsx b/src/components/SearchSuggest/index.tsx index fae0e2cb..9cafd8f4 100644 --- a/src/components/SearchSuggest/index.tsx +++ b/src/components/SearchSuggest/index.tsx @@ -39,6 +39,7 @@ export interface SearchSuggestProps { onFocus?: () => void; onBlur?: () => void; endContent?: React.ReactNode; + generativeSuggestOnClick?: (link: string) => void; } export interface SearchSuggestApi { @@ -48,7 +49,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); @@ -138,6 +140,7 @@ export const SearchSuggest = forwardRef((p renderItem={SuggestItem} onItemClick={onSubmit} onChangeActive={setActive} + {...generativeSuggestOnClick} /> )} From bfd2adc9e0c2b3304adbd5ef6753141b8cb2cbaa Mon Sep 17 00:00:00 2001 From: Alexander Minkin Date: Thu, 15 Aug 2024 01:38:09 +0300 Subject: [PATCH 20/30] feat(demo): add storybook examples for monitoring --- .../src/Components/SearchPage/index.stories.tsx | 17 ++++++++++++----- .../Components/SearchSuggest/index.stories.tsx | 4 ++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/demo/src/Components/SearchPage/index.stories.tsx b/demo/src/Components/SearchPage/index.stories.tsx index 80e16713..b58e766d 100644 --- a/demo/src/Components/SearchPage/index.stories.tsx +++ b/demo/src/Components/SearchPage/index.stories.tsx @@ -11,7 +11,6 @@ const SearchPageDemo = (args) => { const isMobile = args['Mobile']; const generativeSearchLoading = args['GenerativeSearchLoading']; const generativeSearchError = args['GenerativeSearchError']; - const generativeSearchNoData = args['GenerativeSearchNoData']; const [page, setPage] = useState(1); const [items, setItems] = useState(getItems(page, mockData)); @@ -39,7 +38,18 @@ const SearchPageDemo = (args) => { generativeSearchData={generativeSearchData} generativeSearchLoading={generativeSearchLoading} generativeSearchError={generativeSearchError} - generativeSearchNoData={generativeSearchNoData} + 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) + } />
); @@ -58,9 +68,6 @@ export default { GenerativeSearchError: { control: 'boolean', }, - generativeSearchNoData: { - control: 'boolean', - }, }, }; diff --git a/demo/src/Components/SearchSuggest/index.stories.tsx b/demo/src/Components/SearchSuggest/index.stories.tsx index ec8f6208..91945f7b 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 React, {useState} from 'react'; import type {SearchProvider, SearchResult} from '@diplodoc/components'; @@ -52,6 +53,9 @@ const SearchSuggestDemo = () => { provider={provider} onFocus={() => setSearch(true)} onBlur={() => setSearch(false)} + generativeSuggestOnClick={(link) => + console.log(`Clicked on generative search banner going to ${link}`) + } /> ); From f80d4d5fc5a2861806a4f83caed74bac63aef3dd Mon Sep 17 00:00:00 2001 From: Uyama Date: Thu, 15 Aug 2024 15:12:14 +0300 Subject: [PATCH 21/30] feat(dark theme): add dark theme capability --- .../GenerativeSearchAnswer.scss | 48 +++++++++++++++---- .../GenerativeSearchAnswer.tsx | 12 +++-- src/hooks/useCarousel.tsx | 2 +- src/themes/common/index.scss | 8 ++++ 4 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss index f99debbb..fb1ca403 100644 --- a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss @@ -1,3 +1,6 @@ +@import '../../styles/variables'; +@import '../../styles/mixins'; + .generative-search-answer { $block: &; @@ -12,9 +15,8 @@ } &__container { - --g-color-base-generic: rgba(241, 244, 249, 1); border-radius: 28px; - background-color: var(--g-color-base-generic); + background-color: var(--g-color-second-background); padding: 15px; position: relative; overflow: hidden; @@ -35,18 +37,36 @@ 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-base-generic) 100% + 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: 5; + z-index: 1; + position: relative; > h4 { color: var(--g-color-text-primary); @@ -54,7 +74,6 @@ font-weight: 700; line-height: 22px; margin: 0; - z-index: 5; } > p { color: var(--g-color-text-primary); @@ -62,7 +81,6 @@ font-weight: 400; line-height: 16px; margin: 0; - z-index: 5; } } @@ -77,12 +95,12 @@ } &__content { - background-color: var(--g-color-base-background); + background-color: var(--g-color-neutral-background); display: block; padding: 19px 20px; border-radius: 26px; position: relative; - z-index: 5; + z-index: 1; margin-bottom: 28px; a { @@ -114,7 +132,16 @@ } } + &__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; @@ -169,11 +196,11 @@ padding: 10px; border-radius: 16px; - min-width: 140px; + min-width: 134px; min-height: 112px; box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.25); - background-color: var(--g-color-base-background); + background-color: var(--g-color-neutral-background); p { font-size: 12px; @@ -188,6 +215,7 @@ } a { + @include link(); color: rgba(85, 126, 236, 1); font-size: 12px; font-weight: 400; diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx index eec849d7..daec5773 100644 --- a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx @@ -1,7 +1,7 @@ import React, {ReactNode, useState} from 'react'; import {ChevronDown, ChevronLeft, ChevronRight, ThumbsDown, ThumbsUp} from '@gravity-ui/icons'; -import {Button, Icon} from '@gravity-ui/uikit'; +import {Button, Icon, useTheme} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; import {useTranslation} from '../../hooks'; @@ -140,7 +140,7 @@ const GenerativSearchHeader = () => {

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

@@ -157,12 +157,15 @@ const GenerativeSearchWrapperBlock: React.FC = ({ children, isExpanded, }) => { + const theme = useTheme(); + return ( <>
{children} + {theme === 'dark' &&
}
); @@ -222,7 +225,8 @@ const GenerativeSearchAnswer: React.FC = ({ generativeRelevantOnClick, }) => { const [isSubmitted, setIsSubmitted] = useState(false); - const [isExpanded, setIsExpanded] = useState(true); + const [isExpanded, setIsExpanded] = useState(false); + const theme = useTheme(); const {t} = useTranslation('generative-search'); @@ -260,7 +264,7 @@ const GenerativeSearchAnswer: React.FC = ({ return (
-
+
{content}
diff --git a/src/hooks/useCarousel.tsx b/src/hooks/useCarousel.tsx index 494e9805..8f2c417b 100644 --- a/src/hooks/useCarousel.tsx +++ b/src/hooks/useCarousel.tsx @@ -3,7 +3,7 @@ import {useEffect, useRef, useState} from 'react'; export const useCarousel = () => { const containerRef = useRef(null); const [showPrevButton, setShowPrevButton] = useState(false); - const [showNextButton, setShowNextButton] = useState(true); + const [showNextButton, setShowNextButton] = useState(false); const updateButtonsVisibility = () => { if (containerRef.current) { 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); From 5893332ecd18994700821275fe8e8010582f9ac8 Mon Sep 17 00:00:00 2001 From: Uyama Date: Thu, 15 Aug 2024 15:23:09 +0300 Subject: [PATCH 22/30] fix(card cources): fix card min card width --- .../GenerativeSearchAnswer/GenerativeSearchAnswer.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss index fb1ca403..db890e67 100644 --- a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss @@ -197,6 +197,7 @@ border-radius: 16px; min-width: 134px; + max-width: 134px; min-height: 112px; box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.25); From 618ee42daef0ed115b8ba4ddc9a5054465340232 Mon Sep 17 00:00:00 2001 From: Alexander Minkin Date: Thu, 15 Aug 2024 22:58:15 +0300 Subject: [PATCH 23/30] feat(card sources): add target="_blank" to source links --- .../GenerativeSearchAnswer/GenerativeSearchAnswer.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx index daec5773..c075f793 100644 --- a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx @@ -96,6 +96,8 @@ const GenerativeSearchSource: React.FC = ({ : undefined } href={link} + target="_blank" + rel="noopener noreferrer" > {simplifyUrl(link)} From a8bcdcea76529b6539f6c75e3d55eb88466112a1 Mon Sep 17 00:00:00 2001 From: Uyama Date: Fri, 16 Aug 2024 00:31:51 +0300 Subject: [PATCH 24/30] fix(ui): update various UI elements --- demo/src/Components/SearchPage/searchData.ts | 8 +------- .../GenerativeSearchAnswer.scss | 19 +++++++++---------- .../GenerativeSearchAnswer.tsx | 15 ++++++++++++--- .../GenerativeSearchAnswer/YandexGPTLogo.tsx | 2 +- src/components/SearchSuggest/Suggest.tsx | 9 ++++----- src/components/SearchSuggest/index.scss | 9 +++++++-- 6 files changed, 34 insertions(+), 28 deletions(-) diff --git a/demo/src/Components/SearchPage/searchData.ts b/demo/src/Components/SearchPage/searchData.ts index 75f41215..85efa214 100644 --- a/demo/src/Components/SearchPage/searchData.ts +++ b/demo/src/Components/SearchPage/searchData.ts @@ -4,13 +4,7 @@ export default { '

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

  • Создайте проект. 1 Он состоит из нескольких конфигурационных файлов и страниц с контентом. 1

  • Запустите сборку проекта. 13 Для этого используйте инструмент Builder в командной строке. 1 Укажите обязательные ключи запуска: input (–i) — путь до директории проекта, output (–o) — путь до директории для выходных данных (статических HTML). 13 Пример команды: yfm -i ./input-folder -o ./ouput-folder. 1

  • Настройте сборку в YFM. 3 Для этого при выполнении команды yfm укажите ключ запуска --output-format=md. 3 Сборка в YFM позволяет использовать вставки и условия видимости разделов, условия отображения контента и подстановки переменных. 3

  • Используйте готовый проект. 1 Проекты в формате HTML можно использовать локально или разместить на хостинге, в GitHub Pages или S3. 14

', 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', - ], + links: [], titles: [ 'Быстрый старт | Diplodoc', 'Базовая разметка | Diplodoc', diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss index db890e67..b2e9bd4a 100644 --- a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss @@ -72,7 +72,7 @@ color: var(--g-color-text-primary); font-size: 18px; font-weight: 700; - line-height: 22px; + line-height: 16px; margin: 0; } > p { @@ -92,6 +92,7 @@ display: inline-flex; column-gap: 2px; align-items: center; + vertical-align: middle; } &__content { @@ -106,13 +107,15 @@ 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; - color: rgba(167, 169, 174, 1); } a + a { @@ -126,9 +129,9 @@ } p { - font-size: 12px; + font-size: 14px; font-weight: 500; - line-height: 16px; + line-height: 22px; } } @@ -255,11 +258,6 @@ &__rating-button { border: 1px solid rgba(181, 181, 181, 1); - background-color: transparent; - - &:hover { - background-color: var(--g-color-base-background); - } &:active { border-color: rgba(95, 94, 94, 1); @@ -274,7 +272,7 @@ left: 0; width: 100%; height: 93px; - z-index: 10; + z-index: 1; background: linear-gradient( 0deg, var(--g-color-base-background) -0.93%, @@ -287,6 +285,7 @@ position: absolute; bottom: 19px; border: 1px solid transparent; + z-index: 1; &:hover { border-color: rgba(174, 174, 178, 1); diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx index daec5773..22cda26a 100644 --- a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx @@ -1,4 +1,4 @@ -import React, {ReactNode, useState} from 'react'; +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'; @@ -140,7 +140,7 @@ const GenerativSearchHeader = () => {

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

@@ -228,6 +228,11 @@ const GenerativeSearchAnswer: React.FC = ({ const [isExpanded, setIsExpanded] = useState(false); const theme = useTheme(); + useEffect(() => { + setIsSubmitted(false); + setIsExpanded(false); + }, [generativeSearchData]); + const {t} = useTranslation('generative-search'); const handleRatingClick = () => { @@ -267,7 +272,11 @@ const GenerativeSearchAnswer: React.FC = ({
{content}
- + + {Boolean(links.length) && ( + + )} +
{isSubmitted ? (

{t('generative-search_feedback_answer')}

diff --git a/src/components/GenerativeSearchAnswer/YandexGPTLogo.tsx b/src/components/GenerativeSearchAnswer/YandexGPTLogo.tsx index 467312cd..efc288be 100644 --- a/src/components/GenerativeSearchAnswer/YandexGPTLogo.tsx +++ b/src/components/GenerativeSearchAnswer/YandexGPTLogo.tsx @@ -10,7 +10,7 @@ interface IYandexGPTLogo { export const YandexGPTLogo: React.FC = ({ className, width = '10', - height = '11', + height = '10', fill = 'black', }) => { return ( diff --git a/src/components/SearchSuggest/Suggest.tsx b/src/components/SearchSuggest/Suggest.tsx index 9e932ec1..383ae584 100644 --- a/src/components/SearchSuggest/Suggest.tsx +++ b/src/components/SearchSuggest/Suggest.tsx @@ -1,7 +1,7 @@ import type {ReactNode} from 'react'; import React, {forwardRef, memo, useEffect} from 'react'; -import {List, ListItemData, Loader} from '@gravity-ui/uikit'; +import {Link, List, ListItemData, Loader} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; import pick from 'lodash/pick'; @@ -24,18 +24,18 @@ const SuggestGenerative: React.FC = ({link, generativeSu const {t} = useTranslation('search-suggest'); return ( - (generativeSuggestOnClick ? generativeSuggestOnClick(link) : undefined)} >
- +

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

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

-
+ ); }; @@ -133,7 +133,6 @@ export const Suggest = memo( const queryLink = provider.link(`/search?query=${query}`); if (Array.isArray(items) && !items.length) { - // <> return ( <> diff --git a/src/components/SearchSuggest/index.scss b/src/components/SearchSuggest/index.scss index 446a4ca4..6388215b 100644 --- a/src/components/SearchSuggest/index.scss +++ b/src/components/SearchSuggest/index.scss @@ -151,9 +151,12 @@ } &__generative-search { - background-color: #f1f4f9; + @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; @@ -166,7 +169,6 @@ &:active { border: 1px solid rgba(88, 86, 214, 1); - background-color: rgba(243, 238, 249, 1); } } @@ -175,12 +177,15 @@ 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; From 0b18de277c84f010d6eb08991983b4103bd25fb9 Mon Sep 17 00:00:00 2001 From: Uyama Date: Fri, 16 Aug 2024 00:40:00 +0300 Subject: [PATCH 25/30] fix(mocks): revert data mocks --- demo/src/Components/SearchPage/searchData.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/demo/src/Components/SearchPage/searchData.ts b/demo/src/Components/SearchPage/searchData.ts index 85efa214..75f41215 100644 --- a/demo/src/Components/SearchPage/searchData.ts +++ b/demo/src/Components/SearchPage/searchData.ts @@ -4,7 +4,13 @@ export default { '

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

  • Создайте проект. 1 Он состоит из нескольких конфигурационных файлов и страниц с контентом. 1

  • Запустите сборку проекта. 13 Для этого используйте инструмент Builder в командной строке. 1 Укажите обязательные ключи запуска: input (–i) — путь до директории проекта, output (–o) — путь до директории для выходных данных (статических HTML). 13 Пример команды: yfm -i ./input-folder -o ./ouput-folder. 1

  • Настройте сборку в YFM. 3 Для этого при выполнении команды yfm укажите ключ запуска --output-format=md. 3 Сборка в YFM позволяет использовать вставки и условия видимости разделов, условия отображения контента и подстановки переменных. 3

  • Используйте готовый проект. 1 Проекты в формате HTML можно использовать локально или разместить на хостинге, в GitHub Pages или S3. 14

', role: 'assistant', }, - links: [], + 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', From a4e8746736123bf211966c42d53841c60938f995 Mon Sep 17 00:00:00 2001 From: Uyama Date: Fri, 16 Aug 2024 13:15:36 +0300 Subject: [PATCH 26/30] fix(ui): update various UI elements --- .../GenerativeSearchAnswer.scss | 14 +++++++++++--- .../GenerativeSearchAnswer.tsx | 19 +------------------ src/i18n/en.json | 2 -- src/i18n/ru.json | 2 -- 4 files changed, 12 insertions(+), 25 deletions(-) diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss index b2e9bd4a..e9f9f3ed 100644 --- a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.scss @@ -103,6 +103,7 @@ position: relative; z-index: 1; margin-bottom: 28px; + box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.25); a { text-decoration: none; @@ -122,13 +123,19 @@ margin-left: 4px; } - strong { + strong, + h1, + h2, + h3, + h4, + h5 { font-size: 14px; font-weight: 700; line-height: 22px; } - p { + p, + li { font-size: 14px; font-weight: 500; line-height: 22px; @@ -276,12 +283,13 @@ background: linear-gradient( 0deg, var(--g-color-base-background) -0.93%, - rgba(241, 244, 249, 0.03) 93.43% + rgba(241, 244, 249, 0.01) 93.43% ); } &__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; diff --git a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx index 19e65946..5c6d985e 100644 --- a/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx +++ b/src/components/GenerativeSearchAnswer/GenerativeSearchAnswer.tsx @@ -173,19 +173,6 @@ const GenerativeSearchWrapperBlock: React.FC = ({ ); }; -const GenerativeSearchWithoutContentBlock = () => { - const {t} = useTranslation('generative-search'); - - return ( - -
-

{t('generative-search_not_found_title')}

-

{t('generative-search_not_found_text')}

-
-
- ); -}; - const GenerativeSearchErrorBlock = () => { const {t} = useTranslation('generative-search'); @@ -264,10 +251,6 @@ const GenerativeSearchAnswer: React.FC = ({ const {content} = message; - if (content.startsWith('Не удалось найти информацию')) { - return ; - } - return (
@@ -315,7 +298,7 @@ const GenerativeSearchAnswer: React.FC = ({ )}
- {!isExpanded && ( + {!isExpanded && Boolean(links.length) && (