@redocly/theme 0.62.0-next.7 → 0.62.0-next.8

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.
@@ -131,12 +131,12 @@ exports.mobileMenu = (0, styled_components_1.css) `
131
131
  * @tokens Mobile Menu
132
132
  * */
133
133
  /* Fallback for older browsers. dvh accounts for dynamic UI elements like mobile address bars */
134
- --menu-mobile-height: calc(100vh - var(--navbar-height));
135
- --menu-mobile-height: calc(100dvh - var(--navbar-height));
134
+ --menu-mobile-height: calc(100vh - var(--navbar-height) - var(--banner-height));
135
+ --menu-mobile-height: calc(100dvh - var(--navbar-height) - var(--banner-height));
136
136
  --menu-mobile-width: 100%;
137
137
  --menu-mobile-z-index: var(--z-index-raised);
138
138
  --menu-mobile-left: 0;
139
- --menu-mobile-top: var(--navbar-height);
139
+ --menu-mobile-top: calc(var(--navbar-height) + var(--banner-height));
140
140
  --menu-mobile-transition: 0.5s;
141
141
  --menu-mobile-bg: var(--bg-color); // @presenter Color
142
142
  --menu-mobile-margin: var(--menu-mobile-items-margin-top) var(--menu-mobile-margin-horizontal) 0 var(--menu-mobile-margin-horizontal);
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import type { JSX } from 'react';
3
- import type { AiSearchConversationItem, SearchAiMessageResource } from '../../core/types';
3
+ import type { AiSearchConversationItem, SearchAiMessageResource, ToolCall, ContentSegment } from '../../core/types';
4
4
  import { AiSearchError } from '../../core/constants';
5
5
  export type SearchAiDialogProps = {
6
6
  response: string | undefined;
@@ -12,5 +12,7 @@ export type SearchAiDialogProps = {
12
12
  conversation: AiSearchConversationItem[];
13
13
  setConversation: React.Dispatch<React.SetStateAction<AiSearchConversationItem[]>>;
14
14
  onMessageSent: (message: string, history?: AiSearchConversationItem[], messageId?: string) => void;
15
+ toolCalls?: ToolCall[];
16
+ contentSegments?: ContentSegment[];
15
17
  };
16
- export declare function SearchAiDialog({ isGeneratingResponse, response, initialMessage, error, resources, onMessageSent, className, conversation, setConversation, }: SearchAiDialogProps): JSX.Element;
18
+ export declare function SearchAiDialog({ isGeneratingResponse, response, initialMessage, error, resources, onMessageSent, className, conversation, setConversation, toolCalls, contentSegments, }: SearchAiDialogProps): JSX.Element;
@@ -46,7 +46,7 @@ const constants_1 = require("../../core/constants");
46
46
  const SearchAiMessage_1 = require("../../components/Search/SearchAiMessage");
47
47
  const Admonition_1 = require("../../components/Admonition/Admonition");
48
48
  const AiStarsIcon_1 = require("../../icons/AiStarsIcon/AiStarsIcon");
49
- function SearchAiDialog({ isGeneratingResponse, response, initialMessage, error, resources, onMessageSent, className, conversation, setConversation, }) {
49
+ function SearchAiDialog({ isGeneratingResponse, response, initialMessage, error, resources, onMessageSent, className, conversation, setConversation, toolCalls = [], contentSegments, }) {
50
50
  const { useTranslate } = (0, hooks_1.useThemeHooks)();
51
51
  const { aiAssistant } = (0, hooks_1.useThemeConfig)();
52
52
  const { translate } = useTranslate();
@@ -99,12 +99,31 @@ function SearchAiDialog({ isGeneratingResponse, response, initialMessage, error,
99
99
  content,
100
100
  resources,
101
101
  messageId: lastMessage.messageId,
102
+ toolCalls,
103
+ contentSegments,
102
104
  },
103
105
  ];
104
106
  }
105
- return [...prev, { role: constants_1.AiSearchConversationRole.ASSISTANT, content, resources }];
107
+ return [
108
+ ...prev,
109
+ {
110
+ role: constants_1.AiSearchConversationRole.ASSISTANT,
111
+ content,
112
+ resources,
113
+ toolCalls,
114
+ contentSegments,
115
+ },
116
+ ];
106
117
  });
107
- }, [response, conversation.length, error, resources, setConversation]);
118
+ }, [
119
+ response,
120
+ conversation.length,
121
+ error,
122
+ resources,
123
+ toolCalls,
124
+ contentSegments,
125
+ setConversation,
126
+ ]);
108
127
  (0, react_1.useEffect)(() => {
109
128
  if (error) {
110
129
  setConversation((prev) => prev.slice(0, -1));
@@ -123,7 +142,7 @@ function SearchAiDialog({ isGeneratingResponse, response, initialMessage, error,
123
142
  react_1.default.createElement(ConversationWrapper, null,
124
143
  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 &&
125
144
  isGeneratingResponse &&
126
- index === conversation.length - 1, resources: item.resources, messageId: item.messageId, feedback: item.feedback, onFeedbackChange: handleFeedbackChange }))),
145
+ index === conversation.length - 1, resources: item.resources, messageId: item.messageId, feedback: item.feedback, onFeedbackChange: handleFeedbackChange, toolCalls: item.toolCalls || (index === conversation.length - 1 ? toolCalls : undefined), contentSegments: item.contentSegments }))),
127
146
  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))),
128
147
  !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))))),
129
148
  react_1.default.createElement("div", { ref: conversationEndRef })),
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import type { JSX } from 'react';
3
- import { FeedbackType, type SearchAiMessageResource } from '../../core/types';
3
+ import { FeedbackType, type SearchAiMessageResource, type ToolCall, type ContentSegment } from '../../core/types';
4
4
  import { AiSearchConversationRole } from '../../core/constants';
5
5
  export type SearchAiMessageProps = {
6
6
  role: AiSearchConversationRole;
@@ -11,7 +11,9 @@ export type SearchAiMessageProps = {
11
11
  messageId?: string;
12
12
  feedback?: FeedbackType;
13
13
  onFeedbackChange: (messageId: string, feedback: FeedbackType | undefined) => void;
14
+ toolCalls?: ToolCall[];
15
+ contentSegments?: ContentSegment[];
14
16
  };
15
- declare function SearchAiMessageComponent({ role, content, isThinking, resources, className, messageId, feedback, onFeedbackChange, }: SearchAiMessageProps): JSX.Element;
17
+ declare function SearchAiMessageComponent({ role, content, isThinking, resources, className, messageId, feedback, onFeedbackChange, toolCalls, contentSegments, }: SearchAiMessageProps): JSX.Element;
16
18
  export declare const SearchAiMessage: React.MemoExoticComponent<typeof SearchAiMessageComponent>;
17
19
  export {};
@@ -40,9 +40,10 @@ exports.SearchAiMessage = void 0;
40
40
  const react_1 = __importStar(require("react"));
41
41
  const styled_components_1 = __importDefault(require("styled-components"));
42
42
  const types_1 = require("../../core/types");
43
+ const constants_1 = require("../../core/constants");
43
44
  const Link_1 = require("../../components/Link/Link");
44
45
  const Tag_1 = require("../../components/Tag/Tag");
45
- const constants_1 = require("../../core/constants");
46
+ const constants_2 = require("../../core/constants");
46
47
  const hooks_1 = require("../../core/hooks");
47
48
  const Markdown_1 = require("../../components/Markdown/Markdown");
48
49
  const DocumentIcon_1 = require("../../icons/DocumentIcon/DocumentIcon");
@@ -50,16 +51,21 @@ const AiStarsIcon_1 = require("../../icons/AiStarsIcon/AiStarsIcon");
50
51
  const CheckmarkOutlineIcon_1 = require("../../icons/CheckmarkOutlineIcon/CheckmarkOutlineIcon");
51
52
  const SearchAiActionButtons_1 = require("../../components/Search/SearchAiActionButtons");
52
53
  const SearchAiNegativeFeedbackForm_1 = require("../../components/Search/SearchAiNegativeFeedbackForm");
53
- function SearchAiMessageComponent({ role, content, isThinking, resources, className, messageId, feedback, onFeedbackChange, }) {
54
+ function MarkdownSegment({ text }) {
55
+ const { useMarkdownText } = (0, hooks_1.useThemeHooks)();
56
+ const markdown = useMarkdownText(text);
57
+ return react_1.default.createElement(ResponseText, { as: "div", children: markdown, "data-testid": "response-text" });
58
+ }
59
+ function SearchAiMessageComponent({ role, content, isThinking, resources, className, messageId, feedback, onFeedbackChange, toolCalls = [], contentSegments = [{ type: 'text', text: content }], }) {
54
60
  var _a;
55
- const { useMarkdownText, useTranslate, useTelemetry } = (0, hooks_1.useThemeHooks)();
56
- const markDownContent = useMarkdownText(content || '');
61
+ const { useTranslate, useTelemetry } = (0, hooks_1.useThemeHooks)();
57
62
  const { translate } = useTranslate();
58
63
  const telemetry = useTelemetry();
59
64
  const [feedbackSent, setFeedbackSent] = (0, react_1.useState)(false);
60
65
  const hasResources = !isThinking && resources && resources.length > 0;
61
66
  const resourcesCount = (_a = resources === null || resources === void 0 ? void 0 : resources.length) !== null && _a !== void 0 ? _a : 0;
62
67
  const showSuccessMessage = feedbackSent && feedback;
68
+ const isLoading = isThinking && content.length === 0 && toolCalls.length === 0;
63
69
  const sendFeedbackTelemetry = (feedbackValue, dislikeReason) => {
64
70
  if (!messageId)
65
71
  return;
@@ -90,10 +96,25 @@ function SearchAiMessageComponent({ role, content, isThinking, resources, classN
90
96
  }
91
97
  };
92
98
  return (react_1.default.createElement(SearchAiMessageWrapper, { "data-component-name": "Search/SearchAiMessage", role: role, className: className, "data-testid": "search-ai-message" },
93
- 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" })),
94
- react_1.default.createElement(MessageContentWrapper, null, role === constants_1.AiSearchConversationRole.ASSISTANT ? (react_1.default.createElement(react_1.default.Fragment, null,
99
+ 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" })),
100
+ react_1.default.createElement(MessageContentWrapper, null, role === constants_2.AiSearchConversationRole.ASSISTANT ? (react_1.default.createElement(react_1.default.Fragment, null,
95
101
  react_1.default.createElement(MessageWrapper, { role: role },
96
- react_1.default.createElement(ResponseText, { as: "div", children: markDownContent, "data-testid": "response-text" }),
102
+ contentSegments.map((segment, index) => {
103
+ var _a, _b, _c, _d, _e, _f;
104
+ if (segment.type === 'tool') {
105
+ const toolCallCompleted = Boolean(segment.toolCall.result);
106
+ 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')}`;
107
+ 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...');
108
+ const toolCallDisplayText = toolCallCompleted
109
+ ? toolCallCompletedText
110
+ : toolCallInProgressText;
111
+ return (react_1.default.createElement(ToolCallsInfoWrapper, { key: `tool-${index}`, "data-testid": "tool-calls-info" },
112
+ react_1.default.createElement(ToolCallInfoItem, null,
113
+ react_1.default.createElement(DocumentIcon_1.DocumentIcon, { size: "14px", color: "--search-ai-text-color" }),
114
+ react_1.default.createElement(ToolCallText, { "$isSearching": !toolCallCompleted }, toolCallDisplayText))));
115
+ }
116
+ return react_1.default.createElement(MarkdownSegment, { key: `text-${index}`, text: segment.text });
117
+ }),
97
118
  hasResources && (react_1.default.createElement(react_1.default.Fragment, null,
98
119
  react_1.default.createElement(ResourcesWrapper, { "data-testid": "resources-wrapper" },
99
120
  react_1.default.createElement(ResourcesTitle, { "data-translation-key": "search.ai.resourcesFound" },
@@ -102,9 +123,9 @@ function SearchAiMessageComponent({ role, content, isThinking, resources, classN
102
123
  resourcesCount,
103
124
  ' ',
104
125
  translate('search.ai.resourcesFound.resources', 'resources')),
105
- 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" },
126
+ 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" },
106
127
  react_1.default.createElement(ResourceTag, { borderless: true, icon: react_1.default.createElement(DocumentIcon_1.DocumentIcon, { color: "--search-ai-resource-tag-icon-color" }) }, resource.title)))))))),
