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);
`;