@redocly/theme 0.59.0-next.0 → 0.59.0-next.10

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.
Files changed (262) hide show
  1. package/LICENSE +7 -1
  2. package/lib/components/Accordion/Accordion.d.ts +12 -0
  3. package/lib/components/Accordion/Accordion.js +85 -0
  4. package/lib/components/Accordion/AccordionBody.d.ts +8 -0
  5. package/lib/components/Accordion/AccordionBody.js +73 -0
  6. package/lib/components/Accordion/AccordionHeader.d.ts +10 -0
  7. package/lib/components/Accordion/AccordionHeader.js +37 -0
  8. package/lib/components/Accordion/AccordionTitle.d.ts +6 -0
  9. package/lib/components/Accordion/AccordionTitle.js +20 -0
  10. package/lib/components/Accordion/variables.d.ts +1 -0
  11. package/lib/components/Accordion/variables.js +59 -0
  12. package/lib/components/Admonition/Admonition.js +17 -7
  13. package/lib/components/Badge/Badge.js +17 -7
  14. package/lib/components/Breadcrumbs/Breadcrumb.js +17 -7
  15. package/lib/components/Breadcrumbs/BreadcrumbDropdown.js +17 -7
  16. package/lib/components/Button/Button.js +17 -7
  17. package/lib/components/Buttons/AIAssistantButton.d.ts +2 -0
  18. package/lib/components/Buttons/AIAssistantButton.js +139 -0
  19. package/lib/components/Buttons/CopyButton.js +17 -7
  20. package/lib/components/Buttons/variables.d.ts +1 -0
  21. package/lib/components/Buttons/variables.dark.d.ts +1 -0
  22. package/lib/components/Buttons/variables.dark.js +10 -0
  23. package/lib/components/Buttons/variables.js +51 -0
  24. package/lib/components/Catalog/Catalog.d.ts +6 -0
  25. package/lib/components/Catalog/Catalog.js +9 -8
  26. package/lib/components/Catalog/CatalogEntities.js +17 -7
  27. package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.js +17 -7
  28. package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.js +17 -7
  29. package/lib/components/Catalog/CatalogEntity/CatalogEntityInfoBar.js +1 -0
  30. package/lib/components/Catalog/CatalogEntity/CatalogEntityMetadata.js +17 -7
  31. package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityApiDescriptionRelations.js +1 -1
  32. package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelations.js +17 -7
  33. package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityTeamRelations.js +1 -1
  34. package/lib/components/Catalog/CatalogEntity/CatalogEntitySchema.js +17 -7
  35. package/lib/components/Catalog/CatalogEntityIcon.js +2 -1
  36. package/lib/components/Catalog/CatalogFilter/CatalogFilter.d.ts +6 -0
  37. package/lib/components/Catalog/CatalogFilter/CatalogFilter.js +39 -0
  38. package/lib/components/Catalog/CatalogFilter/CatalogFilterCheckboxes.d.ts +6 -0
  39. package/lib/components/Catalog/CatalogFilter/CatalogFilterCheckboxes.js +152 -0
  40. package/lib/components/Catalog/CatalogFilter/CatalogFilterContent.d.ts +13 -0
  41. package/lib/components/Catalog/CatalogFilter/CatalogFilterContent.js +102 -0
  42. package/lib/components/Catalog/CatalogFilter/CatalogFilterDateRange.d.ts +6 -0
  43. package/lib/components/Catalog/CatalogFilter/CatalogFilterDateRange.js +121 -0
  44. package/lib/components/Catalog/CatalogFilter/CatalogFilterSelect.d.ts +6 -0
  45. package/lib/components/Catalog/CatalogFilter/CatalogFilterSelect.js +126 -0
  46. package/lib/components/Catalog/CatalogSelector.js +0 -1
  47. package/lib/components/Catalog/CatalogSortButton.js +17 -7
  48. package/lib/components/Catalog/CatalogTableView/CatalogTableHeaderCell.js +17 -7
  49. package/lib/components/Catalog/CatalogViewModeToggle.js +17 -7
  50. package/lib/components/Catalog/variables.js +1 -2
  51. package/lib/components/CatalogClassic/CatalogClassicActions.js +17 -7
  52. package/lib/components/CatalogClassic/CatalogClassicCard.js +17 -7
  53. package/lib/components/CatalogClassic/CatalogClassicHighlight.js +17 -7
  54. package/lib/components/CatalogClassic/CatalogClassicVirtualizedGroups.js +17 -7
  55. package/lib/components/CodeBlock/CodeBlock.js +17 -7
  56. package/lib/components/CodeBlock/CodeBlockContainer.js +17 -7
  57. package/lib/components/CodeBlock/CodeBlockTabs.js +17 -7
  58. package/lib/components/Dropdown/Dropdown.d.ts +16 -2
  59. package/lib/components/Dropdown/Dropdown.js +22 -12
  60. package/lib/components/Dropdown/DropdownMenuItem.js +17 -7
  61. package/lib/components/Feedback/Comment.js +17 -7
  62. package/lib/components/Feedback/Feedback.js +17 -7
  63. package/lib/components/Feedback/Mood.js +17 -7
  64. package/lib/components/Feedback/Rating.js +17 -7
  65. package/lib/components/Feedback/Reasons.js +17 -7
  66. package/lib/components/Feedback/Scale.js +17 -7
  67. package/lib/components/Feedback/Sentiment.js +17 -7
  68. package/lib/components/Feedback/Stars.js +17 -7
  69. package/lib/components/Filter/FilterContent.js +17 -7
  70. package/lib/components/Filter/FilterInput.d.ts +1 -0
  71. package/lib/components/Filter/FilterInput.js +19 -9
  72. package/lib/components/Filter/FilterOptions.js +2 -0
  73. package/lib/components/Filter/variables.js +7 -4
  74. package/lib/components/Image/Image.js +17 -7
  75. package/lib/components/JsonViewer/JsonViewer.js +17 -7
  76. package/lib/components/JsonViewer/helpers.js +17 -7
  77. package/lib/components/LastUpdated/LastUpdated.js +17 -7
  78. package/lib/components/Link/Link.js +17 -7
  79. package/lib/components/Markdown/Markdown.js +17 -7
  80. package/lib/components/Marker/Marker.js +17 -7
  81. package/lib/components/Menu/MenuContainer.js +17 -7
  82. package/lib/components/Menu/MenuItem.js +18 -8
  83. package/lib/components/Menu/MenuMobile.js +17 -7
  84. package/lib/components/Navbar/NavbarItem.js +3 -3
  85. package/lib/components/PageActions/PageActions.js +17 -7
  86. package/lib/components/PageNavigation/NextButton.js +17 -7
  87. package/lib/components/Panel/Panel.js +17 -7
  88. package/lib/components/Panel/PanelBody.js +17 -7
  89. package/lib/components/Search/FilterFields/SearchFilterFieldSelect.js +17 -7
  90. package/lib/components/Search/FilterFields/SearchFilterFieldTags.js +1 -2
  91. package/lib/components/Search/SearchAiActionButtons.d.ts +10 -0
  92. package/lib/components/Search/SearchAiActionButtons.js +43 -0
  93. package/lib/components/Search/SearchAiConversationInput.d.ts +3 -1
  94. package/lib/components/Search/SearchAiConversationInput.js +56 -14
  95. package/lib/components/Search/SearchAiDialog.d.ts +3 -6
  96. package/lib/components/Search/SearchAiDialog.js +39 -19
  97. package/lib/components/Search/SearchAiMessage.d.ts +9 -5
  98. package/lib/components/Search/SearchAiMessage.js +146 -22
  99. package/lib/components/Search/SearchAiNegativeFeedbackForm.d.ts +8 -0
  100. package/lib/components/Search/SearchAiNegativeFeedbackForm.js +169 -0
  101. package/lib/components/Search/SearchAiResponse.js +2 -3
  102. package/lib/components/Search/SearchDialog.d.ts +2 -1
  103. package/lib/components/Search/SearchDialog.js +55 -14
  104. package/lib/components/Search/SearchFilter.js +17 -7
  105. package/lib/components/Search/SearchGroups.js +19 -9
  106. package/lib/components/Search/SearchHighlight.js +17 -7
  107. package/lib/components/Search/SearchItem.js +17 -7
  108. package/lib/components/Search/SearchRecent.js +17 -7
  109. package/lib/components/Search/SearchShortcut.js +17 -7
  110. package/lib/components/Search/SearchSuggestedPages.js +17 -7
  111. package/lib/components/Search/SearchTrigger.js +17 -7
  112. package/lib/components/Search/variables.js +36 -64
  113. package/lib/components/Segmented/Segmented.js +17 -7
  114. package/lib/components/Select/Select.js +17 -7
  115. package/lib/components/Select/SelectInput.js +18 -8
  116. package/lib/components/Sidebar/Sidebar.js +17 -7
  117. package/lib/components/SidebarActions/styled.js +17 -7
  118. package/lib/components/SkipContent/SkipContent.js +17 -7
  119. package/lib/components/Switch/Switch.js +17 -7
  120. package/lib/components/TableOfContent/TableOfContent.js +17 -7
  121. package/lib/components/Tag/Tag.d.ts +2 -1
  122. package/lib/components/Tag/Tag.js +67 -18
  123. package/lib/components/Tag/variables.dark.js +137 -38
  124. package/lib/components/Tag/variables.js +78 -61
  125. package/lib/components/Tooltip/Tooltip.js +17 -7
  126. package/lib/components/VersionPicker/VersionPicker.js +17 -7
  127. package/lib/core/constants/search.d.ts +5 -4
  128. package/lib/core/constants/search.js +4 -5
  129. package/lib/core/contexts/CodeSnippetContext.js +17 -7
  130. package/lib/core/hooks/index.d.ts +1 -0
  131. package/lib/core/hooks/index.js +1 -0
  132. package/lib/core/hooks/menu/use-nested-menu.js +1 -1
  133. package/lib/core/hooks/search/use-feedback-tooltip.d.ts +6 -0
  134. package/lib/core/hooks/search/use-feedback-tooltip.js +26 -0
  135. package/lib/core/hooks/use-product-picker.js +2 -1
  136. package/lib/core/hooks/use-tabs.d.ts +3 -2
  137. package/lib/core/hooks/use-tabs.js +115 -57
  138. package/lib/core/hooks/use-telemetry-fallback.d.ts +10 -8
  139. package/lib/core/hooks/use-telemetry-fallback.js +10 -8
  140. package/lib/core/styles/dark.js +33 -26
  141. package/lib/core/styles/global.js +68 -59
  142. package/lib/core/templates/Markdown.js +17 -7
  143. package/lib/core/types/hooks.d.ts +6 -3
  144. package/lib/core/types/l10n.d.ts +1 -1
  145. package/lib/core/types/search.d.ts +11 -4
  146. package/lib/core/types/search.js +6 -0
  147. package/lib/core/utils/download-code-walkthrough.js +17 -7
  148. package/lib/core/utils/frontmatter-translate.d.ts +6 -0
  149. package/lib/core/utils/frontmatter-translate.js +14 -0
  150. package/lib/core/utils/get-file-icon.js +17 -7
  151. package/lib/core/utils/index.d.ts +1 -0
  152. package/lib/core/utils/index.js +1 -0
  153. package/lib/icons/AiStarsGradientIcon/AiStarsGradientIcon.js +44 -4
  154. package/lib/icons/AiStarsIcon/AiStarsIcon.js +11 -2
  155. package/lib/icons/CubeIcon/CubeIcon.d.ts +9 -0
  156. package/lib/icons/CubeIcon/CubeIcon.js +17 -0
  157. package/lib/icons/GenericIcon/GenericIcon.js +17 -7
  158. package/lib/icons/HashtagIcon/HashtagIcon.d.ts +9 -0
  159. package/lib/icons/HashtagIcon/HashtagIcon.js +22 -0
  160. package/lib/icons/RedoclyIcon/RedoclyIcon.d.ts +9 -0
  161. package/lib/icons/RedoclyIcon/RedoclyIcon.js +24 -0
  162. package/lib/icons/Spinner/Spinner.js +17 -7
  163. package/lib/icons/ThumbDownFilledIcon/ThumbDownFilledIcon.d.ts +9 -0
  164. package/lib/icons/ThumbDownFilledIcon/ThumbDownFilledIcon.js +34 -0
  165. package/lib/icons/ThumbUpFilledIcon/ThumbUpFilledIcon.d.ts +9 -0
  166. package/lib/icons/ThumbUpFilledIcon/ThumbUpFilledIcon.js +34 -0
  167. package/lib/index.d.ts +3 -0
  168. package/lib/index.js +20 -7
  169. package/lib/layouts/OIDCForbidden.js +17 -7
  170. package/lib/layouts/RootLayout.js +6 -1
  171. package/lib/layouts/ThreePanelLayout.js +17 -7
  172. package/lib/markdoc/components/Cards/Card.js +1 -28
  173. package/lib/markdoc/components/Cards/Cards.js +17 -7
  174. package/lib/markdoc/components/CodeGroup/CodeGroup.js +17 -7
  175. package/lib/markdoc/components/CodeWalkthrough/CodeContainer.js +17 -7
  176. package/lib/markdoc/components/CodeWalkthrough/CodePanel.js +17 -7
  177. package/lib/markdoc/components/CodeWalkthrough/CodePanelHeader.js +17 -7
  178. package/lib/markdoc/components/CodeWalkthrough/CodePanelPreview.js +17 -7
  179. package/lib/markdoc/components/CodeWalkthrough/CodePanelToolbar.js +17 -7
  180. package/lib/markdoc/components/CodeWalkthrough/CodeStep.js +17 -7
  181. package/lib/markdoc/components/CodeWalkthrough/CodeToggle.js +17 -7
  182. package/lib/markdoc/components/CodeWalkthrough/CodeWalkthrough.js +17 -7
  183. package/lib/markdoc/components/CodeWalkthrough/Input.js +17 -7
  184. package/lib/markdoc/components/Heading/Heading.js +17 -7
  185. package/lib/markdoc/components/HtmlBlock/HtmlBlock.js +17 -7
  186. package/lib/markdoc/components/InlineSvg/InlineSvg.js +17 -7
  187. package/lib/markdoc/components/MarkdocExample/MarkdocExample.js +17 -7
  188. package/lib/markdoc/components/Tabs/TabList.d.ts +3 -1
  189. package/lib/markdoc/components/Tabs/TabList.js +214 -54
  190. package/lib/markdoc/components/Tabs/Tabs.d.ts +2 -1
  191. package/lib/markdoc/components/Tabs/Tabs.js +74 -19
  192. package/lib/markdoc/default.d.ts +104 -1
  193. package/lib/markdoc/default.js +17 -7
  194. package/lib/markdoc/tags/card.js +0 -1
  195. package/package.json +8 -8
  196. package/src/components/Accordion/Accordion.tsx +100 -0
  197. package/src/components/Accordion/AccordionBody.tsx +65 -0
  198. package/src/components/Accordion/AccordionHeader.tsx +68 -0
  199. package/src/components/Accordion/AccordionTitle.tsx +26 -0
  200. package/src/components/Accordion/variables.ts +56 -0
  201. package/src/components/Buttons/AIAssistantButton.tsx +145 -0
  202. package/src/components/Buttons/variables.dark.ts +7 -0
  203. package/src/components/Buttons/variables.ts +48 -0
  204. package/src/components/Catalog/Catalog.tsx +18 -6
  205. package/src/components/Catalog/CatalogEntity/CatalogEntityInfoBar.tsx +1 -0
  206. package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityApiDescriptionRelations.tsx +1 -1
  207. package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityTeamRelations.tsx +1 -1
  208. package/src/components/Catalog/CatalogEntityIcon.tsx +2 -1
  209. package/src/components/Catalog/CatalogFilter/CatalogFilter.tsx +61 -0
  210. package/src/components/Catalog/CatalogFilter/CatalogFilterCheckboxes.tsx +169 -0
  211. package/src/components/Catalog/CatalogFilter/CatalogFilterContent.tsx +121 -0
  212. package/src/components/Catalog/CatalogFilter/CatalogFilterDateRange.tsx +147 -0
  213. package/src/components/Catalog/CatalogFilter/CatalogFilterSelect.tsx +136 -0
  214. package/src/components/Catalog/CatalogSelector.tsx +0 -1
  215. package/src/components/Catalog/variables.ts +1 -2
  216. package/src/components/Dropdown/Dropdown.tsx +84 -79
  217. package/src/components/Filter/FilterInput.tsx +3 -2
  218. package/src/components/Filter/FilterOptions.tsx +2 -0
  219. package/src/components/Filter/variables.ts +7 -4
  220. package/src/components/Menu/MenuItem.tsx +1 -0
  221. package/src/components/Navbar/NavbarItem.tsx +6 -5
  222. package/src/components/Search/FilterFields/SearchFilterFieldTags.tsx +3 -3
  223. package/src/components/Search/SearchAiActionButtons.tsx +76 -0
  224. package/src/components/Search/SearchAiConversationInput.tsx +61 -18
  225. package/src/components/Search/SearchAiDialog.tsx +54 -25
  226. package/src/components/Search/SearchAiMessage.tsx +172 -43
  227. package/src/components/Search/SearchAiNegativeFeedbackForm.tsx +210 -0
  228. package/src/components/Search/SearchAiResponse.tsx +2 -2
  229. package/src/components/Search/SearchDialog.tsx +56 -15
  230. package/src/components/Search/SearchGroups.tsx +2 -0
  231. package/src/components/Search/variables.ts +36 -64
  232. package/src/components/Select/SelectInput.tsx +1 -0
  233. package/src/components/Tag/Tag.tsx +36 -20
  234. package/src/components/Tag/variables.dark.ts +137 -38
  235. package/src/components/Tag/variables.ts +78 -61
  236. package/src/core/constants/search.ts +8 -4
  237. package/src/core/hooks/index.ts +1 -0
  238. package/src/core/hooks/menu/use-nested-menu.ts +2 -2
  239. package/src/core/hooks/search/use-feedback-tooltip.ts +32 -0
  240. package/src/core/hooks/use-product-picker.ts +2 -1
  241. package/src/core/hooks/use-tabs.ts +168 -86
  242. package/src/core/hooks/use-telemetry-fallback.ts +10 -8
  243. package/src/core/styles/dark.ts +15 -8
  244. package/src/core/styles/global.ts +11 -2
  245. package/src/core/types/hooks.ts +6 -1
  246. package/src/core/types/l10n.ts +6 -0
  247. package/src/core/types/search.ts +13 -4
  248. package/src/core/utils/frontmatter-translate.ts +9 -0
  249. package/src/core/utils/index.ts +1 -0
  250. package/src/icons/AiStarsGradientIcon/AiStarsGradientIcon.tsx +13 -4
  251. package/src/icons/AiStarsIcon/AiStarsIcon.tsx +11 -2
  252. package/src/icons/CubeIcon/CubeIcon.tsx +27 -0
  253. package/src/icons/HashtagIcon/HashtagIcon.tsx +23 -0
  254. package/src/icons/RedoclyIcon/RedoclyIcon.tsx +26 -0
  255. package/src/icons/ThumbDownFilledIcon/ThumbDownFilledIcon.tsx +38 -0
  256. package/src/icons/ThumbUpFilledIcon/ThumbUpFilledIcon.tsx +35 -0
  257. package/src/index.ts +3 -0
  258. package/src/layouts/RootLayout.tsx +6 -0
  259. package/src/markdoc/components/Cards/Card.tsx +1 -28
  260. package/src/markdoc/components/Tabs/TabList.tsx +312 -105
  261. package/src/markdoc/components/Tabs/Tabs.tsx +136 -11
  262. package/src/markdoc/tags/card.ts +0 -1