107
- isThinking && content.length === 0 && (react_1.default.createElement(ThinkingDotsWrapper, { "data-testid": "thinking-dots-wrapper" },
128
+ isLoading && (react_1.default.createElement(ThinkingDotsWrapper, { "data-testid": "thinking-dots-wrapper" },
108
129
  react_1.default.createElement(ThinkingDot, null),
109
130
  react_1.default.createElement(ThinkingDot, null),
110
131
  react_1.default.createElement(ThinkingDot, null))),
@@ -129,20 +150,23 @@ function areResourcesEqual(prev, next) {
129
150
  });
130
151
  }
131
152
  exports.SearchAiMessage = (0, react_1.memo)(SearchAiMessageComponent, (prevProps, nextProps) => {
153
+ var _a, _b;
132
154
  return (prevProps.role === nextProps.role &&
133
155
  prevProps.content === nextProps.content &&
134
156
  prevProps.isThinking === nextProps.isThinking &&
135
157
  prevProps.messageId === nextProps.messageId &&
136
158
  prevProps.feedback === nextProps.feedback &&
137
159
  prevProps.onFeedbackChange === nextProps.onFeedbackChange &&
138
- areResourcesEqual(prevProps.resources, nextProps.resources));
160
+ areResourcesEqual(prevProps.resources, nextProps.resources) &&
161
+ ((_a = prevProps.toolCalls) === null || _a === void 0 ? void 0 : _a.length) === ((_b = nextProps.toolCalls) === null || _b === void 0 ? void 0 : _b.length) &&
162
+ prevProps.contentSegments === nextProps.contentSegments);
139
163
  });
140
164
  const SearchAiMessageWrapper = styled_components_1.default.div `
141
165
  display: flex;
142
166
  flex-direction: row;
143
167
  align-items: flex-start;
144
168
  width: 100%;
145
- justify-content: ${({ role }) => role === constants_1.AiSearchConversationRole.USER ? 'flex-end' : 'flex-start'};
169
+ justify-content: ${({ role }) => role === constants_2.AiSearchConversationRole.USER ? 'flex-end' : 'flex-start'};
146
170
  `;
147
171
  const MessageContentWrapper = styled_components_1.default.div `
148
172
  display: flex;
@@ -168,18 +192,19 @@ const ResponseText = (0, styled_components_1.default)(Markdown_1.Markdown) `
168
192
  }
169
193
  `;
170
194
  const MessageWrapper = styled_components_1.default.div `
171
- padding: ${({ role }) => role === constants_1.AiSearchConversationRole.USER ? 'var(--spacing-sm)' : 'var(--spacing-xs)'}
172
- var(--spacing-sm);
195
+ padding: ${({ role }) => role === constants_2.AiSearchConversationRole.USER
196
+ ? 'var(--spacing-sm)'
197
+ : 'var(--spacing-sm) var(--spacing-sm) var(--spacing-xs) var(--spacing-sm)'};
173
198
  border-radius: var(--border-radius-lg);
174
199
  width: fit-content;
175
200
  max-width: 100%;
176
201
  word-wrap: break-word;
177
202
  white-space: pre-wrap;
