import React from 'react'; import styled from 'styled-components'; import type { JSX } from 'react'; import { Spinner } from '@redocly/theme/icons/Spinner/Spinner'; import { CheckmarkFilledIcon } from '@redocly/theme/icons/CheckmarkFilledIcon/CheckmarkFilledIcon'; import { DocumentIcon } from '@redocly/theme/icons/DocumentIcon/DocumentIcon'; import { Tag } from '@redocly/theme/components/Tag/Tag'; import { Link } from '@redocly/theme/components/Link/Link'; import { useThemeConfig, useThemeHooks } from '@redocly/theme/core/hooks'; import { Markdown } from '@redocly/theme/components/Markdown/Markdown'; import { Typography } from '@redocly/theme/components/Typography/Typography'; import { Admonition } from '@redocly/theme/components/Admonition/Admonition'; import { ErrorFilledIcon } from '@redocly/theme/icons/ErrorFilledIcon/ErrorFilledIcon'; import { AiSearchError, AI_SEARCH_ERROR_CONFIG as ERROR_CONFIG, } from '@redocly/theme/core/constants'; import { ChatIcon } from '@redocly/theme/icons/ChatIcon/ChatIcon'; export type SearchAiResponseProps = { question: string; isGeneratingResponse: boolean; response?: string; error: AiSearchError | null; resources: { url: string; title: string; }[]; onSuggestionClick: (suggestion: string) => void; }; export function SearchAiResponse(props: SearchAiResponseProps): JSX.Element { const { useMarkdownText } = useThemeHooks(); const { question, response, isGeneratingResponse, resources, error, onSuggestionClick } = props; const { aiAssistant } = useThemeConfig(); const { useTranslate } = useThemeHooks(); const { translate } = useTranslate(); const markdownResponse = useMarkdownText(response || ''); let responseContainer = null; const suggestions = aiAssistant?.suggestions; const hasPendingOrReceivedResponse = response || isGeneratingResponse || error; if (hasPendingOrReceivedResponse) { let icon; switch (true) { case error !== null: icon = ; break; case isGeneratingResponse: icon = ; break; case Boolean(response): icon = ; break; } responseContainer = ( <> {icon} {question} {response && } {!response && isGeneratingResponse && ( {translate('search.ai.thinkingText', 'Thinking...')} )} {error && ( {translate(ERROR_CONFIG[error].messageKey, ERROR_CONFIG[error].messageDefault)} )} {resources.length && !isGeneratingResponse ? ( {resources.length} {translate('search.ai.resourcesFound', 'resources found')} {resources.map((resource, idx) => ( } > {resource.title} ))} ) : null} ); } return ( {!question && ( <> {suggestions?.length && ( {translate('search.ai.suggestionsTitle', 'Suggestions')} {suggestions.map((suggestion, idx) => ( { onSuggestionClick(suggestion); }} > {suggestion} ))} )} )} {responseContainer} ); } const ResponseWrapper = styled.div` display: flex; flex-direction: column; flex: 2; flex-grow: 2; overflow-y: scroll; overscroll-behavior: contain; padding: var(--search-ai-response-padding); gap: var(--search-ai-response-gap); `; const ResponseHeader = styled.div` display: flex; flex-direction: row; gap: var(--search-ai-response-header-gap); align-items: center; `; const Question = styled.div` font-size: var(--search-ai-question-font-size); font-weight: var(--search-ai-question-font-weight); line-height: var(--search-ai-question-line-height); color: var(--search-ai-question-text-color); `; const ResponseBody = styled.div` display: flex; flex-direction: column; gap: var(--search-ai-response-body-gap); padding: var(--search-ai-response-body-padding); `; const ResponseText = styled(Markdown)` color: var(--search-ai-response-text-color); font-size: var(--search-ai-response-text-font-size); line-height: var(--search-ai-response-text-line-height); font-family: inherit; white-space: break-spaces; `; const ThinkingResponseText = styled.pre` color: var(--search-ai-response-text-color); font-size: var(--search-ai-response-text-font-size); line-height: var(--search-ai-response-text-line-height); font-family: inherit; white-space: break-spaces; margin: var(--md-pre-margin) 0; animation: fadeIn 2s ease-in-out infinite; @keyframes fadeIn { 0% { opacity: 1; } 50% { opacity: 0.3; } 100% { opacity: 1; } } `; const Resources = styled.div` gap: var(--search-ai-resources-gap); display: flex; flex-direction: column; `; const ResourcesTitle = styled.div` font-weight: var(--search-ai-resources-title-font-weight); font-size: var(--search-ai-resources-title-font-size); line-height: var(--search-ai-resources-title-line-height); `; const ResourceTags = styled.div` display: flex; flex-wrap: wrap; gap: var(--search-ai-resource-tags-gap); `; const ResourceTag = styled(Tag).attrs({ className: 'tag-resource', })` .tag-default { --tag-color: --search-ai-resource-tag-text-color; } `; const SuggestionsWrapper = styled.div` display: flex; flex-direction: column; justify-content: flex-start; gap: var(--spacing-sm); margin-left: var(--spacing-xs); `; const SuggestionsTitle = styled(Typography)` font-size: var(--search-ai-suggestions-title-font-size); line-height: var(--search-ai-suggestions-title-line-height); font-weight: var(--search-ai-suggestions-title-font-weight); color: var(--search-ai-suggestions-title-text-color); `; const SuggestionItem = styled.div` cursor: pointer; display: flex; align-items: center; justify-content: flex-start; gap: var(--spacing-xs); `;