@redocly/theme
Version:
Shared UI components lib
332 lines (327 loc) • 15.7 kB
JavaScript
;
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