178
- background-color: ${({ role }) => role === constants_1.AiSearchConversationRole.USER
203
+ background-color: ${({ role }) => role === constants_2.AiSearchConversationRole.USER
179
204
  ? 'var(--search-ai-user-bg-color)'
180
205
  : 'var(--search-ai-assistant-bg-color)'};
181
- border: ${({ role }) => role === constants_1.AiSearchConversationRole.USER ? 'none' : 'var(--search-ai-assistant-border)'};
182
- color: ${({ role }) => role === constants_1.AiSearchConversationRole.USER
206
+ border: ${({ role }) => role === constants_2.AiSearchConversationRole.USER ? 'none' : 'var(--search-ai-assistant-border)'};
207
+ color: ${({ role }) => role === constants_2.AiSearchConversationRole.USER
183
208
  ? 'var(--search-ai-user-text-color)'
184
209
  : 'var(--search-ai-assistant-text-color)'};
185
210
  `;
@@ -273,4 +298,35 @@ const SuccessMessageText = styled_components_1.default.div `
273
298
  font-size: var(--font-size-base);
274
299
  color: var(--color-success-darker);
275
300
  `;
301
+ const ToolCallsInfoWrapper = styled_components_1.default.div `
302
+ display: flex;
303
+ flex-direction: column;
304
+ gap: var(--spacing-xxs);
305
+ margin: 0 0 var(--spacing-sm) 0;
306
+ font-size: var(--font-size-xs);
307
+ color: var(--search-ai-text-color);
308
+ opacity: 0.6;
309
+ `;
310
+ const ToolCallInfoItem = styled_components_1.default.div `
311
+ display: flex;
312
+ align-items: center;
313
+ gap: var(--spacing-xxs);
314
+ `;
315
+ const ToolCallText = styled_components_1.default.span `
316
+ font-weight: var(--font-weight-regular);
317
+
318
+ ${({ $isSearching }) => $isSearching &&
319
+ `
320
+ animation: pulse 1.5s ease-in-out infinite;
321
+
322
+ @keyframes pulse {
323
+ 0%, 100% {
324
+ opacity: 1;
325
+ }
326
+ 50% {
327
+ opacity: 0.4;
328
+ }
329
+ }
330
+ `}
331
+ `;
276
332
  //# sourceMappingURL=SearchAiMessage.js.map
@@ -287,7 +287,7 @@ function SearchDialog({ onClose, className, initialMode = 'search', }) {
287
287
  setQuery(query);
288
288
  focusSearchInput();
289
289
  } }),
290
- react_1.default.createElement(SearchSuggestedPages_1.SearchSuggestedPages, null)))))) : (react_1.default.createElement(SearchAiDialog_1.SearchAiDialog, { initialMessage: query, response: aiSearch.response, isGeneratingResponse: aiSearch.isGeneratingResponse, error: aiSearch.error, resources: aiSearch.resources, conversation: aiSearch.conversation, setConversation: aiSearch.setConversation, onMessageSent: aiSearch.askQuestion }))),
290
+ react_1.default.createElement(SearchSuggestedPages_1.SearchSuggestedPages, null)))))) : (react_1.default.createElement(SearchAiDialog_1.SearchAiDialog, { initialMessage: query, response: aiSearch.response, isGeneratingResponse: aiSearch.isGeneratingResponse, error: aiSearch.error, resources: aiSearch.resources, conversation: aiSearch.conversation, setConversation: aiSearch.setConversation, onMessageSent: aiSearch.askQuestion, toolCalls: aiSearch.toolCalls, contentSegments: aiSearch.contentSegments }))),
291
291
  react_1.default.createElement(SearchDialogFooter, null, mode === 'ai-dialog' ? (react_1.default.createElement(AiDisclaimer, null, translate('search.ai.disclaimer', 'AI search might provide incomplete or incorrect results. Verify important information.'))) : (react_1.default.createElement(react_1.default.Fragment, null,
292
292
  react_1.default.createElement(SearchShortcuts, null,
293
293
  react_1.default.createElement(SearchShortcut_1.SearchShortcut, { "data-translation-key": "search.keys.navigate", combination: "Tab", text: translate('search.keys.navigate', 'to navigate') }),
@@ -12,7 +12,7 @@ function LoginButton({ href, className, variant = 'primary', size = 'medium', la
12
12
  const { translate } = useTranslate();
13
13
  const telemetry = useTelemetry();
14
14
  const buttonLabel = label || translate(labelTranslationKey, 'Login');
15
- return (react_1.default.createElement("div", { "data-component-name": componentName, className: className },
15
+ return (react_1.default.createElement("span", { "data-component-name": componentName, className: className },
16
16
  react_1.default.createElement(Button_1.Button, { "data-translation-key": label ? undefined : labelTranslationKey, to: href, onClick: () => telemetry.sendLoginButtonClickedMessage(), "data-testid": "login-btn", extraClass: className, variant: variant, size: size }, buttonLabel)));
17
17
  }
18
18
  //# sourceMappingURL=LoginButton.js.map
@@ -1,4 +1,4 @@
1
- import type { AiSearchErrorConfig } from '../types/search';
1
+ import { type AiSearchErrorConfig, ToolCallName } from '../types/search';
2
2
  export declare enum AiSearchError {
3
3
  Unauthorized = "ai_search_unauthorized",
4
4
  Forbidden = "ai_search_forbidden",
@@ -14,3 +14,7 @@ export type AiSearchConversationRole = (typeof AiSearchConversationRole)[keyof t
14
14
  export declare const AI_SEARCH_ERROR_CONFIG: Record<AiSearchError, AiSearchErrorConfig>;
15
15
  export declare const AI_SEARCH_MAX_MESSAGE_LENGTH = 45000;
16
16
  export declare const SEARCH_DEBOUNCE_TIME_MS = 300;
17
+ export declare const TOOL_CALL_DISPLAY_TEXT: Record<ToolCallName, {
18
+ inProgressText: string;
19
+ completedText: string;
20
+ }>;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SEARCH_DEBOUNCE_TIME_MS = exports.AI_SEARCH_MAX_MESSAGE_LENGTH = exports.AI_SEARCH_ERROR_CONFIG = exports.AiSearchConversationRole = exports.AiSearchError = void 0;
3
+ exports.TOOL_CALL_DISPLAY_TEXT = exports.SEARCH_DEBOUNCE_TIME_MS = exports.AI_SEARCH_MAX_MESSAGE_LENGTH = exports.AI_SEARCH_ERROR_CONFIG = exports.AiSearchConversationRole = exports.AiSearchError = void 0;
4
+ const search_1 = require("../types/search");
4
5
  var AiSearchError;
5
6
  (function (AiSearchError) {
6
7
  AiSearchError["Unauthorized"] = "ai_search_unauthorized";
@@ -38,4 +39,26 @@ exports.AI_SEARCH_ERROR_CONFIG = {
38
39
  };
39
40
  exports.AI_SEARCH_MAX_MESSAGE_LENGTH = 45000;
40
41
  exports.SEARCH_DEBOUNCE_TIME_MS = 300;
42
+ exports.TOOL_CALL_DISPLAY_TEXT = {
43
+ [search_1.ToolCallName.SearchDocumentation]: {
44
+ inProgressText: 'Searching documentation...',
45
+ completedText: 'Search completed',
46
+ },
47
+ [search_1.ToolCallName.ListApis]: {
48
+ inProgressText: 'Browsing APIs...',
49
+ completedText: 'Browsed APIs',
50
+ },
51
+ [search_1.ToolCallName.GetEndpoints]: {
52
+ inProgressText: 'Browsing endpoints for API...',
53
+ completedText: 'Browsed endpoints for API',
54
+ },
55
+ [search_1.ToolCallName.GetSecuritySchemes]: {
56
+ inProgressText: 'Browsing security schemes for API...',
57
+ completedText: 'Browsed security schemes for API',
58
+ },
59
+ [search_1.ToolCallName.GetFullApiDescription]: {
60
+ inProgressText: 'Reading API description...',
61
+ completedText: 'Read API description',
62
+ },
63
+ };
41
64
  //# sourceMappingURL=search.js.map
@@ -8,7 +8,7 @@ import type { CatalogConfig, ProductUiConfig } from '../../config';
8
8
  import type { UseCatalogResponse, FilteredCatalog, UseCatalogSortResponse, UseCatalogSearchResponse, UseCatalogProps, CatalogApiParams, CatalogApiResults } from './catalog';
9
9
  import type { UserMenuData } from './user-menu';
10
10
  import type { ItemState } from './sidebar';
11
- import type { SearchItemData, SearchFacet, SearchFilterItem, SearchFacetQuery, AiSearchConversationItem } from './search';
11
+ import type { SearchItemData, SearchFacet, SearchFilterItem, SearchFacetQuery, AiSearchConversationItem, ToolCallName, ContentSegment } from './search';
12
12
  import type { SubmitFeedbackParams } from './feedback';
13
13
  import type { TFunction } from './l10n';
14
14
  import type { BreadcrumbItem } from './breadcrumb';
@@ -96,6 +96,15 @@ export type ThemeHooks = {
96
96
  error: null | AiSearchError;
97
97
  conversation: AiSearchConversationItem[];
98
98
  setConversation: React.Dispatch<React.SetStateAction<AiSearchConversationItem[]>>;
99
+ toolCalls: Array<{
100
+ name: ToolCallName;
101
+ args: unknown;
102
+ position: number;
103
+ result?: {
104
+ documentCount: number;
105
+ };
106
+ }>;
107
+ contentSegments: ContentSegment[];
99
108
  };
100
109
  useMarkdownText: (text: string) => React.ReactNode;
101
110
  useFacetQuery: (field: string) => {
@@ -1,5 +1,5 @@
1
1
  import type { TOptions } from 'i18next';
2
- export type TranslationKey = 'dev.newApp' | 'dev.newApp.text' | 'dev.sidebar.header' | 'dev.sidebar.footer.text' | 'dev.create.app.dialog.appName.placeholder' | 'dev.create.app.dialog.appName.error' | 'dev.create.app.dialog.selectAPIs' | 'dev.create.app.dialog.description' | 'dev.create.app.dialog.description.placeholder' | 'dev.create.app.dialog.create' | 'dev.create.app.dialog.cancel' | 'dev.main.tab.appKeys' | 'dev.main.tab.logs' | 'dev.app.description.title' | 'dev.edit.description.dialog.title' | 'dev.edit.description.dialog.save' | 'dev.edit.description.dialog.cancel' | 'dev.edit.apis.dialog.selectedAPIs' | 'dev.app.key.create' | 'dev.create.key.dialog.title' | 'dev.create.key.dialog.create' | 'dev.create.key.dialog.cancel' | 'dev.app.edit' | 'dev.app.delete' | 'dev.edit.app.dialog.title' | 'dev.edit.app.dialog.save' | 'dev.edit.app.dialog.cancel' | 'dev.delete.app.dialog.title' | 'dev.delete.app.dialog.confirmation' | 'dev.delete.app.dialog.delete' | 'dev.delete.app.dialog.cancel' | 'dev.app.key.roll' | 'dev.roll.key.dialog.title' | 'dev.roll.key.dialog.apiKey' | 'dev.roll.key.dialog.expires' | 'dev.roll.key.dialog.confirmation' | 'dev.roll.key.dialog.cancel' | 'dev.roll.key.dialog.roll' | 'dev.update.key.dialog.title' | 'dev.update.key.dialog.update' | 'dev.update.key.dialog.cancel' | 'dev.app.key.api.name' | 'dev.app.key.api.status' | 'dev.app.key.api.edit' | 'dev.edit.apis.dialog.title' | 'dev.edit.apis.dialog.apiKey' | 'dev.edit.apis.dialog.save' | 'dev.edit.apis.dialog.cancel' | 'dev.select.placeholder' | 'dev.app.overview.status.pending' | 'dev.app.overview.status.approved' | 'dev.app.overview.status.revoked' | 'dev.app.overview.status' | 'dev.app.overview.non-production' | 'dev.app.overview.production' | 'dev.app.overview.clientId' | 'dev.app.overview.apiKey' | 'dev.app.key.revoke' | 'dev.revoke.key.dialog.title' | 'dev.revoke.key.dialog.apiKey' | 'dev.revoke.key.dialog.expires' | 'dev.revoke.key.dialog.confirmation' | 'dev.revoke.key.dialog.revoke' | 'dev.revoke.key.dialog.cancel' | 'dev.app.overview.expires' | 'dev.app.overview.created' | 'dev.app.overview.visibilityToggle.hide' | 'dev.app.overview.visibilityToggle.show' | 'search.loading' | 'search.noResults.title' | 'search.keys.navigate' | 'search.keys.select' | 'search.keys.exit' | 'search.label' | 'search.cancel' | 'search.recent' | 'search.navbar.label' | 'search.suggested' | 'search.showMore' | 'search.filter.title' | 'search.filter.reset' | 'search.filter.field.reset' | 'search.ai.welcomeText' | 'search.ai.newConversation' | 'search.ai.backToSearch' | 'search.ai.back' | 'search.ai.assistant' | 'search.ai.placeholder' | 'search.ai.generatingResponse' | 'search.ai.followUpQuestion' | 'search.ai.suggestionsTitle' | 'search.ai.thinkingText' | 'search.ai.resourcesFound' | 'search.ai.resourcesFound.basedOn' | 'search.ai.resourcesFound.resources' | 'search.ai.feedback.title' | 'search.ai.feedback.detailsPlaceholder' | 'search.ai.feedback.thanks' | 'search.ai.button' | 'search.ai.label' | 'search.ai.disclaimer' | 'search.ai.error.description' | 'search.ai.error.description.forbidden' | 'search.ai.error.description.unauthorized' | 'search.ai.error.header' | 'search.ai.error.header.forbidden' | 'search.ai.error.header.unauthorized' | 'search.ai.feedback.more' | 'search.searchItem.deprecated' | 'search.groups.all' | 'search.filter.field.footer' | 'aiAssistant.trigger' | 'toc.header' | 'footer.copyrightText' | 'page.homeButton' | 'page.forbidden.title' | 'page.forbidden.description' | 'page.notFound.title' | 'page.notFound.description' | 'page.lastUpdated.timeago' | 'page.lastUpdated.on' | 'catalog.filters.placeholder' | 'catalog.filters.title' | 'catalog.filters.add' | 'catalog.filters.clearAll' | 'catalog.filters.select.addFilter' | 'catalog.filters.select.all' | 'catalog.filters.done' | 'catalog.catalogs.all.title' | 'catalog.catalogs.all.description' | 'catalog.catalogs.all.switcherLabel' | 'catalog.catalogs.service.title' | 'catalog.catalogs.service.description' | 'catalog.catalogs.service.switcherLabel' | 'catalog.catalogs.user.title' | 'catalog.catalogs.user.description' | 'catalog.catalogs.user.switcherLabel' | 'catalog.catalogs.team.title' | 'catalog.catalogs.team.description' | 'catalog.catalogs.team.switcherLabel' | 'catalog.catalogs.domain.title' | 'catalog.catalogs.domain.description' | 'catalog.catalogs.domain.switcherLabel' | 'catalog.catalogs.apiDescription.title' | 'catalog.catalogs.apiDescription.description' | 'catalog.catalogs.apiDescription.switcherLabel' | 'catalog.catalogs.dataSchema.title' | 'catalog.catalogs.dataSchema.description' | 'catalog.catalogs.dataSchema.switcherLabel' | 'catalog.catalogs.apiOperation.title' | 'catalog.catalogs.apiOperation.description' | 'catalog.catalogs.apiOperation.switcherLabel' | 'catalog.entity.metadata.title' | 'catalog.entity.schema.title' | 'catalog.entity.properties.apiDescription.title' | 'catalog.backToAllLabel' | 'catalog.tags.more' | 'catalog.tags.label' | 'catalog.sort' | 'catalog.catalogs.label' | 'catalog.owners.label' | 'catalog.repositories.label' | 'catalog.email.label' | 'catalog.format.label' | 'catalog.entityType.label' | 'catalog.domains.label' | 'catalog.contact.label' | 'catalog.methodAndPath.label' | 'catalog.links.label' | 'catalog.metadata.domains' | 'catalog.metadata.owners' | 'catalog.history.button.label' | 'catalog.history.sidebar.title' | 'catalog.history.sidebar.close' | 'catalog.history.version.label' | 'catalog.history.version.notSpecified' | 'catalog.history.version.default' | 'catalog.history.revisions.limitMessage' | 'catalog.history.revision.current' | 'catalog.history.revisions.showLess' | 'catalog.history.revisions.showMore' | 'sidebar.menu.backLabel' | 'sidebar.menu.backToLabel' | 'sidebar.actions.show' | 'sidebar.actions.hide' | 'sidebar.actions.changeToSingleColumn' | 'sidebar.actions.changeToTwoColumns' | 'sidebar.actions.singleColumn' | 'sidebar.actions.twoColumns' | 'versionPicker.label' | 'versionPicker.unversioned' | 'codeSnippet.copy.buttonText' | 'codeSnippet.copy.tooltipText' | 'codeSnippet.copy.toasterText' | 'markdown.editPage.text' | 'feedback.settings.comment.submitText' | 'feedback.settings.comment.label' | 'feedback.settings.comment.send' | 'feedback.settings.comment.cancel' | 'feedback.settings.comment.satisfiedLabel' | 'feedback.settings.comment.neutralLabel' | 'feedback.settings.comment.dissatisfiedLabel' | 'feedback.settings.submitText' | 'feedback.settings.label' | 'feedback.settings.reasons.label' | 'feedback.submit' | 'feedback.cancel' | 'feedback.settings.comment.likeLabel' | 'feedback.settings.comment.dislikeLabel' | 'feedback.sentiment.thumbUp' | 'feedback.sentiment.thumbDown' | 'feedback.settings.leftScaleLabel' | 'feedback.settings.rightScaleLabel' | 'feedback.settings.optionalEmail.placeholder' | 'feedback.settings.optionalEmail.label' | 'codeSnippet.report.buttonText' | 'codeSnippet.report.tooltipText' | 'codeSnippet.report.label' | 'codeSnippet.expand.tooltipText' | 'codeSnippet.collapse.tooltipText' | 'userMenu.login' | 'userMenu.logout' | 'userMenu.devOnboardingLabel' | 'mobileMenu.mainMenu' | 'mobileMenu.previous' | 'mobileMenu.products' | 'mobileMenu.version' | 'navbar.products' | 'page.nextButton' | 'page.previousButton' | 'page.actions.copyButtonText' | 'page.actions.copyTitle' | 'page.actions.copyDescription' | 'page.actions.viewAsMdTitle' | 'page.actions.viewAsMdButtonText' | 'page.actions.viewAsMdDescription' | 'page.actions.chatGptTitle' | 'page.actions.chatGptButtonText' | 'page.actions.chatGptDescription' | 'page.actions.claudeTitle' | 'page.actions.claudeButtonText' | 'page.actions.claudeDescription' | 'page.actions.cursorMcpButtonText' | 'page.actions.cursorMcpTitle' | 'page.actions.cursorMcpDescription' | 'page.actions.connectMcp' | 'page.actions.connectMcp.cursor' | 'page.actions.connectMcp.cursorDescription' | 'page.actions.connectMcp.vscode' | 'page.actions.connectMcp.vscodeDescription' | 'page.actions.connectMcp.copyConfig' | 'page.actions.connectMcp.copyConfigDescription' | 'openapi.download.description.title' | 'openapi.info.title' | 'openapi.info.contact.url' | 'openapi.info.contact.name' | 'openapi.info.license' | 'openapi.info.termsOfService' | 'openapi.info.metadata.title' | 'openapi.key' | 'openapi.value' | 'openapi.enum' | 'openapi.items' | 'openapi.default' | 'openapi.variable' | 'openapi.variables' | 'openapi.actions.show' | 'openapi.actions.hide' | 'openapi.actions.more' | 'openapi.languages.title' | 'openapi.servers.title' | 'openapi.operations' | 'openapi.webhooks' | 'openapi.description' | 'openapi.badges.deprecated' | 'openapi.badges.required' | 'openapi.badges.webhook' | 'openapi.request' | 'openapi.path' | 'openapi.query' | 'openapi.cookie' | 'openapi.header' | 'openapi.body' | 'openapi.responses' | 'openapi.response' | 'openapi.callbacks' | 'openapi.callbackRequest' | 'openapi.callbackResponse' | 'openapi.payload' | 'openapi.discriminator' | 'openapi.contentType' | 'openapi.tryIt' | 'openapi.loading' | 'openapi.example' | 'openapi.examples' | 'openapi.additionalProperties' | 'openapi.patternProperties' | 'openapi.required' | 'openapi.recursive' | 'openapi.complex' | 'openapi.hideExample' | 'openapi.showExample' | 'openapi.expandAll' | 'openapi.collapseAll' | 'openapi.viewSecurityDetails' | 'openapi.noResponseExample' | 'openapi.discriminator.searchPlaceholder' | 'openapi.discriminator.searchNoResults' | 'openapi.discriminator.defaultMapping' | 'openapi.discriminator.defaultMappingTooltip' | 'openapi.noResponseContent' | 'openapi.noRequestPayload' | 'openapi.hidePattern' | 'openapi.showPattern' | 'openapi.authorizationUrl' | 'openapi.tokenUrl' | 'openapi.refreshUrl' | 'openapi.showOptionalScopes' | 'openapi.hideOptionalScopes' | 'openapi.security' | 'openapi.httpAuthorizationScheme' | 'openapi.bearerFormat' | 'openapi.parameterName' | 'openapi.flowType' | 'openapi.connectUrl' | 'openapi.requiredScopes' | 'openapi.unsupportedLanguage' | 'openapi.failedToGenerateCodeSample' | 'openapi.schemaCatalogLink.title' | 'openapi.schemaCatalogLink.copyButtonTooltip' | 'openapi.schemaCatalogLink.copiedTooltip' | 'openapi.mcp.title' | 'openapi.mcp.endpoint' | 'openapi.mcp.tools' | 'openapi.mcp.protocolVersion' | 'openapi.mcp.capabilities' | 'openapi.mcp.experimentalCapabilities' | 'openapi.mcp.inputSchema' | 'openapi.mcp.inputExample' | 'openapi.mcp.outputSchema' | 'openapi.mcp.outputExample' | 'asyncapi.download.description.title' | 'asyncapi.info.title' | 'graphql.download.description.title' | 'graphql.info.title' | 'graphql.info.contact.url' | 'graphql.info.contact.name' | 'graphql.info.license' | 'graphql.info.termsOfService' | 'graphql.overview' | 'graphql.metadata' | 'graphql.key' | 'graphql.value' | 'graphql.queries' | 'graphql.mutations' | 'graphql.subscriptions' | 'graphql.directives' | 'graphql.objects' | 'graphql.interfaces' | 'graphql.unions' | 'graphql.enums' | 'graphql.inputs' | 'graphql.scalars' | 'graphql.arguments.label' | 'graphql.arguments.show' | 'graphql.arguments.hide' | 'graphql.arguments.here' | 'graphql.returnTypes.label' | 'graphql.returnTypes.show' | 'graphql.returnTypes.hide' | 'graphql.possibleTypes' | 'graphql.defaultValue' | 'graphql.deprecationReason' | 'graphql.requiredScopes' | 'graphql.viewSecurityDetails' | 'graphql.objectScopes' | 'graphql.fieldScopes' | 'graphql.implementedInterfaces' | 'graphql.nonNull' | 'graphql.required' | 'graphql.deprecated' | 'graphql.variables' | 'graphql.querySample' | 'graphql.mutationSample' | 'graphql.subscriptionSample' | 'graphql.responseSample' | 'graphql.locations' | 'graphql.sample' | 'graphql.referenced' | 'graphql.content.fragment' | 'codeWalkthrough.download' | 'codeWalkthrough.preview' | 'time.justNow' | 'time.past.second' | 'time.past.seconds' | 'time.past.minute' | 'time.past.minutes' | 'time.past.hour' | 'time.past.hours' | 'time.past.day' | 'time.past.days' | 'time.past.week' | 'time.past.weeks' | 'time.past.month' | 'time.past.months' | 'time.past.year' | 'time.past.years' | 'page.internalServerError.title' | 'page.internalServerError.description' | 'page.skipToContent.label' | 'select.noResults' | 'loaders.loading' | 'filter.dateRange.from' | 'filter.dateRange.to' | 'mermaid.openFullscreen' | 'mermaid.zoomIn' | 'mermaid.zoomOut' | 'mermaid.reset' | 'mermaid.close' | 'mermaid.viewer';
2
+ export type TranslationKey = 'dev.newApp' | 'dev.newApp.text' | 'dev.sidebar.header' | 'dev.sidebar.footer.text' | 'dev.create.app.dialog.appName.placeholder' | 'dev.create.app.dialog.appName.error' | 'dev.create.app.dialog.selectAPIs' | 'dev.create.app.dialog.description' | 'dev.create.app.dialog.description.placeholder' | 'dev.create.app.dialog.create' | 'dev.create.app.dialog.cancel' | 'dev.main.tab.appKeys' | 'dev.main.tab.logs' | 'dev.app.description.title' | 'dev.edit.description.dialog.title' | 'dev.edit.description.dialog.save' | 'dev.edit.description.dialog.cancel' | 'dev.edit.apis.dialog.selectedAPIs' | 'dev.app.key.create' | 'dev.create.key.dialog.title' | 'dev.create.key.dialog.create' | 'dev.create.key.dialog.cancel' | 'dev.app.edit' | 'dev.app.delete' | 'dev.edit.app.dialog.title' | 'dev.edit.app.dialog.save' | 'dev.edit.app.dialog.cancel' | 'dev.delete.app.dialog.title' | 'dev.delete.app.dialog.confirmation' | 'dev.delete.app.dialog.delete' | 'dev.delete.app.dialog.cancel' | 'dev.app.key.roll' | 'dev.roll.key.dialog.title' | 'dev.roll.key.dialog.apiKey' | 'dev.roll.key.dialog.expires' | 'dev.roll.key.dialog.confirmation' | 'dev.roll.key.dialog.cancel' | 'dev.roll.key.dialog.roll' | 'dev.update.key.dialog.title' | 'dev.update.key.dialog.update' | 'dev.update.key.dialog.cancel' | 'dev.app.key.api.name' | 'dev.app.key.api.status' | 'dev.app.key.api.edit' | 'dev.edit.apis.dialog.title' | 'dev.edit.apis.dialog.apiKey' | 'dev.edit.apis.dialog.save' | 'dev.edit.apis.dialog.cancel' | 'dev.select.placeholder' | 'dev.app.overview.status.pending' | 'dev.app.overview.status.approved' | 'dev.app.overview.status.revoked' | 'dev.app.overview.status' | 'dev.app.overview.non-production' | 'dev.app.overview.production' | 'dev.app.overview.clientId' | 'dev.app.overview.apiKey' | 'dev.app.key.revoke' | 'dev.revoke.key.dialog.title' | 'dev.revoke.key.dialog.apiKey' | 'dev.revoke.key.dialog.expires' | 'dev.revoke.key.dialog.confirmation' | 'dev.revoke.key.dialog.revoke' | 'dev.revoke.key.dialog.cancel' | 'dev.app.overview.expires' | 'dev.app.overview.created' | 'dev.app.overview.visibilityToggle.hide' | 'dev.app.overview.visibilityToggle.show' | 'search.loading' | 'search.noResults.title' | 'search.keys.navigate' | 'search.keys.select' | 'search.keys.exit' | 'search.label' | 'search.cancel' | 'search.recent' | 'search.navbar.label' | 'search.suggested' | 'search.showMore' | 'search.filter.title' | 'search.filter.reset' | 'search.filter.field.reset' | 'search.ai.welcomeText' | 'search.ai.newConversation' | 'search.ai.backToSearch' | 'search.ai.back' | 'search.ai.assistant' | 'search.ai.placeholder' | 'search.ai.generatingResponse' | 'search.ai.followUpQuestion' | 'search.ai.suggestionsTitle' | 'search.ai.thinkingText' | 'search.ai.resourcesFound' | 'search.ai.resourcesFound.basedOn' | 'search.ai.resourcesFound.resources' | 'search.ai.feedback.title' | 'search.ai.feedback.detailsPlaceholder' | 'search.ai.feedback.thanks' | 'search.ai.toolResult.found' | 'search.ai.toolResult.found.documents' | 'search.ai.toolCall.searching' | 'search.ai.button' | 'search.ai.label' | 'search.ai.disclaimer' | 'search.ai.error.description' | 'search.ai.error.description.forbidden' | 'search.ai.error.description.unauthorized' | 'search.ai.error.header' | 'search.ai.error.header.forbidden' | 'search.ai.error.header.unauthorized' | 'search.ai.feedback.more' | 'search.searchItem.deprecated' | 'search.groups.all' | 'search.filter.field.footer' | 'aiAssistant.trigger' | 'toc.header' | 'footer.copyrightText' | 'page.homeButton' | 'page.forbidden.title' | 'page.forbidden.description' | 'page.notFound.title' | 'page.notFound.description' | 'page.lastUpdated.timeago' | 'page.lastUpdated.on' | 'catalog.filters.placeholder' | 'catalog.filters.title' | 'catalog.filters.add' | 'catalog.filters.clearAll' | 'catalog.filters.select.addFilter' | 'catalog.filters.select.all' | 'catalog.filters.done' | 'catalog.catalogs.all.title' | 'catalog.catalogs.all.description' | 'catalog.catalogs.all.switcherLabel' | 'catalog.catalogs.service.title' | 'catalog.catalogs.service.description' | 'catalog.catalogs.service.switcherLabel' | 'catalog.catalogs.user.title' | 'catalog.catalogs.user.description' | 'catalog.catalogs.user.switcherLabel' | 'catalog.catalogs.team.title' | 'catalog.catalogs.team.description' | 'catalog.catalogs.team.switcherLabel' | 'catalog.catalogs.domain.title' | 'catalog.catalogs.domain.description' | 'catalog.catalogs.domain.switcherLabel' | 'catalog.catalogs.apiDescription.title' | 'catalog.catalogs.apiDescription.description' | 'catalog.catalogs.apiDescription.switcherLabel' | 'catalog.catalogs.dataSchema.title' | 'catalog.catalogs.dataSchema.description' | 'catalog.catalogs.dataSchema.switcherLabel' | 'catalog.catalogs.apiOperation.title' | 'catalog.catalogs.apiOperation.description' | 'catalog.catalogs.apiOperation.switcherLabel' | 'catalog.entity.metadata.title' | 'catalog.entity.schema.title' | 'catalog.entity.properties.apiDescription.title' | 'catalog.backToAllLabel' | 'catalog.tags.more' | 'catalog.tags.label' | 'catalog.sort' | 'catalog.catalogs.label' | 'catalog.owners.label' | 'catalog.repositories.label' | 'catalog.email.label' | 'catalog.format.label' | 'catalog.entityType.label' | 'catalog.domains.label' | 'catalog.contact.label' | 'catalog.methodAndPath.label' | 'catalog.links.label' | 'catalog.metadata.domains' | 'catalog.metadata.owners' | 'catalog.history.button.label' | 'catalog.history.sidebar.title' | 'catalog.history.sidebar.close' | 'catalog.history.version.label' | 'catalog.history.version.notSpecified' | 'catalog.history.version.default' | 'catalog.history.revisions.limitMessage' | 'catalog.history.revision.current' | 'catalog.history.revisions.showLess' | 'catalog.history.revisions.showMore' | 'sidebar.menu.backLabel' | 'sidebar.menu.backToLabel' | 'sidebar.actions.show' | 'sidebar.actions.hide' | 'sidebar.actions.changeToSingleColumn' | 'sidebar.actions.changeToTwoColumns' | 'sidebar.actions.singleColumn' | 'sidebar.actions.twoColumns' | 'versionPicker.label' | 'versionPicker.unversioned' | 'codeSnippet.copy.buttonText' | 'codeSnippet.copy.tooltipText' | 'codeSnippet.copy.toasterText' | 'markdown.editPage.text' | 'feedback.settings.comment.submitText' | 'feedback.settings.comment.label' | 'feedback.settings.comment.send' | 'feedback.settings.comment.cancel' | 'feedback.settings.comment.satisfiedLabel' | 'feedback.settings.comment.neutralLabel' | 'feedback.settings.comment.dissatisfiedLabel' | 'feedback.settings.submitText' | 'feedback.settings.label' | 'feedback.settings.reasons.label' | 'feedback.submit' | 'feedback.cancel' | 'feedback.settings.comment.likeLabel' | 'feedback.settings.comment.dislikeLabel' | 'feedback.sentiment.thumbUp' | 'feedback.sentiment.thumbDown' | 'feedback.settings.leftScaleLabel' | 'feedback.settings.rightScaleLabel' | 'feedback.settings.optionalEmail.placeholder' | 'feedback.settings.optionalEmail.label' | 'codeSnippet.report.buttonText' | 'codeSnippet.report.tooltipText' | 'codeSnippet.report.label' | 'codeSnippet.expand.tooltipText' | 'codeSnippet.collapse.tooltipText' | 'userMenu.login' | 'userMenu.logout' | 'userMenu.devOnboardingLabel' | 'mobileMenu.mainMenu' | 'mobileMenu.previous' | 'mobileMenu.products' | 'mobileMenu.version' | 'navbar.products' | 'page.nextButton' | 'page.previousButton' | 'page.actions.copyButtonText' | 'page.actions.copyTitle' | 'page.actions.copyDescription' | 'page.actions.viewAsMdTitle' | 'page.actions.viewAsMdButtonText' | 'page.actions.viewAsMdDescription' | 'page.actions.chatGptTitle' | 'page.actions.chatGptButtonText' | 'page.actions.chatGptDescription' | 'page.actions.claudeTitle' | 'page.actions.claudeButtonText' | 'page.actions.claudeDescription' | 'page.actions.cursorMcpButtonText' | 'page.actions.cursorMcpTitle' | 'page.actions.cursorMcpDescription' | 'page.actions.connectMcp' | 'page.actions.connectMcp.cursor' | 'page.actions.connectMcp.cursorDescription' | 'page.actions.connectMcp.vscode' | 'page.actions.connectMcp.vscodeDescription' | 'page.actions.connectMcp.copyConfig' | 'page.actions.connectMcp.copyConfigDescription' | 'openapi.download.description.title' | 'openapi.info.title' | 'openapi.info.contact.url' | 'openapi.info.contact.name' | 'openapi.info.license' | 'openapi.info.termsOfService' | 'openapi.info.metadata.title' | 'openapi.key' | 'openapi.value' | 'openapi.enum' | 'openapi.items' | 'openapi.default' | 'openapi.variable' | 'openapi.variables' | 'openapi.actions.show' | 'openapi.actions.hide' | 'openapi.actions.more' | 'openapi.languages.title' | 'openapi.servers.title' | 'openapi.operations' | 'openapi.webhooks' | 'openapi.description' | 'openapi.badges.deprecated' | 'openapi.badges.required' | 'openapi.badges.webhook' | 'openapi.request' | 'openapi.path' | 'openapi.query' | 'openapi.cookie' | 'openapi.header' | 'openapi.body' | 'openapi.responses' | 'openapi.response' | 'openapi.callbacks' | 'openapi.callbackRequest' | 'openapi.callbackResponse' | 'openapi.payload' | 'openapi.discriminator' | 'openapi.contentType' | 'openapi.tryIt' | 'openapi.loading' | 'openapi.example' | 'openapi.examples' | 'openapi.additionalProperties' | 'openapi.patternProperties' | 'openapi.required' | 'openapi.recursive' | 'openapi.complex' | 'openapi.hideExample' | 'openapi.showExample' | 'openapi.expandAll' | 'openapi.collapseAll' | 'openapi.viewSecurityDetails' | 'openapi.noResponseExample' | 'openapi.discriminator.searchPlaceholder' | 'openapi.discriminator.searchNoResults' | 'openapi.discriminator.defaultMapping' | 'openapi.discriminator.defaultMappingTooltip' | 'openapi.noResponseContent' | 'openapi.noRequestPayload' | 'openapi.hidePattern' | 'openapi.showPattern' | 'openapi.authorizationUrl' | 'openapi.tokenUrl' | 'openapi.refreshUrl' | 'openapi.showOptionalScopes' | 'openapi.hideOptionalScopes' | 'openapi.security' | 'openapi.httpAuthorizationScheme' | 'openapi.bearerFormat' | 'openapi.parameterName' | 'openapi.flowType' | 'openapi.connectUrl' | 'openapi.requiredScopes' | 'openapi.unsupportedLanguage' | 'openapi.failedToGenerateCodeSample' | 'openapi.schemaCatalogLink.title' | 'openapi.schemaCatalogLink.copyButtonTooltip' | 'openapi.schemaCatalogLink.copiedTooltip' | 'openapi.mcp.title' | 'openapi.mcp.endpoint' | 'openapi.mcp.tools' | 'openapi.mcp.protocolVersion' | 'openapi.mcp.capabilities' | 'openapi.mcp.experimentalCapabilities' | 'openapi.mcp.inputSchema' | 'openapi.mcp.inputExample' | 'openapi.mcp.outputSchema' | 'openapi.mcp.outputExample' | 'asyncapi.download.description.title' | 'asyncapi.info.title' | 'graphql.download.description.title' | 'graphql.info.title' | 'graphql.info.contact.url' | 'graphql.info.contact.name' | 'graphql.info.license' | 'graphql.info.termsOfService' | 'graphql.overview' | 'graphql.metadata' | 'graphql.key' | 'graphql.value' | 'graphql.queries' | 'graphql.mutations' | 'graphql.subscriptions' | 'graphql.directives' | 'graphql.objects' | 'graphql.interfaces' | 'graphql.unions' | 'graphql.enums' | 'graphql.inputs' | 'graphql.scalars' | 'graphql.arguments.label' | 'graphql.arguments.show' | 'graphql.arguments.hide' | 'graphql.arguments.here' | 'graphql.returnTypes.label' | 'graphql.returnTypes.show' | 'graphql.returnTypes.hide' | 'graphql.possibleTypes' | 'graphql.defaultValue' | 'graphql.deprecationReason' | 'graphql.requiredScopes' | 'graphql.viewSecurityDetails' | 'graphql.objectScopes' | 'graphql.fieldScopes' | 'graphql.implementedInterfaces' | 'graphql.nonNull' | 'graphql.required' | 'graphql.deprecated' | 'graphql.variables' | 'graphql.querySample' | 'graphql.mutationSample' | 'graphql.subscriptionSample' | 'graphql.responseSample' | 'graphql.locations' | 'graphql.sample' | 'graphql.referenced' | 'graphql.content.fragment' | 'codeWalkthrough.download' | 'codeWalkthrough.preview' | 'time.justNow' | 'time.past.second' | 'time.past.seconds' | 'time.past.minute' | 'time.past.minutes' | 'time.past.hour' | 'time.past.hours' | 'time.past.day' | 'time.past.days' | 'time.past.week' | 'time.past.weeks' | 'time.past.month' | 'time.past.months' | 'time.past.year' | 'time.past.years' | 'page.internalServerError.title' | 'page.internalServerError.description' | 'page.skipToContent.label' | 'select.noResults' | 'loaders.loading' | 'filter.dateRange.from' | 'filter.dateRange.to' | 'mermaid.openFullscreen' | 'mermaid.zoomIn' | 'mermaid.zoomOut' | 'mermaid.reset' | 'mermaid.close' | 'mermaid.viewer';
3
3
  export type Locale = {
4
4
  code: string;
5
5
  name: string;
@@ -91,10 +91,34 @@ export type SearchAiMessageResource = {
91
91
  url: string;
92
92
  title: string;
93
93
  };
94
+ export type ToolCall = {
95
+ name: ToolCallName;
96
+ args: unknown;
97
+ position: number;
98
+ result?: {
99
+ documentCount: number;
100
+ };
101
+ };
102
+ export type ContentSegment = {
103
+ type: 'text';
104
+ text: string;
105
+ } | {
106
+ type: 'tool';
107
+ toolCall: ToolCall;
108
+ };
94
109
  export type AiSearchConversationItem = {
95
110
  role: AiSearchConversationRole;
96
111
  content: string;
97
112
  resources?: SearchAiMessageResource[];
98
113
  messageId?: string;
99
114
  feedback?: FeedbackType;
115
+ toolCalls?: ToolCall[];
116
+ contentSegments?: ContentSegment[];
100
117
  };
118
+ export declare enum ToolCallName {
119
+ SearchDocumentation = "search_documentation",
120
+ ListApis = "list-apis",
121
+ GetEndpoints = "get-endpoints",
122
+ GetSecuritySchemes = "get-security-schemes",
123
+ GetFullApiDescription = "get-full-api-description"
124
+ }
@@ -1,9 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FeedbackType = void 0;
3
+ exports.ToolCallName = exports.FeedbackType = void 0;
4
4
  var FeedbackType;
5
5
  (function (FeedbackType) {
6
6
  FeedbackType["Like"] = "like";
7
7
  FeedbackType["Dislike"] = "dislike";
8
8
  })(FeedbackType || (exports.FeedbackType = FeedbackType = {}));
9
+ var ToolCallName;
10
+ (function (ToolCallName) {
11
+ ToolCallName["SearchDocumentation"] = "search_documentation";
12
+ ToolCallName["ListApis"] = "list-apis";
13
+ ToolCallName["GetEndpoints"] = "get-endpoints";
14
+ ToolCallName["GetSecuritySchemes"] = "get-security-schemes";
15
+ ToolCallName["GetFullApiDescription"] = "get-full-api-description";
16
+ })(ToolCallName || (exports.ToolCallName = ToolCallName = {}));
9
17
  //# sourceMappingURL=search.js.map
@@ -0,0 +1,2 @@
1
+ import type { ToolCall, ContentSegment } from '../types/search';
2
+ export declare function splitContentByToolCalls(content: string | undefined, toolCalls: ToolCall[] | undefined): ContentSegment[];
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.splitContentByToolCalls = splitContentByToolCalls;
4
+ function splitContentByToolCalls(content, toolCalls) {
5
+ if (!toolCalls || toolCalls.length === 0) {
6
+ return [{ type: 'text', text: content || '' }];
7
+ }
8
+ const segments = [];
9
+ let lastPos = 0;
10
+ for (const toolCall of toolCalls) {
11
+ if (content && toolCall.position > lastPos) {
12
+ segments.push({ type: 'text', text: content.substring(lastPos, toolCall.position) });
13
+ }
14
+ segments.push({ type: 'tool', toolCall });
15
+ lastPos = toolCall.position;
16
+ }
17
+ if (content && lastPos < content.length) {
18
+ segments.push({ type: 'text', text: content.substring(lastPos) });
19
+ }
20
+ return segments;
21
+ }
22
+ //# sourceMappingURL=content-segments.js.map
@@ -43,3 +43,4 @@ export * from './get-operation-color';
43
43
  export * from './frontmatter-translate';
44
44
  export * from './transform-revisions-to-version-history';
45
45
  export * from './build-revision-url';
46
+ export * from './content-segments';
@@ -59,4 +59,5 @@ __exportStar(require("./get-operation-color"), exports);
59
59
  __exportStar(require("./frontmatter-translate"), exports);
60
60
  __exportStar(require("./transform-revisions-to-version-history"), exports);
61
61
  __exportStar(require("./build-revision-url"), exports);
62
+ __exportStar(require("./content-segments"), exports);
62
63
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.62.0-next.7",
3
+ "version": "0.62.0-next.8",
4
4
  "description": "Shared UI components lib",
5
5
  "keywords": [
6
6
  "theme",
@@ -29,8 +29,8 @@
29
29
  "@markdoc/markdoc": "0.5.2",
30
30
  "lodash.debounce": "^4.0.8",
31
31
  "lodash.throttle": "^4.1.1",
32
- "react": "19.2.4",
33
- "react-dom": "19.2.4",
32
+ "react": "^19.2.4",
33
+ "react-dom": "^19.2.4",
34
34
  "react-router-dom": "^6.30.3",
35
35
  "styled-components": "^4.1.1 || ^5.3.11 || ^6.0.0"
36
36
  },
@@ -81,7 +81,7 @@
81
81
  "openapi-sampler": "1.6.2",
82
82
  "react-calendar": "5.1.0",
83
83
  "react-date-picker": "11.0.0",
84
- "@redocly/config": "0.41.4"
84
+ "@redocly/config": "0.42.0"
85
85
  },
86
86
  "scripts": {
87
87
  "watch": "tsc -p tsconfig.build.json && (concurrently \"tsc -w -p tsconfig.build.json\" \"tsc-alias -w -p tsconfig.build.json\")",
@@ -130,12 +130,12 @@ export const mobileMenu = css`
130
130
  * @tokens Mobile Menu
131
131
  * */
132
132
  /* Fallback for older browsers. dvh accounts for dynamic UI elements like mobile address bars */
133
- --menu-mobile-height: calc(100vh - var(--navbar-height));
134
- --menu-mobile-height: calc(100dvh - var(--navbar-height));
133
+ --menu-mobile-height: calc(100vh - var(--navbar-height) - var(--banner-height));
134
+ --menu-mobile-height: calc(100dvh - var(--navbar-height) - var(--banner-height));
135
135
  --menu-mobile-width: 100%;
136
136
  --menu-mobile-z-index: var(--z-index-raised);
137
137
  --menu-mobile-left: 0;
138
- --menu-mobile-top: var(--navbar-height);
138
+ --menu-mobile-top: calc(var(--navbar-height) + var(--banner-height));
139
139
  --menu-mobile-transition: 0.5s;
140
140
  --menu-mobile-bg: var(--bg-color); // @presenter Color
141
141
  --menu-mobile-margin: var(--menu-mobile-items-margin-top) var(--menu-mobile-margin-horizontal) 0 var(--menu-mobile-margin-horizontal);
@@ -6,6 +6,8 @@ import type {
6
6
  AiSearchConversationItem,
7
7
  SearchAiMessageResource,
8
8
  FeedbackType,
9
+ ToolCall,
10
+ ContentSegment,
9
11
  } from '@redocly/theme/core/types';
10
12
 
11
13
  import { useThemeConfig, useThemeHooks } from '@redocly/theme/core/hooks';
@@ -34,6 +36,8 @@ export type SearchAiDialogProps = {
34
36
  history?: AiSearchConversationItem[],
35
37
  messageId?: string,
36
38
  ) => void;
39
+ toolCalls?: ToolCall[];
40
+ contentSegments?: ContentSegment[];
37
41
  };
38
42
 
39
43
  export function SearchAiDialog({
@@ -46,6 +50,8 @@ export function SearchAiDialog({
46
50
  className,
47
51
  conversation,
48
52
  setConversation,
53
+ toolCalls = [],
54
+ contentSegments,
49
55
  }: SearchAiDialogProps): JSX.Element {
50
56
  const { useTranslate } = useThemeHooks();
51
57
  const { aiAssistant } = useThemeConfig();
@@ -111,13 +117,32 @@ export function SearchAiDialog({
111
117
  content,
112
118
  resources,
113
119
  messageId: lastMessage.messageId,
120
+ toolCalls,
121
+ contentSegments,
114
122
  },
115
123
  ];
116
124
  }
117
125
 
118
- return [...prev, { role: AiSearchConversationRole.ASSISTANT, content, resources }];
126
+ return [
127
+ ...prev,
128
+ {
129
+ role: AiSearchConversationRole.ASSISTANT,
130
+ content,
131
+ resources,
132
+ toolCalls,
133
+ contentSegments,
134
+ },
135
+ ];
119
136
  });
120
- }, [response, conversation.length, error, resources, setConversation]);
137
+ }, [
138
+ response,
139
+ conversation.length,
140
+ error,
141
+ resources,
142
+ toolCalls,
143
+ contentSegments,
144
+ setConversation,
145
+ ]);
121
146
 
