feat: статья из Discover, локализация, подсказки

- Статья: заголовок + ссылка (truncate), title в URL, articleTitle в Message
- Локализация Sources, Research Progress, Answer, шагов, formingAnswer
- Подсказки: промпт без жёсткого примера, разнообразие, label 'Что ещё спросить'
- embeddedTranslations, countryToLocale, locale инструкция для LLM

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
home
2026-02-21 00:37:06 +03:00
parent f4d945a2b5
commit 3fa83bc605
68 changed files with 2301 additions and 345 deletions

View File

@@ -21,6 +21,7 @@ import SearchVideos from './SearchVideos';
import { useSpeech } from 'react-text-to-speech';
import ThinkBox from './ThinkBox';
import { useChat, Section } from '@/lib/hooks/useChat';
import { useTranslation } from '@/lib/localization/context';
import Citation from './MessageRenderer/Citation';
import AssistantSteps from './AssistantSteps';
import { ResearchBlock } from '@/lib/types';
@@ -58,6 +59,7 @@ const MessageBox = ({
researchEnded,
chatHistory,
} = useChat();
const { t } = useTranslation();
const parsedMessage = section.parsedTextBlocks.join('\n\n');
const speechMessage = section.speechMessage || '';
@@ -70,7 +72,10 @@ const MessageBox = ({
const sources = sourceBlocks.flatMap((block) => block.data);
const hasContent = section.parsedTextBlocks.length > 0;
const hasContent =
section.parsedTextBlocks.some(
(t) => typeof t === 'string' && t.trim().length > 0,
);
const { speechStatus, start, stop } = useSpeech({ text: speechMessage });
@@ -103,12 +108,36 @@ const MessageBox = ({
},
};
const isSummaryArticle =
section.message.query.startsWith('Summary: ') &&
section.message.articleTitle;
const summaryUrl = isSummaryArticle
? section.message.query.slice(9)
: undefined;
return (
<div className="space-y-6">
<div className={'w-full pt-8 break-words'}>
<h2 className="text-black dark:text-white font-medium text-3xl lg:w-9/12">
{section.message.query}
</h2>
<div className="w-full pt-8 break-words lg:max-w-[60%]">
{isSummaryArticle ? (
<div className="space-y-1 min-w-0 overflow-hidden">
<h2 className="text-black dark:text-white font-medium text-3xl">
{section.message.articleTitle}
</h2>
<a
href={summaryUrl}
target="_blank"
rel="noopener noreferrer"
className="block text-sm text-black/60 dark:text-white/60 hover:text-black/80 dark:hover:text-white/80 truncate min-w-0"
title={summaryUrl}
>
{summaryUrl}
</a>
</div>
) : (
<h2 className="text-black dark:text-white font-medium text-3xl">
{section.message.query}
</h2>
)}
</div>
<div className="flex flex-col space-y-9 lg:space-y-0 lg:flex-row lg:justify-between lg:space-x-9">
@@ -121,7 +150,7 @@ const MessageBox = ({
<div className="flex flex-row items-center space-x-2">
<BookCopy className="text-black dark:text-white" size={20} />
<h3 className="text-black dark:text-white font-medium text-xl">
Sources
{t('chat.sources')}
</h3>
</div>
<MessageSources sources={sources} />
@@ -152,7 +181,7 @@ const MessageBox = ({
<div className="flex items-center gap-2 p-3 rounded-lg bg-light-secondary dark:bg-dark-secondary border border-light-200 dark:border-dark-200">
<Disc3 className="w-4 h-4 text-black dark:text-white animate-spin" />
<span className="text-sm text-black/70 dark:text-white/70">
Brainstorming...
{t('chat.brainstorming')}
</span>
</div>
)}
@@ -170,11 +199,23 @@ const MessageBox = ({
size={20}
/>
<h3 className="text-black dark:text-white font-medium text-xl">
Answer
{t('chat.answer')}
</h3>
</div>
)}
{!hasContent && sources.length > 0 && isLast && loading && (
<p className="text-sm text-black/60 dark:text-white/60 italic">
{t('chat.formingAnswer')}
</p>
)}
{!hasContent && sources.length > 0 && isLast && !loading && (
<p className="text-sm text-black/60 dark:text-white/60 italic">
{t('chat.answerFailed')}
</p>
)}
{hasContent && (
<>
<Markdown
@@ -229,7 +270,7 @@ const MessageBox = ({
size={20}
/>
<h3 className="text-black dark:text-white font-medium text-xl">
Related
{t('chat.related')}
</h3>
</div>
<div className="space-y-0">