@@ -13,6 +13,8 @@ type SearchAiConversationInputProps = {
13
13
  isGeneratingResponse: boolean;
14
14
  placeholder?: string;
15
15
  className?: string;
16
+ disabled?: boolean;
17
+ multiline?: boolean;
16
18
  };
17
19
 
18
20
  export function SearchAiConversationInput({
@@ -20,11 +22,13 @@ export function SearchAiConversationInput({
20
22
  onMessageSent,
21
23
  className,
22
24
  placeholder,
25
+ disabled,
26
+ multiline = false,
23
27
  }: SearchAiConversationInputProps): JSX.Element {
24
28
  const { useTranslate } = useThemeHooks();
25
29
  const { translate } = useTranslate();
26
30
 
27
- const inputRef = useRef<HTMLInputElement>(null);
31
+ const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement>(null);
28
32
  const [query, setQuery] = useState('');
29
33
 
30
34
  useEffect(() => {
@@ -36,35 +40,54 @@ export function SearchAiConversationInput({
36
40
  }, [isGeneratingResponse]);
37
41
 
38
42
  const handleSendMessage = () => {
43
+ if (disabled) return;
44
+
39
45
  setQuery('');
40
46
  onMessageSent(query);
41
47
  };
42
48
 
43
- const handleOnKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
49
+ const handleOnKeyDown = (
50
+ e: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>,
51
+ ): void => {
44
52
  if (e.key === 'Enter' && !isGeneratingResponse) {
53
+ if (multiline && e.shiftKey) {
54
+ return; // Allow new line in textarea
55
+ }
56
+ e.preventDefault();
45
57
  handleSendMessage();
46
58
  }
47
59
  };
48
60
 
49
- const isDisabled = isGeneratingResponse || query.trim().length === 0;
61
+ const isDisabled = disabled || isGeneratingResponse || query.trim().length === 0;
62
+
63
+ const commonProps = {
64
+ placeholder:
65
+ placeholder || translate('search.ai.followUpQuestion', 'Ask a follow up question?'),
66
+ onChange: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) =>
67
+ setQuery(e.target.value),
68
+ onKeyDown: handleOnKeyDown,
69
+ value: query,
70
+ disabled: disabled || isGeneratingResponse,
71
+ maxLength: AI_SEARCH_MAX_MESSAGE_LENGTH,
72
+ };
50
73
 
51
74
  return (
52
75
  <SearchAiConversationInputWrapper
53
76
  data-component-name="Search/SearchAiConversationInput"
54
77
  className={className}
55
78
  >
56
- <ConversationInput
57
- ref={inputRef}
58
- type="text"
59
- placeholder={
60
- placeholder || translate('search.ai.followUpQuestion', 'Ask a follow up question?')
61
- }
62
- onChange={(e) => setQuery(e.target.value)}
63
- onKeyUp={handleOnKeyUp}
64
- value={query}
65
- disabled={isGeneratingResponse}
66
- maxLength={AI_SEARCH_MAX_MESSAGE_LENGTH}
67
- />
79
+ {multiline ? (
80
+ <ConversationTextarea
81
+ ref={inputRef as React.RefObject<HTMLTextAreaElement>}
82
+ {...commonProps}
83
+ />
84
+ ) : (
85
+ <ConversationInput
86
+ ref={inputRef as React.RefObject<HTMLInputElement>}
87
+ type="text"
88
+ {...commonProps}
89
+ />
90
+ )}
68
91
 
69
92
  <SendButton
70
93
  disabled={isDisabled}
@@ -93,9 +116,10 @@ const SearchAiConversationInputWrapper = styled.div`
93
116
  position: relative;
94
117
  `;
95
118
 
96
- const ConversationInput = styled.input`
119
+ const inputStyles = `
97
120
  width: 100%;
98
121
  padding: var(--search-ai-conversation-input-padding);
122
+ padding-right: var(--search-ai-conversation-input-padding-right, 52px);
99
123
  border: var(--search-ai-conversation-input-border);
100
124
  border-radius: var(--search-ai-conversation-input-border-radius);
101
125
  background-color: var(--search-ai-conversation-input-bg-color);
@@ -111,21 +135,40 @@ const ConversationInput = styled.input`
111
135
  border-color: var(--search-ai-conversation-input-border-color-focus);
112
136
  }
113
137
 
138
+ &:disabled {
139
+ background-color: var(--search-ai-conversation-input-bg-color-disabled);
140
+ }
141
+
114
142
  &:focus:disabled {
115
143
  border-color: var(--search-ai-conversation-input-border-color-disabled);
116
144
  }
117
145
  `;
118
146
 
147
+ const ConversationInput = styled.input`
148
+ ${inputStyles}
149
+ height: var(--search-ai-conversation-input-min-height);
150
+ `;
151
+
152
+ const ConversationTextarea = styled.textarea`
153
+ ${inputStyles}
154
+ min-height: var(--search-ai-conversation-input-min-height);
155
+ max-height: var(--search-ai-conversation-input-max-height);
156
+ resize: none;
157
+ field-sizing: content;
158
+ `;
159
+
119
160
  const SendButton = styled(Button)`
120
161
  position: absolute;
121
162
  right: var(--search-ai-conversation-input-send-button-right);
122
- top: 50%;
123
- transform: translateY(-50%);
163
+ bottom: var(--search-ai-conversation-input-send-button-bottom);
164
+ transform: translateY(50%);
124
165
  transition: background-color 0.2s ease;
125
166
  background-color: var(--search-ai-conversation-input-send-button-bg-color);
126
167
  display: flex;
127
168
  align-items: center;
128
169
  justify-content: center;
170
+ border-radius: var(--search-ai-conversation-input-send-button-border-radius);
171
+ padding: var(--search-ai-conversation-input-send-button-padding);
129
172
 
130
173
  &:hover {
131
174
  background-color: var(--search-ai-conversation-input-send-button-bg-color-hover);
@@ -1,7 +1,12 @@
1
- import React, { useEffect } from 'react';
1
+ import React, { useEffect, useCallback } from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
4
  import type { JSX } from 'react';
5
+ import type {
6
+ AiSearchConversationItem,
7
+ SearchAiMessageResource,
8
+ FeedbackType,
9
+ } from '@redocly/theme/core/types';
5
10
 
6
11
  import { useThemeConfig, useThemeHooks } from '@redocly/theme/core/hooks';
7
12
  import { Button } from '@redocly/theme/components/Button/Button';
@@ -11,7 +16,6 @@ import {
11
16
  AI_SEARCH_ERROR_CONFIG as ERROR_CONFIG,
12
17
  AiSearchConversationRole,
13
18
  } from '@redocly/theme/core/constants';
14
- import { AiSearchConversationItem } from '@redocly/theme/core/types';
15
19
  import { SearchAiMessage } from '@redocly/theme/components/Search/SearchAiMessage';
16
20
  import { Admonition } from '@redocly/theme/components/Admonition/Admonition';
17
21
  import { AiStarsIcon } from '@redocly/theme/icons/AiStarsIcon/AiStarsIcon';
@@ -20,15 +24,16 @@ export type SearchAiDialogProps = {
20
24
  response: string | undefined;
21
25
  isGeneratingResponse: boolean;
22
26
  error: AiSearchError | null;
23
- resources: {
24
- url: string;
25
- title: string;
26
- }[];
27
+ resources: SearchAiMessageResource[];
27
28
  initialMessage?: string;
28
29
  className?: string;
29
30
  conversation: AiSearchConversationItem[];
30
31
  setConversation: React.Dispatch<React.SetStateAction<AiSearchConversationItem[]>>;
31
- onMessageSent: (message: string, history?: AiSearchConversationItem[]) => void;
32
+ onMessageSent: (
33
+ message: string,
34
+ history?: AiSearchConversationItem[],
35
+ messageId?: string,
36
+ ) => void;
32
37
  };
33
38
 
34
39
  export function SearchAiDialog({
@@ -43,12 +48,12 @@ export function SearchAiDialog({
43
48
  setConversation,
44
49
  }: SearchAiDialogProps): JSX.Element {
45
50
  const { useTranslate } = useThemeHooks();
46
- const { search } = useThemeConfig();
51
+ const { aiAssistant } = useThemeConfig();
47
52
  const { translate } = useTranslate();
48
53
 
49
54
  const conversationEndRef = React.useRef<HTMLDivElement>(null);
50
55
 
51
- const suggestions = search?.ai?.suggestions;
56
+ const suggestions = aiAssistant?.suggestions;
52
57
 
53
58
  const placeholder = isGeneratingResponse
54
59
  ? translate('search.ai.generatingResponse', 'Generating response...')
@@ -56,21 +61,28 @@ export function SearchAiDialog({
56
61
  ? translate('search.ai.followUpQuestion', 'Ask a follow up question?')
57
62
  : translate('search.ai.placeholder', 'Ask a question...');
58
63
 
59
- const scrollToBottom = () => {
64
+ const scrollToBottom = useCallback(() => {
60
65
  conversationEndRef.current?.scrollIntoView({ block: 'end' });
61
- };
66
+ }, []);
62
67
 
63
- const handleOnMessageSent = (message: string) => {
64
- if (!message.trim()) {
65
- return;
66
- }
67
- const mappedHistory = conversation.map(({ role, content }) => ({
68
- role,
69
- content,
70
- }));
71
- onMessageSent(message, mappedHistory);
72
- setConversation((prev) => [...prev, { role: AiSearchConversationRole.USER, content: message }]);
73
- };
68
+ const handleOnMessageSent = useCallback(
69
+ (message: string) => {
70
+ if (!message.trim()) {
71
+ return;
72
+ }
73
+ const mappedHistory = conversation.map(({ role, content }) => ({
74
+ role,
75
+ content,
76
+ }));
77
+
78
+ onMessageSent(message, mappedHistory);
79
+ setConversation((prev) => [
80
+ ...prev,
81
+ { role: AiSearchConversationRole.USER, content: message },
82
+ ]);
83
+ },
84
+ [conversation, onMessageSent, setConversation],
85
+ );
74
86
 
75
87
  useEffect(() => {
76
88
  if (!initialMessage?.trim().length) {
@@ -94,11 +106,16 @@ export function SearchAiDialog({
94
106
  if (lastMessage && lastMessage.role === AiSearchConversationRole.ASSISTANT) {
95
107
  return [
96
108
  ...prev.slice(0, -1),
97
- { role: AiSearchConversationRole.ASSISTANT, content, resources },
109
+ {
110
+ role: AiSearchConversationRole.ASSISTANT,
111
+ content,
112
+ resources,
113
+ messageId: lastMessage.messageId,
114
+ },
98
115
  ];
99
116
  }
100
117
 
101
- return [...prev, { role: AiSearchConversationRole.ASSISTANT, content }];
118
+ return [...prev, { role: AiSearchConversationRole.ASSISTANT, content, resources }];
102
119
  });
103
120
  }, [response, conversation.length, error, resources, setConversation]);
104
121
 
@@ -110,7 +127,16 @@ export function SearchAiDialog({
110
127
 
111
128
  useEffect(() => {
112
129
  scrollToBottom();
113
- }, [conversation, isGeneratingResponse]);
130
+ }, [conversation, isGeneratingResponse, scrollToBottom]);
131
+
132
+ const handleFeedbackChange = useCallback(
133
+ (messageId: string, feedback: FeedbackType | undefined) => {
134
+ setConversation((prev) =>
135
+ prev.map((item) => (item.messageId === messageId ? { ...item, feedback } : item)),
136
+ );
137
+ },
138
+ [setConversation],
139
+ );
114
140
 
115
141
  return (
116
142
  <SearchAiDialogWrapper data-component-name="Search/SearchAiDialog" className={className}>
@@ -142,6 +168,9 @@ export function SearchAiDialog({
142
168
  index === conversation.length - 1
143
169
  }
144
170
  resources={item.resources}
171
+ messageId={item.messageId}
172
+ feedback={item.feedback}
173
+ onFeedbackChange={handleFeedbackChange}
145
174
  />
146
175
  ))}
147
176
 
@@ -1,8 +1,9 @@
1
- import React from 'react';
1
+ import React, { memo, useState } from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
4
  import type { JSX } from 'react';
5
5
 
6
+ import { FeedbackType, type SearchAiMessageResource } from '@redocly/theme/core/types';
6
7
  import { Link } from '@redocly/theme/components/Link/Link';
7
8
  import { Tag } from '@redocly/theme/components/Tag/Tag';
8
9
  import { AiSearchConversationRole } from '@redocly/theme/core/constants';
@@ -10,28 +11,72 @@ import { useThemeHooks } from '@redocly/theme/core/hooks';
10
11
  import { Markdown } from '@redocly/theme/components/Markdown/Markdown';
11
12
  import { DocumentIcon } from '@redocly/theme/icons/DocumentIcon/DocumentIcon';
12
13
  import { AiStarsIcon } from '@redocly/theme/icons/AiStarsIcon/AiStarsIcon';
14
+ import { CheckmarkOutlineIcon } from '@redocly/theme/icons/CheckmarkOutlineIcon/CheckmarkOutlineIcon';
15
+
16
+ import { SearchAiActionButtons } from './SearchAiActionButtons';
17
+ import { SearchAiNegativeFeedbackForm } from './SearchAiNegativeFeedbackForm';
13
18
 
14
19
  export type SearchAiMessageProps = {
15
20
  role: AiSearchConversationRole;
16
21
  content: string;
17
22
  isThinking?: boolean;
18
- resources?: {
19
- url: string;
20
- title: string;
21
- }[];
23
+ resources?: SearchAiMessageResource[];
22
24
  className?: string;
25
+ messageId?: string;
26
+ feedback?: FeedbackType;
27
+ onFeedbackChange: (messageId: string, feedback: FeedbackType | undefined) => void;
23
28
  };
24
29
 
25
- export function SearchAiMessage({
30
+ function SearchAiMessageComponent({
26
31
  role,
27
32
  content,
28
33
  isThinking,
29
34
  resources,
30
35
  className,
36
+ messageId,
37
+ feedback,
38
+ onFeedbackChange,
31
39
  }: SearchAiMessageProps): JSX.Element {
32
- const { useMarkdownText, useTranslate } = useThemeHooks();
40
+ const { useMarkdownText, useTranslate, useTelemetry } = useThemeHooks();
33
41
  const markDownContent = useMarkdownText(content || '');
34
42
  const { translate } = useTranslate();
43
+ const telemetry = useTelemetry();
44
+ const [feedbackSent, setFeedbackSent] = useState(false);
45
+
46
+ const hasResources = !isThinking && resources && resources.length > 0;
47
+ const resourcesCount = resources?.length ?? 0;
48
+
49
+ const showSuccessMessage = feedbackSent && feedback;
50
+
51
+ const sendFeedbackTelemetry = (feedbackValue: FeedbackType, dislikeReason?: string) => {
52
+ if (!messageId) return;
53
+
54
+ try {
55
+ telemetry.sendSearchAIFeedbackMessage({
56
+ feedback: feedbackValue,
57
+ messageId,
58
+ reason: dislikeReason,
59
+ });
60
+ } catch (error) {
61
+ console.error('Error sending feedback', error);
62
+ }
63
+ };
64
+
65
+ const handleFeedbackClick = (feedbackValue: FeedbackType, reason?: string) => {
66
+ if (!messageId) {
67
+ return;
68
+ }
69
+
70
+ if (!reason) {
71
+ onFeedbackChange(messageId, feedbackValue);
72
+ }
73
+
74
+ sendFeedbackTelemetry(feedbackValue, reason);
75
+
76
+ if (feedbackValue === FeedbackType.Like || reason) {
77
+ setFeedbackSent(true);
78
+ }
79
+ };
35
80
 
36
81
  return (
37
82
  <SearchAiMessageWrapper
@@ -49,46 +94,99 @@ export function SearchAiMessage({
49
94
  margin="0 var(--spacing-xs) 0 0"
50
95
  />
51
96
  )}
52
- <MessageWrapper role={role}>
53
- {role === AiSearchConversationRole.ASSISTANT ? (
54
- <>
55
- <ResponseText as="div" children={markDownContent} data-testid="response-text" />
56
- {!isThinking && resources && resources.length > 0 && (
57
- <ResourcesWrapper data-testid="resources-wrapper">
58
- <ResourcesTitle data-translation-key="search.ai.resourcesFound">
59
- {translate('search.ai.resourcesFound.basedOn', 'Based on')} {resources.length}{' '}
60
- {translate('search.ai.resourcesFound.resources', 'resources')}
61
- </ResourcesTitle>
62
- <ResourceTagsWrapper>
63
- {resources.map((resource, idx) => (
64
- <Link key={idx} to={resource.url} target="_blank">
65
- <ResourceTag
66
- borderless
67
- icon={<DocumentIcon color="--search-ai-resource-tag-icon-color" />}
68
- >
69
- {resource.title}
70
- </ResourceTag>
71
- </Link>
72
- ))}
73
- </ResourceTagsWrapper>
74
- </ResourcesWrapper>
75
- )}
76
- </>
77
- ) : (
78
- content
97
+ <MessageContentWrapper>
98
+ <MessageWrapper role={role}>
99
+ {role === AiSearchConversationRole.ASSISTANT ? (
100
+ <>
101
+ <ResponseText as="div" children={markDownContent} data-testid="response-text" />
102
+ {hasResources && (
103
+ <>
104
+ <ResourcesWrapper data-testid="resources-wrapper">
105
+ <ResourcesTitle data-translation-key="search.ai.resourcesFound">
106
+ {translate('search.ai.resourcesFound.basedOn', 'Based on')} {resourcesCount}{' '}
107
+ {translate('search.ai.resourcesFound.resources', 'resources')}
108
+ </ResourcesTitle>
109
+ <ResourceTagsWrapper>
110
+ {resources?.map((resource, idx) => (
111
+ <Link key={`${resource.url}-${idx}`} to={resource.url} target="_blank">
112
+ <ResourceTag
113
+ borderless
114
+ icon={<DocumentIcon color="--search-ai-resource-tag-icon-color" />}
115
+ >
116
+ {resource.title}
117
+ </ResourceTag>
118
+ </Link>
119
+ ))}
120
+ </ResourceTagsWrapper>
121
+ </ResourcesWrapper>
122
+ <FeedbackWrapper>
123
+ <SearchAiActionButtons
124
+ content={content}
125
+ feedback={feedback}
126
+ onFeedback={handleFeedbackClick}
127
+ disabled={feedbackSent}
128
+ />
129
+ </FeedbackWrapper>
130
+ </>
131
+ )}
132
+ </>
133
+ ) : (
134
+ content
135
+ )}
136
+ {isThinking && content.length === 0 && (
137
+ <ThinkingDotsWrapper data-testid="thinking-dots-wrapper">
138
+ <ThinkingDot />
139
+ <ThinkingDot />
140
+ <ThinkingDot />
141
+ </ThinkingDotsWrapper>
142
+ )}
143
+ </MessageWrapper>
144
+ {messageId && feedback === FeedbackType.Dislike && !showSuccessMessage && (
145
+ <SearchAiNegativeFeedbackForm
146
+ messageId={messageId}
147
+ onClose={onFeedbackChange}
148
+ onSubmit={(reason) => handleFeedbackClick(FeedbackType.Dislike, reason)}
149
+ />
79
150
  )}
80
- {isThinking && content.length === 0 && (
81
- <ThinkingDotsWrapper data-testid="thinking-dots-wrapper">
82
- <ThinkingDot />
83
- <ThinkingDot />
84
- <ThinkingDot />
85
- </ThinkingDotsWrapper>
151
+ {showSuccessMessage && (
152
+ <SuccessMessageWrapper data-component-name="Search/SearchAiMessage/Success">
153
+ <CheckmarkOutlineIcon size="20px" color="var(--color-success-base)" />
154
+ <SuccessMessageText>
155
+ {translate('search.ai.feedback.thanks', 'Thank you for your feedback!')}
156
+ </SuccessMessageText>
157
+ </SuccessMessageWrapper>
86
158
  )}
87
- </MessageWrapper>
159
+ </MessageContentWrapper>
88
160
  </SearchAiMessageWrapper>
89
161
  );
90
162
  }
91
163
 
164
+ function areResourcesEqual(
165
+ prev?: SearchAiMessageResource[],
166
+ next?: SearchAiMessageResource[],
167
+ ): boolean {
168
+ if (prev === next) return true;
169
+
170
+ if (!prev || !next || prev.length !== next.length) return false;
171
+
172
+ return prev.every((resource, index) => {
173
+ const nextResource = next[index];
174
+ return resource.url === nextResource.url && resource.title === nextResource.title;
175
+ });
176
+ }
177
+
178
+ export const SearchAiMessage = memo(SearchAiMessageComponent, (prevProps, nextProps) => {
179
+ return (
180
+ prevProps.role === nextProps.role &&
181
+ prevProps.content === nextProps.content &&
182
+ prevProps.isThinking === nextProps.isThinking &&
183
+ prevProps.messageId === nextProps.messageId &&
184
+ prevProps.feedback === nextProps.feedback &&
185
+ prevProps.onFeedbackChange === nextProps.onFeedbackChange &&
186
+ areResourcesEqual(prevProps.resources, nextProps.resources)
187
+ );
188
+ });
189
+
92
190
  const SearchAiMessageWrapper = styled.div<{ role: string }>`
93
191
  display: flex;
94
192
  flex-direction: row;
@@ -98,6 +196,14 @@ const SearchAiMessageWrapper = styled.div<{ role: string }>`
98
196
  role === AiSearchConversationRole.USER ? 'flex-end' : 'flex-start'};
99
197
  `;
100
198
 
199
+ const MessageContentWrapper = styled.div`
200
+ display: flex;
201
+ flex-direction: column;
202
+ gap: var(--spacing-sm);
203
+ max-width: 80%;
204
+ min-width: 0;
205
+ `;
206
+
101
207
  const ResponseText = styled(Markdown)`
102
208
  color: var(--search-ai-text-color);
103
209
  font-size: var(--search-ai-text-font-size);
@@ -120,7 +226,8 @@ const MessageWrapper = styled.div<{ role: string }>`
120
226
  role === AiSearchConversationRole.USER ? 'var(--spacing-sm)' : 'var(--spacing-xs)'}
121
227
  var(--spacing-sm);
122
228
  border-radius: var(--border-radius-lg);
123
- max-width: 80%;
229
+ width: fit-content;
230
+ max-width: 100%;
124
231
  word-wrap: break-word;
125
232
  white-space: pre-wrap;
126
233
  background-color: ${({ role }) =>
@@ -139,7 +246,13 @@ const ResourcesWrapper = styled.div`
139
246
  gap: var(--search-ai-resources-gap);
140
247
  display: flex;
141
248
  flex-direction: column;
142
- margin: var(--spacing-xs) 0;
249
+ margin: 0;
250
+ `;
251
+ const FeedbackWrapper = styled.div`
252
+ display: flex;
253
+ flex-direction: row;
254
+ gap: var(--search-ai-feedback-gap);
255
+ margin-top: var(--spacing-sm);
143
256
  `;
144
257
 
145
258
  const ResourcesTitle = styled.div`
@@ -210,3 +323,19 @@ const ThinkingDot = styled.div`
210
323
  }
211
324
  }
212
325
  `;
326
+
327
+ const SuccessMessageWrapper = styled.div`
328
+ max-width: fit-content;
329
+ display: flex;
330
+ align-items: center;
331
+ gap: var(--spacing-sm);
332
+ padding: var(--spacing-sm);
333
+ background: var(--color-success-bg);
334
+ border: 1px solid var(--color-success-border);
335
+ border-radius: var(--border-radius-lg);
336
+ `;
337
+
338
+ const SuccessMessageText = styled.div`
339
+ font-size: var(--font-size-base);
340
+ color: var(--color-success-darker);
341
+ `;