122
147
  useEffect(() => {
123
148
  if (error) {
@@ -171,6 +196,10 @@ export function SearchAiDialog({
171
196
  messageId={item.messageId}
172
197
  feedback={item.feedback}
173
198
  onFeedbackChange={handleFeedbackChange}
199
+ toolCalls={
200
+ item.toolCalls || (index === conversation.length - 1 ? toolCalls : undefined)
201
+ }
202
+ contentSegments={item.contentSegments}
174
203
  />
175
204
  ))}
176
205
 
@@ -3,7 +3,13 @@ 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
+ import {
7
+ FeedbackType,
8
+ type SearchAiMessageResource,
9
+ type ToolCall,
10
+ type ContentSegment,
11
+ } from '@redocly/theme/core/types';
12
+ import { TOOL_CALL_DISPLAY_TEXT } from '@redocly/theme/core/constants';
7
13
  import { Link } from '@redocly/theme/components/Link/Link';
8
14
  import { Tag } from '@redocly/theme/components/Tag/Tag';
9
15
  import { AiSearchConversationRole } from '@redocly/theme/core/constants';
@@ -15,6 +21,12 @@ import { CheckmarkOutlineIcon } from '@redocly/theme/icons/CheckmarkOutlineIcon/
15
21
  import { SearchAiActionButtons } from '@redocly/theme/components/Search/SearchAiActionButtons';
16
22
  import { SearchAiNegativeFeedbackForm } from '@redocly/theme/components/Search/SearchAiNegativeFeedbackForm';
17
23
 
24
+ function MarkdownSegment({ text }: { text: string }): JSX.Element {
25
+ const { useMarkdownText } = useThemeHooks();
26
+ const markdown = useMarkdownText(text);
27
+ return <ResponseText as="div" children={markdown} data-testid="response-text" />;
28
+ }
29
+
18
30
  export type SearchAiMessageProps = {
19
31
  role: AiSearchConversationRole;
20
32
  content: string;
@@ -24,6 +36,8 @@ export type SearchAiMessageProps = {
24
36
  messageId?: string;
25
37
  feedback?: FeedbackType;
26
38
  onFeedbackChange: (messageId: string, feedback: FeedbackType | undefined) => void;
39
+ toolCalls?: ToolCall[];
40
+ contentSegments?: ContentSegment[];
27
41
  };
28
42
 
29
43
  function SearchAiMessageComponent({
@@ -35,9 +49,10 @@ function SearchAiMessageComponent({
35
49
  messageId,
36
50
  feedback,
37
51
  onFeedbackChange,
52
+ toolCalls = [],
53
+ contentSegments = [{ type: 'text' as const, text: content }],
38
54
  }: SearchAiMessageProps): JSX.Element {
39
- const { useMarkdownText, useTranslate, useTelemetry } = useThemeHooks();
40
- const markDownContent = useMarkdownText(content || '');
55
+ const { useTranslate, useTelemetry } = useThemeHooks();
41
56
  const { translate } = useTranslate();
42
57
  const telemetry = useTelemetry();
43
58
  const [feedbackSent, setFeedbackSent] = useState(false);
@@ -47,6 +62,8 @@ function SearchAiMessageComponent({
47
62
 
48
63
  const showSuccessMessage = feedbackSent && feedback;
49
64
 
65
+ const isLoading = isThinking && content.length === 0 && toolCalls.length === 0;
66
+
50
67
  const sendFeedbackTelemetry = (feedbackValue: FeedbackType, dislikeReason?: string) => {
51
68
  if (!messageId) return;
52
69
 
@@ -100,7 +117,36 @@ function SearchAiMessageComponent({
100
117
  {role === AiSearchConversationRole.ASSISTANT ? (
101
118
  <>
102
119
  <MessageWrapper role={role}>
103
- <ResponseText as="div" children={markDownContent} data-testid="response-text" />
120
+ {contentSegments.map((segment, index) => {
121
+ if (segment.type === 'tool') {
122
+ const toolCallCompleted = Boolean(segment.toolCall.result);
123
+
124
+ const toolCallCompletedText =
125
+ TOOL_CALL_DISPLAY_TEXT[segment.toolCall.name]?.completedText ??
126
+ `${translate('search.ai.toolResult.found', 'Found')} ${segment.toolCall.result?.documentCount ?? 0} ${translate('search.ai.toolResult.found.documents', 'documents')}`;
127
+
128
+ const toolCallInProgressText =
129
+ TOOL_CALL_DISPLAY_TEXT[segment.toolCall.name]?.inProgressText ??
130
+ translate('search.ai.toolCall.searching', 'Searching...');
131
+
132
+ const toolCallDisplayText = toolCallCompleted
133
+ ? toolCallCompletedText
134
+ : toolCallInProgressText;
135
+
136
+ return (
137
+ <ToolCallsInfoWrapper key={`tool-${index}`} data-testid="tool-calls-info">
138
+ <ToolCallInfoItem>
139
+ <DocumentIcon size="14px" color="--search-ai-text-color" />
140
+ <ToolCallText $isSearching={!toolCallCompleted}>
141
+ {toolCallDisplayText}
142
+ </ToolCallText>
143
+ </ToolCallInfoItem>
144
+ </ToolCallsInfoWrapper>
145
+ );
146
+ }
147
+
148
+ return <MarkdownSegment key={`text-${index}`} text={segment.text} />;
149
+ })}
104
150
  {hasResources && (
105
151
  <>
106
152
  <ResourcesWrapper data-testid="resources-wrapper">
@@ -109,8 +155,8 @@ function SearchAiMessageComponent({
109
155
  {translate('search.ai.resourcesFound.resources', 'resources')}
110
156
  </ResourcesTitle>
111
157
  <ResourceTagsWrapper>
112
- {resources?.map((resource, idx) => (
113
- <Link key={`${resource.url}-${idx}`} to={resource.url} target="_blank">
158
+ {resources?.map((resource, index) => (
159
+ <Link key={`${resource.url}-${index}`} to={resource.url} target="_blank">
114
160
  <ResourceTag
115
161
  borderless
116
162
  icon={<DocumentIcon color="--search-ai-resource-tag-icon-color" />}
@@ -123,7 +169,7 @@ function SearchAiMessageComponent({
123
169
  </ResourcesWrapper>
124
170
  </>
125
171
  )}
126
- {isThinking && content.length === 0 && (
172
+ {isLoading && (
127
173
  <ThinkingDotsWrapper data-testid="thinking-dots-wrapper">
128
174
  <ThinkingDot />
129
175
  <ThinkingDot />
@@ -190,7 +236,9 @@ export const SearchAiMessage = memo(SearchAiMessageComponent, (prevProps, nextPr
190
236
  prevProps.messageId === nextProps.messageId &&
191
237
  prevProps.feedback === nextProps.feedback &&
192
238
  prevProps.onFeedbackChange === nextProps.onFeedbackChange &&
193
- areResourcesEqual(prevProps.resources, nextProps.resources)
239
+ areResourcesEqual(prevProps.resources, nextProps.resources) &&
240
+ prevProps.toolCalls?.length === nextProps.toolCalls?.length &&
241
+ prevProps.contentSegments === nextProps.contentSegments
194
242
  );
195
243
  });
196
244
 
@@ -230,8 +278,9 @@ const ResponseText = styled(Markdown)`
230
278
 
231
279
  const MessageWrapper = styled.div<{ role: string }>`
232
280
  padding: ${({ role }) =>
233
- role === AiSearchConversationRole.USER ? 'var(--spacing-sm)' : 'var(--spacing-xs)'}
234
- var(--spacing-sm);
281
+ role === AiSearchConversationRole.USER
282
+ ? 'var(--spacing-sm)'
283
+ : 'var(--spacing-sm) var(--spacing-sm) var(--spacing-xs) var(--spacing-sm)'};
235
284
  border-radius: var(--border-radius-lg);
236
285
  width: fit-content;
237
286
  max-width: 100%;
@@ -346,3 +395,38 @@ const SuccessMessageText = styled.div`
346
395
  font-size: var(--font-size-base);
347
396
  color: var(--color-success-darker);
348
397
  `;
398
+
399
+ const ToolCallsInfoWrapper = styled.div`
400
+ display: flex;
401
+ flex-direction: column;
402
+ gap: var(--spacing-xxs);
403
+ margin: 0 0 var(--spacing-sm) 0;
404
+ font-size: var(--font-size-xs);
405
+ color: var(--search-ai-text-color);
406
+ opacity: 0.6;
407
+ `;
408
+
409
+ const ToolCallInfoItem = styled.div`
410
+ display: flex;
411
+ align-items: center;
412
+ gap: var(--spacing-xxs);
413
+ `;
414
+
415
+ const ToolCallText = styled.span<{ $isSearching?: boolean }>`
416
+ font-weight: var(--font-weight-regular);
417
+
418
+ ${({ $isSearching }) =>
419
+ $isSearching &&
420
+ `
421
+ animation: pulse 1.5s ease-in-out infinite;
422
+
423
+ @keyframes pulse {
424
+ 0%, 100% {
425
+ opacity: 1;
426
+ }
427
+ 50% {
428
+ opacity: 0.4;
429
+ }
430
+ }
431
+ `}
432
+ `;
@@ -469,6 +469,8 @@ export function SearchDialog({
469
469
  conversation={aiSearch.conversation}
470
470
  setConversation={aiSearch.setConversation}
471
471
  onMessageSent={aiSearch.askQuestion}
472
+ toolCalls={aiSearch.toolCalls}
473
+ contentSegments={aiSearch.contentSegments}
472
474
  />
473
475
  )}
474
476
  </SearchDialogBody>
@@ -31,7 +31,7 @@ export function LoginButton({
31
31
  const buttonLabel = label || translate(labelTranslationKey, 'Login');
32
32
 
33
33
  return (
34
- <div data-component-name={componentName} className={className}>
34
+ <span data-component-name={componentName} className={className}>
35
35
  <Button
36
36
  data-translation-key={label ? undefined : labelTranslationKey}
37
37
  to={href}
@@ -43,6 +43,6 @@ export function LoginButton({
43
43
  >
44
44
  {buttonLabel}
45
45
  </Button>
46
- </div>
46
+ </span>
47
47
  );
48
48
  }
@@ -1,4 +1,4 @@
1
- import type { AiSearchErrorConfig } from '../types/search';
1
+ import { type AiSearchErrorConfig, ToolCallName } from '../types/search';
2
2
 
3
3
  export enum AiSearchError {
4
4
  Unauthorized = 'ai_search_unauthorized',
@@ -45,3 +45,29 @@ export const AI_SEARCH_ERROR_CONFIG: Record<AiSearchError, AiSearchErrorConfig>
45
45
  export const AI_SEARCH_MAX_MESSAGE_LENGTH = 45000;
46
46
 
47
47
  export const SEARCH_DEBOUNCE_TIME_MS = 300;
48
+
49
+ export const TOOL_CALL_DISPLAY_TEXT: Record<
50
+ ToolCallName,
51
+ { inProgressText: string; completedText: string }
52
+ > = {
53
+ [ToolCallName.SearchDocumentation]: {
54
+ inProgressText: 'Searching documentation...',
55
+ completedText: 'Search completed',
56
+ },
57
+ [ToolCallName.ListApis]: {
58
+ inProgressText: 'Browsing APIs...',
59
+ completedText: 'Browsed APIs',
60
+ },
61
+ [ToolCallName.GetEndpoints]: {
62
+ inProgressText: 'Browsing endpoints for API...',
63
+ completedText: 'Browsed endpoints for API',
64
+ },
65
+ [ToolCallName.GetSecuritySchemes]: {
66
+ inProgressText: 'Browsing security schemes for API...',
67
+ completedText: 'Browsed security schemes for API',
68
+ },
69
+ [ToolCallName.GetFullApiDescription]: {
70
+ inProgressText: 'Reading API description...',
71
+ completedText: 'Read API description',
72
+ },
73
+ };
@@ -55,7 +55,15 @@ export const useThemeHooks = vi.fn(() => ({
55
55
  })),
56
56
  useAiSearch: vi.fn(() => ({
57
57
  askQuestion: vi.fn(),
58
- references: [],
58
+ isGeneratingResponse: false,
59
+ question: '',
60
+ response: undefined,
61
+ resources: [],
62
+ clearConversation: vi.fn(),
63
+ error: null,
64
+ conversation: [],
65
+ setConversation: vi.fn(),
66
+ toolCalls: [],
59
67
  })),
60
68
  useFacetQuery: vi.fn(() => ({
61
69
  searchFacet: null,
@@ -28,6 +28,8 @@ import type {
28
28
  SearchFilterItem,
29
29
  SearchFacetQuery,
30
30
  AiSearchConversationItem,
31
+ ToolCallName,
32
+ ContentSegment,
31
33
  } from './search';
32
34
  import type { SubmitFeedbackParams } from './feedback';
33
35
  import type { TFunction } from './l10n';
@@ -127,6 +129,13 @@ export type ThemeHooks = {
127
129
  error: null | AiSearchError;
128
130
  conversation: AiSearchConversationItem[];
129
131
  setConversation: React.Dispatch<React.SetStateAction<AiSearchConversationItem[]>>;
132
+ toolCalls: Array<{
133
+ name: ToolCallName;
134
+ args: unknown;
135
+ position: number;
136
+ result?: { documentCount: number };
137
+ }>;
138
+ contentSegments: ContentSegment[];
130
139
  };
131
140
  useMarkdownText: (text: string) => React.ReactNode;
132
141
  useFacetQuery: (field: string) => {
@@ -99,6 +99,9 @@ export type TranslationKey =
99
99
  | 'search.ai.feedback.title'
100
100
  | 'search.ai.feedback.detailsPlaceholder'
101
101
  | 'search.ai.feedback.thanks'
102
+ | 'search.ai.toolResult.found'
103
+ | 'search.ai.toolResult.found.documents'
104
+ | 'search.ai.toolCall.searching'
102
105
  | 'search.ai.button'
103
106
  | 'search.ai.label'
104
107
  | 'search.ai.disclaimer'
@@ -104,10 +104,29 @@ export type SearchAiMessageResource = {
104
104
  title: string;
105
105
  };
106
106
 
107
+ export type ToolCall = {
108
+ name: ToolCallName;
109
+ args: unknown;
110
+ position: number;
111
+ result?: { documentCount: number };
112
+ };
113
+
114
+ export type ContentSegment = { type: 'text'; text: string } | { type: 'tool'; toolCall: ToolCall };
115
+
107
116
  export type AiSearchConversationItem = {
108
117
  role: AiSearchConversationRole;
109
118
  content: string;
110
119
  resources?: SearchAiMessageResource[];
111
120
  messageId?: string;
112
121
  feedback?: FeedbackType;
122
+ toolCalls?: ToolCall[];
123
+ contentSegments?: ContentSegment[];
113
124
  };
125
+
126
+ export enum ToolCallName {
127
+ SearchDocumentation = 'search_documentation',
128
+ ListApis = 'list-apis',
129
+ GetEndpoints = 'get-endpoints',
130
+ GetSecuritySchemes = 'get-security-schemes',
131
+ GetFullApiDescription = 'get-full-api-description',
132
+ }
@@ -0,0 +1,27 @@
1
+ import type { ToolCall, ContentSegment } from '../types/search';
2
+
3
+ export function splitContentByToolCalls(
4
+ content: string | undefined,
5
+ toolCalls: ToolCall[] | undefined,
6
+ ): ContentSegment[] {
7
+ if (!toolCalls || toolCalls.length === 0) {
8
+ return [{ type: 'text', text: content || '' }];
9
+ }
10
+
11
+ const segments: ContentSegment[] = [];
12
+ let lastPos = 0;
13
+
14
+ for (const toolCall of toolCalls) {
15
+ if (content && toolCall.position > lastPos) {
16
+ segments.push({ type: 'text', text: content.substring(lastPos, toolCall.position) });
17
+ }
18
+ segments.push({ type: 'tool', toolCall });
19
+ lastPos = toolCall.position;
20
+ }
21
+
22
+ if (content && lastPos < content.length) {
23
+ segments.push({ type: 'text', text: content.substring(lastPos) });
24
+ }
25
+
26
+ return segments;
27
+ }
@@ -43,3 +43,4 @@ export * from './get-operation-color';
43
43
  export * from './frontmatter-translate';
44
44
  export * from './transform-revisions-to-version-history';
45
45
  export * from './build-revision-url';
46
+ export * from './content-segments';