UNPKG

@redocly/theme

Version:

Shared UI components lib

332 lines (327 loc) 15.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SearchAiMessage = void 0; const react_1 = __importStar(require("react")); const styled_components_1 = __importDefault(require("styled-components")); const types_1 = require("../../core/types"); const constants_1 = require("../../core/constants"); const Link_1 = require("../../components/Link/Link"); const Tag_1 = require("../../components/Tag/Tag"); const constants_2 = require("../../core/constants"); const hooks_1 = require("../../core/hooks"); const Markdown_1 = require("../../components/Markdown/Markdown"); const DocumentIcon_1 = require("../../icons/DocumentIcon/DocumentIcon"); const AiStarsIcon_1 = require("../../icons/AiStarsIcon/AiStarsIcon"); const CheckmarkOutlineIcon_1 = require("../../icons/CheckmarkOutlineIcon/CheckmarkOutlineIcon"); const SearchAiActionButtons_1 = require("../../components/Search/SearchAiActionButtons"); const SearchAiNegativeFeedbackForm_1 = require("../../components/Search/SearchAiNegativeFeedbackForm"); function MarkdownSegment({ text }) { const { useMarkdownText } = (0, hooks_1.useThemeHooks)(); const markdown = useMarkdownText(text); return react_1.default.createElement(ResponseText, { as: "div", children: markdown, "data-testid": "response-text" }); } function SearchAiMessageComponent({ role, content, isThinking, resources, className, messageId, feedback, onFeedbackChange, toolCalls = [], contentSegments = [{ type: 'text', text: content }], }) { var _a; const { useTranslate, useTelemetry } = (0, hooks_1.useThemeHooks)(); const { translate } = useTranslate(); const telemetry = useTelemetry(); const [feedbackSent, setFeedbackSent] = (0, react_1.useState)(false); const hasResources = !isThinking && resources && resources.length > 0; const resourcesCount = (_a = resources === null || resources === void 0 ? void 0 : resources.length) !== null && _a !== void 0 ? _a : 0; const showSuccessMessage = feedbackSent && feedback; const isLoading = isThinking && content.length === 0 && toolCalls.length === 0; const sendFeedbackTelemetry = (feedbackValue, dislikeReason) => { if (!messageId) return; try { telemetry.sendSearchAIFeedbackMessage([ { object: 'feedback', feedback: feedbackValue, messageId, reason: dislikeReason, }, ]); } catch (error) { console.error('Error sending feedback', error); } }; const handleFeedbackClick = (feedbackValue, reason) => { if (!messageId) { return; } if (!reason) { onFeedbackChange(messageId, feedbackValue); } sendFeedbackTelemetry(feedbackValue, reason); if (feedbackValue === types_1.FeedbackType.Like || reason) { setFeedbackSent(true); } }; return (react_1.default.createElement(SearchAiMessageWrapper, { "data-component-name": "Search/SearchAiMessage", role: role, className: className, "data-testid": "search-ai-message" }, role === constants_2.AiSearchConversationRole.ASSISTANT && (react_1.default.createElement(AiStarsIcon_1.AiStarsIcon, { size: "32px", background: "var(--search-ai-icon-bg-color)", borderRadius: "var(--border-radius-lg)", color: "var(--search-ai-icon-color)", margin: "0 var(--spacing-xs) 0 0" })), react_1.default.createElement(MessageContentWrapper, null, role === constants_2.AiSearchConversationRole.ASSISTANT ? (react_1.default.createElement(react_1.default.Fragment, null, react_1.default.createElement(MessageWrapper, { role: role }, contentSegments.map((segment, index) => { var _a, _b, _c, _d, _e, _f; if (segment.type === 'tool') { const toolCallCompleted = Boolean(segment.toolCall.result); const toolCallCompletedText = (_b = (_a = constants_1.TOOL_CALL_DISPLAY_TEXT[segment.toolCall.name]) === null || _a === void 0 ? void 0 : _a.completedText) !== null && _b !== void 0 ? _b : `${translate('search.ai.toolResult.found', 'Found')} ${(_d = (_c = segment.toolCall.result) === null || _c === void 0 ? void 0 : _c.documentCount) !== null && _d !== void 0 ? _d : 0} ${translate('search.ai.toolResult.found.documents', 'documents')}`; const toolCallInProgressText = (_f = (_e = constants_1.TOOL_CALL_DISPLAY_TEXT[segment.toolCall.name]) === null || _e === void 0 ? void 0 : _e.inProgressText) !== null && _f !== void 0 ? _f : translate('search.ai.toolCall.searching', 'Searching...'); const toolCallDisplayText = toolCallCompleted ? toolCallCompletedText : toolCallInProgressText; return (react_1.default.createElement(ToolCallsInfoWrapper, { key: `tool-${index}`, "data-testid": "tool-calls-info" }, react_1.default.createElement(ToolCallInfoItem, null, react_1.default.createElement(DocumentIcon_1.DocumentIcon, { size: "14px", color: "--search-ai-text-color" }), react_1.default.createElement(ToolCallText, { "$isSearching": !toolCallCompleted }, toolCallDisplayText)))); } return react_1.default.createElement(MarkdownSegment, { key: `text-${index}`, text: segment.text }); }), hasResources && (react_1.default.createElement(react_1.default.Fragment, null, react_1.default.createElement(ResourcesWrapper, { "data-testid": "resources-wrapper" }, react_1.default.createElement(ResourcesTitle, { "data-translation-key": "search.ai.resourcesFound" }, translate('search.ai.resourcesFound.basedOn', 'Based on'), " ", resourcesCount, ' ', translate('search.ai.resourcesFound.resources', 'resources')), react_1.default.createElement(ResourceTagsWrapper, null, resources === null || resources === void 0 ? void 0 : resources.map((resource, index) => (react_1.default.createElement(Link_1.Link, { key: `${resource.url}-${index}`, to: resource.url, target: "_blank" }, react_1.default.createElement(ResourceTag, { borderless: true, icon: react_1.default.createElement(DocumentIcon_1.DocumentIcon, { color: "--search-ai-resource-tag-icon-color" }) }, resource.title)))))))), isLoading && (react_1.default.createElement(ThinkingDotsWrapper, { "data-testid": "thinking-dots-wrapper" }, react_1.default.createElement(ThinkingDot, null), react_1.default.createElement(ThinkingDot, null), react_1.default.createElement(ThinkingDot, null))), content.length > 0 && (react_1.default.createElement(FeedbackWrapper, null, react_1.default.createElement(SearchAiActionButtons_1.SearchAiActionButtons, { content: content, feedback: feedback, onFeedback: handleFeedbackClick, disabled: !!feedback })))), messageId && feedback === types_1.FeedbackType.Dislike && !showSuccessMessage && (react_1.default.createElement(SearchAiNegativeFeedbackForm_1.SearchAiNegativeFeedbackForm, { messageId: messageId, onClose: (messageId, feedback) => { onFeedbackChange(messageId, feedback); setFeedbackSent(true); }, onSubmit: (reason) => handleFeedbackClick(types_1.FeedbackType.Dislike, reason) })), showSuccessMessage && (react_1.default.createElement(SuccessMessageWrapper, { "data-component-name": "Search/SearchAiMessage/Success" }, react_1.default.createElement(CheckmarkOutlineIcon_1.CheckmarkOutlineIcon, { size: "20px", color: "var(--color-success-base)" }), react_1.default.createElement(SuccessMessageText, null, translate('search.ai.feedback.thanks', 'Thank you for your feedback!')))))) : (react_1.default.createElement(MessageWrapper, { role: role }, content))))); } function areResourcesEqual(prev, next) { if (prev === next) return true; if (!prev || !next || prev.length !== next.length) return false; return prev.every((resource, index) => { const nextResource = next[index]; return resource.url === nextResource.url && resource.title === nextResource.title; }); } exports.SearchAiMessage = (0, react_1.memo)(SearchAiMessageComponent, (prevProps, nextProps) => { var _a, _b; return (prevProps.role === nextProps.role && prevProps.content === nextProps.content && prevProps.isThinking === nextProps.isThinking && prevProps.messageId === nextProps.messageId && prevProps.feedback === nextProps.feedback && prevProps.onFeedbackChange === nextProps.onFeedbackChange && areResourcesEqual(prevProps.resources, nextProps.resources) && ((_a = prevProps.toolCalls) === null || _a === void 0 ? void 0 : _a.length) === ((_b = nextProps.toolCalls) === null || _b === void 0 ? void 0 : _b.length) && prevProps.contentSegments === nextProps.contentSegments); }); const SearchAiMessageWrapper = styled_components_1.default.div ` display: flex; flex-direction: row; align-items: flex-start; width: 100%; justify-content: ${({ role }) => role === constants_2.AiSearchConversationRole.USER ? 'flex-end' : 'flex-start'}; `; const MessageContentWrapper = styled_components_1.default.div ` display: flex; flex-direction: column; gap: var(--spacing-sm); max-width: 80%; min-width: 0; `; const ResponseText = (0, styled_components_1.default)(Markdown_1.Markdown) ` color: var(--search-ai-text-color); font-size: var(--search-ai-text-font-size); line-height: var(--search-ai-text-line-height); font-family: inherit; white-space: break-spaces; article { > *:first-child { margin-top: 0; } > *:last-child { margin-bottom: 0; } } `; const MessageWrapper = styled_components_1.default.div ` padding: ${({ role }) => role === constants_2.AiSearchConversationRole.USER ? 'var(--spacing-sm)' : 'var(--spacing-sm) var(--spacing-sm) var(--spacing-xs) var(--spacing-sm)'}; border-radius: var(--border-radius-lg); width: fit-content; max-width: 100%; word-wrap: break-word; white-space: pre-wrap; background-color: ${({ role }) => role === constants_2.AiSearchConversationRole.USER ? 'var(--search-ai-user-bg-color)' : 'var(--search-ai-assistant-bg-color)'}; border: ${({ role }) => role === constants_2.AiSearchConversationRole.USER ? 'none' : 'var(--search-ai-assistant-border)'}; color: ${({ role }) => role === constants_2.AiSearchConversationRole.USER ? 'var(--search-ai-user-text-color)' : 'var(--search-ai-assistant-text-color)'}; `; const ResourcesWrapper = styled_components_1.default.div ` gap: var(--search-ai-resources-gap); display: flex; flex-direction: column; margin: 0; `; const FeedbackWrapper = styled_components_1.default.div ` display: flex; flex-direction: row; gap: var(--search-ai-feedback-gap); margin-top: var(--spacing-sm); `; const ResourcesTitle = styled_components_1.default.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 ResourceTagsWrapper = styled_components_1.default.div ` display: flex; flex-wrap: wrap; gap: var(--search-ai-resource-tags-gap); `; const ResourceTag = (0, styled_components_1.default)(Tag_1.Tag) ` .tag-default { --tag-color: var(--search-ai-resource-tag-text-color); max-width: 100%; overflow: hidden; display: inline-block; } svg { min-width: var(--search-ai-resource-tag-icon-size); min-height: var(--search-ai-resource-tag-icon-size); flex-shrink: 0; } > div { overflow: hidden; word-break: break-word; white-space: normal; max-width: 100%; } `; const ThinkingDotsWrapper = styled_components_1.default.div ` display: flex; gap: var(--search-ai-thinking-dots-gap); padding: var(--search-ai-thinking-dots-padding); `; const ThinkingDot = styled_components_1.default.div ` width: var(--search-ai-thinking-dot-size); height: var(--search-ai-thinking-dot-size); border-radius: 50%; background: var(--search-ai-thinking-dot-color); animation: bounce 1.4s infinite ease-in-out; &:nth-child(1) { animation-delay: -0.32s; } &:nth-child(2) { animation-delay: -0.16s; } &:nth-child(3) { animation-delay: 0s; } @keyframes bounce { 0%, 80%, 100% { opacity: 0.2; transform: scale(0.8); } 40% { opacity: 1; transform: scale(1); } } `; const SuccessMessageWrapper = styled_components_1.default.div ` max-width: fit-content; display: flex; align-items: center; gap: var(--spacing-sm); padding: var(--spacing-sm); background: var(--color-success-bg); border: 1px solid var(--color-success-border); border-radius: var(--border-radius-lg); `; const SuccessMessageText = styled_components_1.default.div ` font-size: var(--font-size-base); color: var(--color-success-darker); `; const ToolCallsInfoWrapper = styled_components_1.default.div ` display: flex; flex-direction: column; gap: var(--spacing-xxs); margin: 0 0 var(--spacing-sm) 0; font-size: var(--font-size-xs); color: var(--search-ai-text-color); opacity: 0.6; `; const ToolCallInfoItem = styled_components_1.default.div ` display: flex; align-items: center; gap: var(--spacing-xxs); `; const ToolCallText = styled_components_1.default.span ` font-weight: var(--font-weight-regular); ${({ $isSearching }) => $isSearching && ` animation: pulse 1.5s ease-in-out infinite; @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } } `} `; //# sourceMappingURL=SearchAiMessage.js.map