@redocly/theme 0.59.0-next.7 → 0.59.0-next.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/Buttons/AIAssistantButton.js +1 -1
- package/lib/components/Catalog/CatalogFilter/CatalogFilter.js +4 -0
- package/lib/components/Search/SearchAiActionButtons.d.ts +10 -0
- package/lib/components/Search/SearchAiActionButtons.js +43 -0
- package/lib/components/Search/SearchAiDialog.d.ts +3 -6
- package/lib/components/Search/SearchAiDialog.js +20 -9
- package/lib/components/Search/SearchAiMessage.d.ts +9 -5
- package/lib/components/Search/SearchAiMessage.js +146 -22
- package/lib/components/Search/SearchAiNegativeFeedbackForm.d.ts +8 -0
- package/lib/components/Search/SearchAiNegativeFeedbackForm.js +169 -0
- package/lib/components/Search/variables.js +29 -66
- package/lib/core/hooks/index.d.ts +1 -0
- package/lib/core/hooks/index.js +1 -0
- package/lib/core/hooks/search/use-feedback-tooltip.d.ts +6 -0
- package/lib/core/hooks/search/use-feedback-tooltip.js +26 -0
- package/lib/core/hooks/use-telemetry-fallback.d.ts +1 -0
- package/lib/core/hooks/use-telemetry-fallback.js +1 -0
- package/lib/core/types/l10n.d.ts +1 -1
- package/lib/core/types/search.d.ts +11 -4
- package/lib/core/types/search.js +6 -0
- package/lib/core/utils/frontmatter-translate.d.ts +6 -0
- package/lib/core/utils/frontmatter-translate.js +14 -0
- package/lib/core/utils/index.d.ts +1 -0
- package/lib/core/utils/index.js +1 -0
- package/lib/icons/ThumbDownFilledIcon/ThumbDownFilledIcon.d.ts +9 -0
- package/lib/icons/ThumbDownFilledIcon/ThumbDownFilledIcon.js +34 -0
- package/lib/icons/ThumbUpFilledIcon/ThumbUpFilledIcon.d.ts +9 -0
- package/lib/icons/ThumbUpFilledIcon/ThumbUpFilledIcon.js +34 -0
- package/package.json +3 -3
- package/src/components/Buttons/AIAssistantButton.tsx +1 -1
- package/src/components/Catalog/CatalogFilter/CatalogFilter.tsx +5 -0
- package/src/components/Search/SearchAiActionButtons.tsx +76 -0
- package/src/components/Search/SearchAiDialog.tsx +52 -23
- package/src/components/Search/SearchAiMessage.tsx +172 -43
- package/src/components/Search/SearchAiNegativeFeedbackForm.tsx +210 -0
- package/src/components/Search/variables.ts +29 -66
- package/src/core/hooks/index.ts +1 -0
- package/src/core/hooks/search/use-feedback-tooltip.ts +32 -0
- package/src/core/hooks/use-telemetry-fallback.ts +1 -0
- package/src/core/types/l10n.ts +3 -0
- package/src/core/types/search.ts +13 -4
- package/src/core/utils/frontmatter-translate.ts +9 -0
- package/src/core/utils/index.ts +1 -0
- package/src/icons/ThumbDownFilledIcon/ThumbDownFilledIcon.tsx +38 -0
- package/src/icons/ThumbUpFilledIcon/ThumbUpFilledIcon.tsx +35 -0
|
@@ -49,7 +49,7 @@ const RedoclyIcon_1 = require("../../icons/RedoclyIcon/RedoclyIcon");
|
|
|
49
49
|
const defaultConfig = {
|
|
50
50
|
hide: false,
|
|
51
51
|
inputType: 'button',
|
|
52
|
-
inputIcon: '
|
|
52
|
+
inputIcon: 'sparkles',
|
|
53
53
|
};
|
|
54
54
|
const getIcon = (iconType, inputType = 'button') => {
|
|
55
55
|
const iconSize = inputType === 'icon'
|
|
@@ -17,6 +17,10 @@ const filterComponents = {
|
|
|
17
17
|
function CatalogFilter({ filter, filterValuesCasing, showCounter = true, className, }) {
|
|
18
18
|
if (!filter.parentUsed)
|
|
19
19
|
return null;
|
|
20
|
+
const filteredOptions = filter.filteredOptions || filter.options;
|
|
21
|
+
if (!filteredOptions || filteredOptions.length === 0) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
20
24
|
const FilterComponent = filterComponents[(filter.type || 'checkboxes')];
|
|
21
25
|
return (react_1.default.createElement(CatalogFilterGroup, { className: className, "data-component-name": "Catalog/CatalogFilter", key: filter.property + filter.title },
|
|
22
26
|
react_1.default.createElement(FilterComponent, { filter: filter, filterValuesCasing: filterValuesCasing, showCounter: showCounter })));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { JSX } from 'react';
|
|
2
|
+
import { FeedbackType } from '../../core/types';
|
|
3
|
+
export type SearchAiActionButtonsProps = {
|
|
4
|
+
content: string;
|
|
5
|
+
className?: string;
|
|
6
|
+
feedback?: FeedbackType;
|
|
7
|
+
onFeedback: (feedback: FeedbackType) => void;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare function SearchAiActionButtons({ content, className, feedback, onFeedback, disabled, }: SearchAiActionButtonsProps): JSX.Element;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SearchAiActionButtons = SearchAiActionButtons;
|
|
7
|
+
const react_1 = __importDefault(require("react"));
|
|
8
|
+
const styled_components_1 = __importDefault(require("styled-components"));
|
|
9
|
+
const types_1 = require("../../core/types");
|
|
10
|
+
const Button_1 = require("../../components/Button/Button");
|
|
11
|
+
const ThumbUpIcon_1 = require("../../icons/ThumbUpIcon/ThumbUpIcon");
|
|
12
|
+
const ThumbUpFilledIcon_1 = require("../../icons/ThumbUpFilledIcon/ThumbUpFilledIcon");
|
|
13
|
+
const ThumbDownIcon_1 = require("../../icons/ThumbDownIcon/ThumbDownIcon");
|
|
14
|
+
const ThumbDownFilledIcon_1 = require("../../icons/ThumbDownFilledIcon/ThumbDownFilledIcon");
|
|
15
|
+
const CopyButton_1 = require("../../components/Buttons/CopyButton");
|
|
16
|
+
function SearchAiActionButtons({ content, className, feedback, onFeedback, disabled, }) {
|
|
17
|
+
return (react_1.default.createElement(ActionButtonsWrapper, { className: className, "data-component-name": "Search/SearchAiActionButtons" },
|
|
18
|
+
react_1.default.createElement(CopyButton_1.CopyButton, { data: content }),
|
|
19
|
+
react_1.default.createElement(FeedbackButton, { variant: "text", size: "small", icon: feedback === types_1.FeedbackType.Like ? react_1.default.createElement(ThumbUpFilledIcon_1.ThumbUpFilledIcon, null) : react_1.default.createElement(ThumbUpIcon_1.ThumbUpIcon, null), onClick: () => !disabled && onFeedback(types_1.FeedbackType.Like), extraClass: feedback === types_1.FeedbackType.Like ? 'active' : '', "aria-label": "Like this response", disabled: disabled }),
|
|
20
|
+
react_1.default.createElement(FeedbackButton, { variant: "text", size: "small", icon: feedback === types_1.FeedbackType.Dislike ? react_1.default.createElement(ThumbDownFilledIcon_1.ThumbDownFilledIcon, null) : react_1.default.createElement(ThumbDownIcon_1.ThumbDownIcon, null), onClick: () => !disabled && onFeedback(types_1.FeedbackType.Dislike), extraClass: feedback === types_1.FeedbackType.Dislike ? 'active' : '', "aria-label": "Dislike this response", disabled: disabled })));
|
|
21
|
+
}
|
|
22
|
+
const ActionButtonsWrapper = styled_components_1.default.div `
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
gap: var(--search-ai-feedback-gap);
|
|
26
|
+
`;
|
|
27
|
+
const FeedbackButton = (0, styled_components_1.default)(Button_1.Button) `
|
|
28
|
+
&:disabled {
|
|
29
|
+
pointer-events: none;
|
|
30
|
+
cursor: default;
|
|
31
|
+
opacity: 1;
|
|
32
|
+
background-color: var(--button-bg-color);
|
|
33
|
+
color: var(--button-color);
|
|
34
|
+
border-color: var(--button-border-color);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
&:disabled.active {
|
|
38
|
+
background-color: var(--button-bg-color-active);
|
|
39
|
+
border-color: var(--button-border-color-active);
|
|
40
|
+
color: var(--button-color-active);
|
|
41
|
+
}
|
|
42
|
+
`;
|
|
43
|
+
//# sourceMappingURL=SearchAiActionButtons.js.map
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { JSX } from 'react';
|
|
3
|
+
import type { AiSearchConversationItem, SearchAiMessageResource } from '../../core/types';
|
|
3
4
|
import { AiSearchError } from '../../core/constants';
|
|
4
|
-
import { AiSearchConversationItem } from '../../core/types';
|
|
5
5
|
export type SearchAiDialogProps = {
|
|
6
6
|
response: string | undefined;
|
|
7
7
|
isGeneratingResponse: boolean;
|
|
8
8
|
error: AiSearchError | null;
|
|
9
|
-
resources:
|
|
10
|
-
url: string;
|
|
11
|
-
title: string;
|
|
12
|
-
}[];
|
|
9
|
+
resources: SearchAiMessageResource[];
|
|
13
10
|
initialMessage?: string;
|
|
14
11
|
className?: string;
|
|
15
12
|
conversation: AiSearchConversationItem[];
|
|
16
13
|
setConversation: React.Dispatch<React.SetStateAction<AiSearchConversationItem[]>>;
|
|
17
|
-
onMessageSent: (message: string, history?: AiSearchConversationItem[]) => void;
|
|
14
|
+
onMessageSent: (message: string, history?: AiSearchConversationItem[], messageId?: string) => void;
|
|
18
15
|
};
|
|
19
16
|
export declare function SearchAiDialog({ isGeneratingResponse, response, initialMessage, error, resources, onMessageSent, className, conversation, setConversation, }: SearchAiDialogProps): JSX.Element;
|
|
@@ -57,11 +57,11 @@ function SearchAiDialog({ isGeneratingResponse, response, initialMessage, error,
|
|
|
57
57
|
: conversation.length > 0
|
|
58
58
|
? translate('search.ai.followUpQuestion', 'Ask a follow up question?')
|
|
59
59
|
: translate('search.ai.placeholder', 'Ask a question...');
|
|
60
|
-
const scrollToBottom = () => {
|
|
60
|
+
const scrollToBottom = (0, react_1.useCallback)(() => {
|
|
61
61
|
var _a;
|
|
62
62
|
(_a = conversationEndRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ block: 'end' });
|
|
63
|
-
};
|
|
64
|
-
const handleOnMessageSent = (message) => {
|
|
63
|
+
}, []);
|
|
64
|
+
const handleOnMessageSent = (0, react_1.useCallback)((message) => {
|
|
65
65
|
if (!message.trim()) {
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
@@ -70,8 +70,11 @@ function SearchAiDialog({ isGeneratingResponse, response, initialMessage, error,
|
|
|
70
70
|
content,
|
|
71
71
|
}));
|
|
72
72
|
onMessageSent(message, mappedHistory);
|
|
73
|
-
setConversation((prev) => [
|
|
74
|
-
|
|
73
|
+
setConversation((prev) => [
|
|
74
|
+
...prev,
|
|
75
|
+
{ role: constants_1.AiSearchConversationRole.USER, content: message },
|
|
76
|
+
]);
|
|
77
|
+
}, [conversation, onMessageSent, setConversation]);
|
|
75
78
|
(0, react_1.useEffect)(() => {
|
|
76
79
|
if (!(initialMessage === null || initialMessage === void 0 ? void 0 : initialMessage.trim().length)) {
|
|
77
80
|
return;
|
|
@@ -91,10 +94,15 @@ function SearchAiDialog({ isGeneratingResponse, response, initialMessage, error,
|
|
|
91
94
|
if (lastMessage && lastMessage.role === constants_1.AiSearchConversationRole.ASSISTANT) {
|
|
92
95
|
return [
|
|
93
96
|
...prev.slice(0, -1),
|
|
94
|
-
{
|
|
97
|
+
{
|
|
98
|
+
role: constants_1.AiSearchConversationRole.ASSISTANT,
|
|
99
|
+
content,
|
|
100
|
+
resources,
|
|
101
|
+
messageId: lastMessage.messageId,
|
|
102
|
+
},
|
|
95
103
|
];
|
|
96
104
|
}
|
|
97
|
-
return [...prev, { role: constants_1.AiSearchConversationRole.ASSISTANT, content }];
|
|
105
|
+
return [...prev, { role: constants_1.AiSearchConversationRole.ASSISTANT, content, resources }];
|
|
98
106
|
});
|
|
99
107
|
}, [response, conversation.length, error, resources, setConversation]);
|
|
100
108
|
(0, react_1.useEffect)(() => {
|
|
@@ -104,7 +112,10 @@ function SearchAiDialog({ isGeneratingResponse, response, initialMessage, error,
|
|
|
104
112
|
}, [error, setConversation]);
|
|
105
113
|
(0, react_1.useEffect)(() => {
|
|
106
114
|
scrollToBottom();
|
|
107
|
-
}, [conversation, isGeneratingResponse]);
|
|
115
|
+
}, [conversation, isGeneratingResponse, scrollToBottom]);
|
|
116
|
+
const handleFeedbackChange = (0, react_1.useCallback)((messageId, feedback) => {
|
|
117
|
+
setConversation((prev) => prev.map((item) => (item.messageId === messageId ? Object.assign(Object.assign({}, item), { feedback }) : item)));
|
|
118
|
+
}, [setConversation]);
|
|
108
119
|
return (react_1.default.createElement(SearchAiDialogWrapper, { "data-component-name": "Search/SearchAiDialog", className: className },
|
|
109
120
|
!conversation.length && (react_1.default.createElement(WelcomeWrapper, null,
|
|
110
121
|
react_1.default.createElement(AiStarsIcon_1.AiStarsIcon, { color: "var(--search-ai-icon-color)", size: "32px", background: "var(--search-ai-icon-bg-color)", borderRadius: "var(--border-radius-lg)", margin: "0 var(--spacing-xs) 0 0" }),
|
|
@@ -112,7 +123,7 @@ function SearchAiDialog({ isGeneratingResponse, response, initialMessage, error,
|
|
|
112
123
|
react_1.default.createElement(ConversationWrapper, null,
|
|
113
124
|
conversation.map((item, index) => (react_1.default.createElement(SearchAiMessage_1.SearchAiMessage, { key: `search-ai-message-${index}`, role: item.role, content: item.content, isThinking: item.role === constants_1.AiSearchConversationRole.ASSISTANT &&
|
|
114
125
|
isGeneratingResponse &&
|
|
115
|
-
index === conversation.length - 1, resources: item.resources }))),
|
|
126
|
+
index === conversation.length - 1, resources: item.resources, messageId: item.messageId, feedback: item.feedback, onFeedbackChange: handleFeedbackChange }))),
|
|
116
127
|
error && (react_1.default.createElement(Admonition_1.Admonition, { type: "danger", name: translate(constants_1.AI_SEARCH_ERROR_CONFIG[error].headerKey, constants_1.AI_SEARCH_ERROR_CONFIG[error].headerDefault) }, translate(constants_1.AI_SEARCH_ERROR_CONFIG[error].messageKey, constants_1.AI_SEARCH_ERROR_CONFIG[error].messageDefault))),
|
|
117
128
|
!conversation.length && !error && (react_1.default.createElement(SuggestionsWrapper, null, suggestions === null || suggestions === void 0 ? void 0 : suggestions.map((suggestion) => (react_1.default.createElement(Button_1.Button, { key: suggestion, variant: "outlined", onClick: () => handleOnMessageSent(suggestion) }, suggestion))))),
|
|
118
129
|
react_1.default.createElement("div", { ref: conversationEndRef })),
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
1
2
|
import type { JSX } from 'react';
|
|
3
|
+
import { FeedbackType, type SearchAiMessageResource } from '../../core/types';
|
|
2
4
|
import { AiSearchConversationRole } from '../../core/constants';
|
|
3
5
|
export type SearchAiMessageProps = {
|
|
4
6
|
role: AiSearchConversationRole;
|
|
5
7
|
content: string;
|
|
6
8
|
isThinking?: boolean;
|
|
7
|
-
resources?:
|
|
8
|
-
url: string;
|
|
9
|
-
title: string;
|
|
10
|
-
}[];
|
|
9
|
+
resources?: SearchAiMessageResource[];
|
|
11
10
|
className?: string;
|
|
11
|
+
messageId?: string;
|
|
12
|
+
feedback?: FeedbackType;
|
|
13
|
+
onFeedbackChange: (messageId: string, feedback: FeedbackType | undefined) => void;
|
|
12
14
|
};
|
|
13
|
-
|
|
15
|
+
declare function SearchAiMessageComponent({ role, content, isThinking, resources, className, messageId, feedback, onFeedbackChange, }: SearchAiMessageProps): JSX.Element;
|
|
16
|
+
export declare const SearchAiMessage: React.MemoExoticComponent<typeof SearchAiMessageComponent>;
|
|
17
|
+
export {};
|
|
@@ -1,11 +1,45 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
5
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.SearchAiMessage =
|
|
7
|
-
const react_1 =
|
|
39
|
+
exports.SearchAiMessage = void 0;
|
|
40
|
+
const react_1 = __importStar(require("react"));
|
|
8
41
|
const styled_components_1 = __importDefault(require("styled-components"));
|
|
42
|
+
const types_1 = require("../../core/types");
|
|
9
43
|
const Link_1 = require("../../components/Link/Link");
|
|
10
44
|
const Tag_1 = require("../../components/Tag/Tag");
|
|
11
45
|
const constants_1 = require("../../core/constants");
|
|
@@ -13,29 +47,91 @@ const hooks_1 = require("../../core/hooks");
|
|
|
13
47
|
const Markdown_1 = require("../../components/Markdown/Markdown");
|
|
14
48
|
const DocumentIcon_1 = require("../../icons/DocumentIcon/DocumentIcon");
|
|
15
49
|
const AiStarsIcon_1 = require("../../icons/AiStarsIcon/AiStarsIcon");
|
|
16
|
-
|
|
17
|
-
|
|
50
|
+
const CheckmarkOutlineIcon_1 = require("../../icons/CheckmarkOutlineIcon/CheckmarkOutlineIcon");
|
|
51
|
+
const SearchAiActionButtons_1 = require("./SearchAiActionButtons");
|
|
52
|
+
const SearchAiNegativeFeedbackForm_1 = require("./SearchAiNegativeFeedbackForm");
|
|
53
|
+
function SearchAiMessageComponent({ role, content, isThinking, resources, className, messageId, feedback, onFeedbackChange, }) {
|
|
54
|
+
var _a;
|
|
55
|
+
const { useMarkdownText, useTranslate, useTelemetry } = (0, hooks_1.useThemeHooks)();
|
|
18
56
|
const markDownContent = useMarkdownText(content || '');
|
|
19
57
|
const { translate } = useTranslate();
|
|
58
|
+
const telemetry = useTelemetry();
|
|
59
|
+
const [feedbackSent, setFeedbackSent] = (0, react_1.useState)(false);
|
|
60
|
+
const hasResources = !isThinking && resources && resources.length > 0;
|
|
61
|
+
const resourcesCount = (_a = resources === null || resources === void 0 ? void 0 : resources.length) !== null && _a !== void 0 ? _a : 0;
|
|
62
|
+
const showSuccessMessage = feedbackSent && feedback;
|
|
63
|
+
const sendFeedbackTelemetry = (feedbackValue, dislikeReason) => {
|
|
64
|
+
if (!messageId)
|
|
65
|
+
return;
|
|
66
|
+
try {
|
|
67
|
+
telemetry.sendSearchAIFeedbackMessage({
|
|
68
|
+
feedback: feedbackValue,
|
|
69
|
+
messageId,
|
|
70
|
+
reason: dislikeReason,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
console.error('Error sending feedback', error);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const handleFeedbackClick = (feedbackValue, reason) => {
|
|
78
|
+
if (!messageId) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (!reason) {
|
|
82
|
+
onFeedbackChange(messageId, feedbackValue);
|
|
83
|
+
}
|
|
84
|
+
sendFeedbackTelemetry(feedbackValue, reason);
|
|
85
|
+
if (feedbackValue === types_1.FeedbackType.Like || reason) {
|
|
86
|
+
setFeedbackSent(true);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
20
89
|
return (react_1.default.createElement(SearchAiMessageWrapper, { "data-component-name": "Search/SearchAiMessage", role: role, className: className, "data-testid": "search-ai-message" },
|
|
21
90
|
role === constants_1.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" })),
|
|
22
|
-
react_1.default.createElement(
|
|
23
|
-
|
|
24
|
-
react_1.default.createElement(
|
|
25
|
-
|
|
26
|
-
react_1.default.createElement(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
91
|
+
react_1.default.createElement(MessageContentWrapper, null,
|
|
92
|
+
react_1.default.createElement(MessageWrapper, { role: role },
|
|
93
|
+
role === constants_1.AiSearchConversationRole.ASSISTANT ? (react_1.default.createElement(react_1.default.Fragment, null,
|
|
94
|
+
react_1.default.createElement(ResponseText, { as: "div", children: markDownContent, "data-testid": "response-text" }),
|
|
95
|
+
hasResources && (react_1.default.createElement(react_1.default.Fragment, null,
|
|
96
|
+
react_1.default.createElement(ResourcesWrapper, { "data-testid": "resources-wrapper" },
|
|
97
|
+
react_1.default.createElement(ResourcesTitle, { "data-translation-key": "search.ai.resourcesFound" },
|
|
98
|
+
translate('search.ai.resourcesFound.basedOn', 'Based on'),
|
|
99
|
+
" ",
|
|
100
|
+
resourcesCount,
|
|
101
|
+
' ',
|
|
102
|
+
translate('search.ai.resourcesFound.resources', 'resources')),
|
|
103
|
+
react_1.default.createElement(ResourceTagsWrapper, null, resources === null || resources === void 0 ? void 0 : resources.map((resource, idx) => (react_1.default.createElement(Link_1.Link, { key: `${resource.url}-${idx}`, to: resource.url, target: "_blank" },
|
|
104
|
+
react_1.default.createElement(ResourceTag, { borderless: true, icon: react_1.default.createElement(DocumentIcon_1.DocumentIcon, { color: "--search-ai-resource-tag-icon-color" }) }, resource.title)))))),
|
|
105
|
+
react_1.default.createElement(FeedbackWrapper, null,
|
|
106
|
+
react_1.default.createElement(SearchAiActionButtons_1.SearchAiActionButtons, { content: content, feedback: feedback, onFeedback: handleFeedbackClick, disabled: feedbackSent })))))) : (content),
|
|
107
|
+
isThinking && content.length === 0 && (react_1.default.createElement(ThinkingDotsWrapper, { "data-testid": "thinking-dots-wrapper" },
|
|
108
|
+
react_1.default.createElement(ThinkingDot, null),
|
|
109
|
+
react_1.default.createElement(ThinkingDot, null),
|
|
110
|
+
react_1.default.createElement(ThinkingDot, null)))),
|
|
111
|
+
messageId && feedback === types_1.FeedbackType.Dislike && !showSuccessMessage && (react_1.default.createElement(SearchAiNegativeFeedbackForm_1.SearchAiNegativeFeedbackForm, { messageId: messageId, onClose: onFeedbackChange, onSubmit: (reason) => handleFeedbackClick(types_1.FeedbackType.Dislike, reason) })),
|
|
112
|
+
showSuccessMessage && (react_1.default.createElement(SuccessMessageWrapper, { "data-component-name": "Search/SearchAiMessage/Success" },
|
|
113
|
+
react_1.default.createElement(CheckmarkOutlineIcon_1.CheckmarkOutlineIcon, { size: "20px", color: "var(--color-success-base)" }),
|
|
114
|
+
react_1.default.createElement(SuccessMessageText, null, translate('search.ai.feedback.thanks', 'Thank you for your feedback!')))))));
|
|
115
|
+
}
|
|
116
|
+
function areResourcesEqual(prev, next) {
|
|
117
|
+
if (prev === next)
|
|
118
|
+
return true;
|
|
119
|
+
if (!prev || !next || prev.length !== next.length)
|
|
120
|
+
return false;
|
|
121
|
+
return prev.every((resource, index) => {
|
|
122
|
+
const nextResource = next[index];
|
|
123
|
+
return resource.url === nextResource.url && resource.title === nextResource.title;
|
|
124
|
+
});
|
|
38
125
|
}
|
|
126
|
+
exports.SearchAiMessage = (0, react_1.memo)(SearchAiMessageComponent, (prevProps, nextProps) => {
|
|
127
|
+
return (prevProps.role === nextProps.role &&
|
|
128
|
+
prevProps.content === nextProps.content &&
|
|
129
|
+
prevProps.isThinking === nextProps.isThinking &&
|
|
130
|
+
prevProps.messageId === nextProps.messageId &&
|
|
131
|
+
prevProps.feedback === nextProps.feedback &&
|
|
132
|
+
prevProps.onFeedbackChange === nextProps.onFeedbackChange &&
|
|
133
|
+
areResourcesEqual(prevProps.resources, nextProps.resources));
|
|
134
|
+
});
|
|
39
135
|
const SearchAiMessageWrapper = styled_components_1.default.div `
|
|
40
136
|
display: flex;
|
|
41
137
|
flex-direction: row;
|
|
@@ -43,6 +139,13 @@ const SearchAiMessageWrapper = styled_components_1.default.div `
|
|
|
43
139
|
width: 100%;
|
|
44
140
|
justify-content: ${({ role }) => role === constants_1.AiSearchConversationRole.USER ? 'flex-end' : 'flex-start'};
|
|
45
141
|
`;
|
|
142
|
+
const MessageContentWrapper = styled_components_1.default.div `
|
|
143
|
+
display: flex;
|
|
144
|
+
flex-direction: column;
|
|
145
|
+
gap: var(--spacing-sm);
|
|
146
|
+
max-width: 80%;
|
|
147
|
+
min-width: 0;
|
|
148
|
+
`;
|
|
46
149
|
const ResponseText = (0, styled_components_1.default)(Markdown_1.Markdown) `
|
|
47
150
|
color: var(--search-ai-text-color);
|
|
48
151
|
font-size: var(--search-ai-text-font-size);
|
|
@@ -63,7 +166,8 @@ const MessageWrapper = styled_components_1.default.div `
|
|
|
63
166
|
padding: ${({ role }) => role === constants_1.AiSearchConversationRole.USER ? 'var(--spacing-sm)' : 'var(--spacing-xs)'}
|
|
64
167
|
var(--spacing-sm);
|
|
65
168
|
border-radius: var(--border-radius-lg);
|
|
66
|
-
|
|
169
|
+
width: fit-content;
|
|
170
|
+
max-width: 100%;
|
|
67
171
|
word-wrap: break-word;
|
|
68
172
|
white-space: pre-wrap;
|
|
69
173
|
background-color: ${({ role }) => role === constants_1.AiSearchConversationRole.USER
|
|
@@ -78,7 +182,13 @@ const ResourcesWrapper = styled_components_1.default.div `
|
|
|
78
182
|
gap: var(--search-ai-resources-gap);
|
|
79
183
|
display: flex;
|
|
80
184
|
flex-direction: column;
|
|
81
|
-
margin:
|
|
185
|
+
margin: 0;
|
|
186
|
+
`;
|
|
187
|
+
const FeedbackWrapper = styled_components_1.default.div `
|
|
188
|
+
display: flex;
|
|
189
|
+
flex-direction: row;
|
|
190
|
+
gap: var(--search-ai-feedback-gap);
|
|
191
|
+
margin-top: var(--spacing-sm);
|
|
82
192
|
`;
|
|
83
193
|
const ResourcesTitle = styled_components_1.default.div `
|
|
84
194
|
font-weight: var(--search-ai-resources-title-font-weight);
|
|
@@ -144,4 +254,18 @@ const ThinkingDot = styled_components_1.default.div `
|
|
|
144
254
|
}
|
|
145
255
|
}
|
|
146
256
|
`;
|
|
257
|
+
const SuccessMessageWrapper = styled_components_1.default.div `
|
|
258
|
+
max-width: fit-content;
|
|
259
|
+
display: flex;
|
|
260
|
+
align-items: center;
|
|
261
|
+
gap: var(--spacing-sm);
|
|
262
|
+
padding: var(--spacing-sm);
|
|
263
|
+
background: var(--color-success-bg);
|
|
264
|
+
border: 1px solid var(--color-success-border);
|
|
265
|
+
border-radius: var(--border-radius-lg);
|
|
266
|
+
`;
|
|
267
|
+
const SuccessMessageText = styled_components_1.default.div `
|
|
268
|
+
font-size: var(--font-size-base);
|
|
269
|
+
color: var(--color-success-darker);
|
|
270
|
+
`;
|
|
147
271
|
//# sourceMappingURL=SearchAiMessage.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { JSX } from 'react';
|
|
2
|
+
import type { FeedbackType } from '../../core/types';
|
|
3
|
+
export type SearchAiNegativeFeedbackFormProps = {
|
|
4
|
+
messageId: string;
|
|
5
|
+
onClose: (messageId: string, feedback: FeedbackType | undefined) => void;
|
|
6
|
+
onSubmit: (reason: string) => void;
|
|
7
|
+
};
|
|
8
|
+
export declare function SearchAiNegativeFeedbackForm({ messageId, onClose, onSubmit, }: SearchAiNegativeFeedbackFormProps): JSX.Element;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.SearchAiNegativeFeedbackForm = SearchAiNegativeFeedbackForm;
|
|
40
|
+
const react_1 = __importStar(require("react"));
|
|
41
|
+
const styled_components_1 = __importDefault(require("styled-components"));
|
|
42
|
+
const Button_1 = require("../../components/Button/Button");
|
|
43
|
+
const CloseIcon_1 = require("../../icons/CloseIcon/CloseIcon");
|
|
44
|
+
const ArrowLeftIcon_1 = require("../../icons/ArrowLeftIcon/ArrowLeftIcon");
|
|
45
|
+
const SendIcon_1 = require("../../icons/SendIcon/SendIcon");
|
|
46
|
+
const hooks_1 = require("../../core/hooks");
|
|
47
|
+
const NEGATIVE_FEEDBACK_DEFAULT_REASONS = [
|
|
48
|
+
"Didn't answer my question",
|
|
49
|
+
"Couldn't find what I was looking for",
|
|
50
|
+
'Wrong topic',
|
|
51
|
+
'Partially helpful, but missing details',
|
|
52
|
+
];
|
|
53
|
+
function SearchAiNegativeFeedbackForm({ messageId, onClose, onSubmit, }) {
|
|
54
|
+
const { useTranslate } = (0, hooks_1.useThemeHooks)();
|
|
55
|
+
const { translate } = useTranslate();
|
|
56
|
+
const [showMoreInput, setShowMoreInput] = (0, react_1.useState)(false);
|
|
57
|
+
const [detailedFeedback, setDetailedFeedback] = (0, react_1.useState)('');
|
|
58
|
+
const textAreaRef = react_1.default.useRef(null);
|
|
59
|
+
const adjustTextAreaHeight = () => {
|
|
60
|
+
const textArea = textAreaRef.current;
|
|
61
|
+
if (textArea) {
|
|
62
|
+
textArea.style.height = 'auto';
|
|
63
|
+
textArea.style.height = `${textArea.scrollHeight}px`;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const handleTextChange = (e) => {
|
|
67
|
+
setDetailedFeedback(e.target.value);
|
|
68
|
+
adjustTextAreaHeight();
|
|
69
|
+
};
|
|
70
|
+
(0, react_1.useEffect)(() => {
|
|
71
|
+
if (showMoreInput) {
|
|
72
|
+
adjustTextAreaHeight();
|
|
73
|
+
}
|
|
74
|
+
}, [showMoreInput]);
|
|
75
|
+
return (react_1.default.createElement(FeedbackFormWrapper, { "data-component-name": "Search/SearchAiNegativeFeedbackForm" },
|
|
76
|
+
react_1.default.createElement(FeedbackHeader, null,
|
|
77
|
+
showMoreInput ? (react_1.default.createElement(BackButton, { variant: "text", size: "small", icon: react_1.default.createElement(ArrowLeftIcon_1.ArrowLeftIcon, null), onClick: () => setShowMoreInput(false), "aria-label": "Back to feedback reasons" })) : null,
|
|
78
|
+
react_1.default.createElement(FeedbackTitle, { "data-translation-key": "search.ai.feedback.title" }, translate('search.ai.feedback.title', "What didn't you like about this response?")),
|
|
79
|
+
react_1.default.createElement(CloseButton, { variant: "text", size: "small", icon: react_1.default.createElement(CloseIcon_1.CloseIcon, null), onClick: () => onClose(messageId, undefined), "aria-label": "Close feedback form" })),
|
|
80
|
+
!showMoreInput ? (react_1.default.createElement(FeedbackReasonsWrapper, null,
|
|
81
|
+
NEGATIVE_FEEDBACK_DEFAULT_REASONS.map((reason) => (react_1.default.createElement(Button_1.Button, { key: reason, variant: "outlined", size: "small", onClick: () => onSubmit(reason) }, reason))),
|
|
82
|
+
react_1.default.createElement(Button_1.Button, { variant: "outlined", size: "small", onClick: () => setShowMoreInput(true) }, "More..."))) : (react_1.default.createElement(FeedbackInputWrapper, null,
|
|
83
|
+
react_1.default.createElement(FeedbackTextArea, { ref: textAreaRef, placeholder: translate('search.ai.feedback.detailsPlaceholder', 'Add specific details'), value: detailedFeedback, onChange: handleTextChange, rows: 1, autoFocus: true }),
|
|
84
|
+
react_1.default.createElement(SendButton, { size: "small", icon: react_1.default.createElement(SendIcon_1.SendIcon, { color: !detailedFeedback.trim()
|
|
85
|
+
? '--button-content-color-disabled'
|
|
86
|
+
: 'var(--search-ai-conversation-input-send-button-icon-color)' }), onClick: () => onSubmit(detailedFeedback), disabled: !detailedFeedback.trim(), "aria-label": "Send feedback" })))));
|
|
87
|
+
}
|
|
88
|
+
const FeedbackFormWrapper = styled_components_1.default.div `
|
|
89
|
+
display: flex;
|
|
90
|
+
flex-direction: column;
|
|
91
|
+
gap: var(--spacing-sm);
|
|
92
|
+
padding: var(--spacing-xs);
|
|
93
|
+
background: var(--search-ai-feedback-form-bg-color);
|
|
94
|
+
border: 1px solid var(--search-ai-feedback-form-border-color);
|
|
95
|
+
border-radius: var(--border-radius-lg);
|
|
96
|
+
`;
|
|
97
|
+
const FeedbackHeader = styled_components_1.default.div `
|
|
98
|
+
display: flex;
|
|
99
|
+
justify-content: space-between;
|
|
100
|
+
align-items: center;
|
|
101
|
+
gap: var(--spacing-sm);
|
|
102
|
+
`;
|
|
103
|
+
const FeedbackTitle = styled_components_1.default.div `
|
|
104
|
+
font-size: var(--font-size-base);
|
|
105
|
+
color: var(--text-color);
|
|
106
|
+
flex: 1;
|
|
107
|
+
`;
|
|
108
|
+
const BackButton = (0, styled_components_1.default)(Button_1.Button) `
|
|
109
|
+
flex-shrink: 0;
|
|
110
|
+
`;
|
|
111
|
+
const CloseButton = (0, styled_components_1.default)(Button_1.Button) `
|
|
112
|
+
flex-shrink: 0;
|
|
113
|
+
`;
|
|
114
|
+
const FeedbackReasonsWrapper = styled_components_1.default.div `
|
|
115
|
+
display: flex;
|
|
116
|
+
flex-wrap: wrap;
|
|
117
|
+
gap: var(--spacing-xs);
|
|
118
|
+
`;
|
|
119
|
+
const FeedbackInputWrapper = styled_components_1.default.div `
|
|
120
|
+
position: relative;
|
|
121
|
+
width: 100%;
|
|
122
|
+
`;
|
|
123
|
+
const FeedbackTextArea = styled_components_1.default.textarea `
|
|
124
|
+
width: 100%;
|
|
125
|
+
min-height: 5rem;
|
|
126
|
+
max-height: 12.5rem;
|
|
127
|
+
padding: var(--spacing-xs);
|
|
128
|
+
padding-right: 3rem;
|
|
129
|
+
border: 1px solid var(--border-color-primary);
|
|
130
|
+
border-radius: var(--border-radius-md);
|
|
131
|
+
font-family: inherit;
|
|
132
|
+
font-size: var(--font-size-base);
|
|
133
|
+
line-height: var(--line-height-base);
|
|
134
|
+
background: var(--background-color);
|
|
135
|
+
color: var(--text-color);
|
|
136
|
+
resize: none;
|
|
137
|
+
overflow-y: auto;
|
|
138
|
+
|
|
139
|
+
&:focus {
|
|
140
|
+
outline: 1px solid var(--button-border-color-focused);
|
|
141
|
+
border-color: var(--button-border-color-focused);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
&::placeholder {
|
|
145
|
+
color: var(--text-color-helper);
|
|
146
|
+
}
|
|
147
|
+
`;
|
|
148
|
+
const SendButton = (0, styled_components_1.default)(Button_1.Button) `
|
|
149
|
+
position: absolute;
|
|
150
|
+
right: var(--search-ai-conversation-input-send-button-right);
|
|
151
|
+
bottom: var(--spacing-sm);
|
|
152
|
+
transition: background-color 0.2s ease;
|
|
153
|
+
background-color: var(--search-ai-conversation-input-send-button-bg-color);
|
|
154
|
+
display: flex;
|
|
155
|
+
align-items: center;
|
|
156
|
+
justify-content: center;
|
|
157
|
+
border-radius: var(--search-ai-conversation-input-send-button-border-radius);
|
|
158
|
+
padding: var(--search-ai-conversation-input-send-button-padding);
|
|
159
|
+
|
|
160
|
+
&:hover {
|
|
161
|
+
background-color: var(--search-ai-conversation-input-send-button-bg-color-hover);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
&:disabled {
|
|
165
|
+
background-color: var(--search-ai-conversation-input-send-button-bg-color-disabled);
|
|
166
|
+
border: var(--search-ai-conversation-input-send-button-border-disabled);
|
|
167
|
+
}
|
|
168
|
+
`;
|
|
169
|
+
//# sourceMappingURL=SearchAiNegativeFeedbackForm.js.map
|