@redocly/theme 0.70.0-next.5 → 0.70.0-next.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/Buttons/ConnectMCPButton.js +8 -13
- package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.js +1 -1
- package/lib/components/Catalog/CatalogFilter/CatalogFilterDateRange.js +2 -1
- package/lib/components/Dropdown/Dropdown.d.ts +4 -2
- package/lib/components/Dropdown/Dropdown.js +28 -8
- package/lib/components/Filter/FilterDateRange.js +36 -2
- package/lib/components/Search/SearchAiActivity.d.ts +10 -0
- package/lib/components/Search/SearchAiActivity.js +278 -0
- package/lib/components/Search/SearchAiDialog.js +5 -1
- package/lib/components/Search/SearchAiMessage.d.ts +2 -1
- package/lib/components/Search/SearchAiMessage.js +20 -108
- package/lib/components/Search/SearchItem.js +1 -8
- package/lib/components/Search/variables.js +15 -2
- package/lib/components/Select/SelectInput.js +2 -8
- package/lib/components/SplitButton/SplitButton.js +37 -3
- package/lib/core/constants/mcp.d.ts +2 -2
- package/lib/core/constants/mcp.js +3 -8
- package/lib/core/constants/search.js +5 -1
- package/lib/core/hooks/index.d.ts +1 -0
- package/lib/core/hooks/index.js +1 -0
- package/lib/core/hooks/menu/use-collapse.js +1 -1
- package/lib/core/hooks/search/use-search-ai-activity.d.ts +11 -0
- package/lib/core/hooks/search/use-search-ai-activity.js +15 -0
- package/lib/core/hooks/use-connect-mcp-button.js +2 -1
- package/lib/core/hooks/use-full-height.d.ts +1 -1
- package/lib/core/hooks/use-full-height.js +8 -8
- package/lib/core/styles/global.d.ts +2 -0
- package/lib/core/styles/global.js +80 -76
- package/lib/core/styles/index.d.ts +1 -0
- package/lib/core/styles/index.js +3 -1
- package/lib/core/styles/search-ai-appear.d.ts +1 -0
- package/lib/core/styles/search-ai-appear.js +18 -0
- package/lib/core/types/dropdown.d.ts +1 -0
- package/lib/core/types/dropdown.js +3 -0
- package/lib/core/types/index.d.ts +1 -0
- package/lib/core/types/l10n.d.ts +1 -1
- package/lib/core/types/mcp.d.ts +2 -1
- package/lib/core/types/search.d.ts +23 -0
- package/lib/core/utils/assign-ref.d.ts +2 -0
- package/lib/core/utils/assign-ref.js +12 -0
- package/lib/core/utils/content-segments.d.ts +12 -1
- package/lib/core/utils/content-segments.js +90 -5
- package/lib/core/utils/dropdown.d.ts +2 -0
- package/lib/core/utils/dropdown.js +12 -0
- package/lib/core/utils/index.d.ts +2 -0
- package/lib/core/utils/index.js +2 -0
- package/lib/core/utils/mcp.d.ts +2 -1
- package/lib/core/utils/mcp.js +5 -0
- package/lib/core/utils/search-ai-activity.d.ts +4 -0
- package/lib/core/utils/search-ai-activity.js +83 -0
- package/lib/markdoc/tags/connect-mcp.js +15 -0
- package/package.json +2 -2
- package/src/components/Buttons/ConnectMCPButton.tsx +8 -14
- package/src/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.tsx +3 -1
- package/src/components/Catalog/CatalogFilter/CatalogFilterDateRange.tsx +3 -2
- package/src/components/Dropdown/Dropdown.tsx +28 -6
- package/src/components/Filter/FilterDateRange.tsx +3 -2
- package/src/components/Search/SearchAiActivity.tsx +318 -0
- package/src/components/Search/SearchAiDialog.tsx +4 -3
- package/src/components/Search/SearchAiMessage.tsx +27 -135
- package/src/components/Search/SearchItem.tsx +2 -9
- package/src/components/Search/variables.ts +15 -2
- package/src/components/Select/SelectInput.tsx +2 -8
- package/src/components/SplitButton/SplitButton.tsx +8 -2
- package/src/core/constants/mcp.ts +3 -9
- package/src/core/constants/search.ts +5 -1
- package/src/core/hooks/__mocks__/index.ts +1 -0
- package/src/core/hooks/index.ts +1 -0
- package/src/core/hooks/menu/use-collapse.ts +1 -1
- package/src/core/hooks/search/use-search-ai-activity.ts +31 -0
- package/src/core/hooks/use-connect-mcp-button.ts +7 -2
- package/src/core/hooks/use-full-height.ts +8 -8
- package/src/core/styles/global.ts +80 -75
- package/src/core/styles/index.ts +2 -1
- package/src/core/styles/search-ai-appear.ts +16 -0
- package/src/core/types/dropdown.ts +1 -0
- package/src/core/types/index.ts +1 -0
- package/src/core/types/l10n.ts +1 -1
- package/src/core/types/mcp.ts +3 -1
- package/src/core/types/search.ts +21 -0
- package/src/core/utils/assign-ref.ts +9 -0
- package/src/core/utils/content-segments.ts +98 -6
- package/src/core/utils/dropdown.ts +14 -0
- package/src/core/utils/index.ts +2 -0
- package/src/core/utils/mcp.ts +7 -1
- package/src/core/utils/search-ai-activity.ts +110 -0
- package/src/markdoc/tags/connect-mcp.ts +16 -1
|
@@ -119,6 +119,10 @@ export function SearchAiDialog({
|
|
|
119
119
|
|
|
120
120
|
// oxlint-disable-next-line react-you-might-not-need-an-effect/no-pass-data-to-parent -- merges the streamed assistant response into the lifted conversation state as chunks arrive
|
|
121
121
|
setConversation((prev) => {
|
|
122
|
+
// A reset can land between this effect's render and its update.
|
|
123
|
+
if (prev.length === 0) {
|
|
124
|
+
return prev;
|
|
125
|
+
}
|
|
122
126
|
const lastMessage = prev[prev.length - 1];
|
|
123
127
|
const content: string = response || '';
|
|
124
128
|
|
|
@@ -214,9 +218,6 @@ export function SearchAiDialog({
|
|
|
214
218
|
messageId={item.messageId}
|
|
215
219
|
feedback={item.feedback}
|
|
216
220
|
onFeedbackChange={handleFeedbackChange}
|
|
217
|
-
toolCalls={
|
|
218
|
-
item.toolCalls || (index === conversation.length - 1 ? toolCalls : undefined)
|
|
219
|
-
}
|
|
220
221
|
contentSegments={item.contentSegments}
|
|
221
222
|
/>
|
|
222
223
|
))}
|
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
type SearchAiMessageResource,
|
|
7
7
|
type ToolCall,
|
|
8
8
|
type ContentSegment,
|
|
9
|
-
ToolCallName,
|
|
10
9
|
} from '@redocly/theme/core/types';
|
|
11
|
-
import {
|
|
10
|
+
import { AiSearchConversationRole } from '@redocly/theme/core/constants';
|
|
11
|
+
import { groupToolCallSegments, hasToolCallsAndText } from '@redocly/theme/core/utils';
|
|
12
12
|
import { Link } from '@redocly/theme/components/Link/Link';
|
|
13
13
|
import { Tag } from '@redocly/theme/components/Tag/Tag';
|
|
14
14
|
import { useThemeHooks } from '@redocly/theme/core/hooks';
|
|
@@ -18,43 +18,14 @@ import { AiStarsIcon } from '@redocly/theme/icons/AiStarsIcon/AiStarsIcon';
|
|
|
18
18
|
import { CheckmarkOutlineIcon } from '@redocly/theme/icons/CheckmarkOutlineIcon/CheckmarkOutlineIcon';
|
|
19
19
|
import { SearchAiActionButtons } from '@redocly/theme/components/Search/SearchAiActionButtons';
|
|
20
20
|
import { SearchAiNegativeFeedbackForm } from '@redocly/theme/components/Search/SearchAiNegativeFeedbackForm';
|
|
21
|
+
import { SearchAiActivity } from '@redocly/theme/components/Search/SearchAiActivity';
|
|
22
|
+
import { searchAiAppearAnimation } from '@redocly/theme/core/styles';
|
|
21
23
|
|
|
22
24
|
function MarkdownSegment({ text }: { text: string }): JSX.Element {
|
|
23
25
|
const { useMarkdownText } = useThemeHooks();
|
|
24
26
|
const markdown = useMarkdownText(text);
|
|
25
27
|
return <ResponseText as="div" children={markdown} data-testid="response-text" />;
|
|
26
28
|
}
|
|
27
|
-
// The codemode `execute` tool passes a human-readable `description` of what its code
|
|
28
|
-
// does; show that instead of a generic "Executing execute..." label.
|
|
29
|
-
function getExecuteDescription(args: unknown): string | undefined {
|
|
30
|
-
if (args && typeof args === 'object' && 'description' in args) {
|
|
31
|
-
const { description } = args as { description?: unknown };
|
|
32
|
-
if (typeof description === 'string' && description.trim().length > 0) {
|
|
33
|
-
return description.trim();
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return undefined;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function getToolCallDisplayText(toolCall: ToolCall): {
|
|
40
|
-
inProgressText: string;
|
|
41
|
-
completedText: string;
|
|
42
|
-
} {
|
|
43
|
-
if (toolCall.name === ToolCallName.Execute) {
|
|
44
|
-
const description = getExecuteDescription(toolCall.args);
|
|
45
|
-
if (description) {
|
|
46
|
-
return { inProgressText: description, completedText: description };
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
TOOL_CALL_DISPLAY_TEXT[toolCall.name] ?? {
|
|
52
|
-
inProgressText: `Executing ${toolCall.name}...`,
|
|
53
|
-
completedText: `${toolCall.name} executed`,
|
|
54
|
-
}
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
29
|
export type SearchAiMessageProps = {
|
|
59
30
|
role: AiSearchConversationRole;
|
|
60
31
|
content: string;
|
|
@@ -64,6 +35,7 @@ export type SearchAiMessageProps = {
|
|
|
64
35
|
messageId?: string;
|
|
65
36
|
feedback?: FeedbackType;
|
|
66
37
|
onFeedbackChange: (messageId: string, feedback: FeedbackType | undefined) => void;
|
|
38
|
+
/** @deprecated Not used; the message reads its tool calls from `contentSegments`. */
|
|
67
39
|
toolCalls?: ToolCall[];
|
|
68
40
|
contentSegments?: ContentSegment[];
|
|
69
41
|
};
|
|
@@ -77,7 +49,6 @@ function SearchAiMessageComponent({
|
|
|
77
49
|
messageId,
|
|
78
50
|
feedback,
|
|
79
51
|
onFeedbackChange,
|
|
80
|
-
toolCalls = [],
|
|
81
52
|
contentSegments = [{ type: 'text' as const, text: content }],
|
|
82
53
|
}: SearchAiMessageProps): JSX.Element {
|
|
83
54
|
const { useTranslate, useTelemetry } = useThemeHooks();
|
|
@@ -90,7 +61,8 @@ function SearchAiMessageComponent({
|
|
|
90
61
|
|
|
91
62
|
const showSuccessMessage = feedbackSent && feedback;
|
|
92
63
|
|
|
93
|
-
const
|
|
64
|
+
const groupedSegments = groupToolCallSegments(contentSegments, isThinking);
|
|
65
|
+
const isFullWidth = hasToolCallsAndText(contentSegments);
|
|
94
66
|
|
|
95
67
|
const sendFeedbackTelemetry = (feedbackValue: FeedbackType, dislikeReason?: string) => {
|
|
96
68
|
if (!messageId) return;
|
|
@@ -143,29 +115,18 @@ function SearchAiMessageComponent({
|
|
|
143
115
|
margin="0 var(--spacing-xs) 0 0"
|
|
144
116
|
/>
|
|
145
117
|
)}
|
|
146
|
-
<MessageContentWrapper>
|
|
118
|
+
<MessageContentWrapper $isFullWidth={isFullWidth}>
|
|
147
119
|
{role === AiSearchConversationRole.ASSISTANT ? (
|
|
148
120
|
<>
|
|
149
|
-
<MessageWrapper $role={role}>
|
|
150
|
-
{
|
|
151
|
-
if (segment.type === '
|
|
152
|
-
const toolCallCompleted = Boolean(segment.toolCall.result);
|
|
153
|
-
|
|
154
|
-
const { inProgressText, completedText } = getToolCallDisplayText(
|
|
155
|
-
segment.toolCall,
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
const toolCallDisplayText = toolCallCompleted ? completedText : inProgressText;
|
|
159
|
-
|
|
121
|
+
<MessageWrapper $role={role} $isFullWidth={isFullWidth}>
|
|
122
|
+
{groupedSegments.map((segment, index) => {
|
|
123
|
+
if (segment.type === 'tools') {
|
|
160
124
|
return (
|
|
161
|
-
<
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
</ToolCallText>
|
|
167
|
-
</ToolCallInfoItem>
|
|
168
|
-
</ToolCallsInfoWrapper>
|
|
125
|
+
<ToolCallsActivity
|
|
126
|
+
key={`tools-${index}`}
|
|
127
|
+
toolCalls={segment.toolCalls}
|
|
128
|
+
isActive={segment.isActive}
|
|
129
|
+
/>
|
|
169
130
|
);
|
|
170
131
|
}
|
|
171
132
|
|
|
@@ -193,14 +154,7 @@ function SearchAiMessageComponent({
|
|
|
193
154
|
</ResourcesWrapper>
|
|
194
155
|
</>
|
|
195
156
|
)}
|
|
196
|
-
{
|
|
197
|
-
<ThinkingDotsWrapper data-testid="thinking-dots-wrapper">
|
|
198
|
-
<ThinkingDot />
|
|
199
|
-
<ThinkingDot />
|
|
200
|
-
<ThinkingDot />
|
|
201
|
-
</ThinkingDotsWrapper>
|
|
202
|
-
)}
|
|
203
|
-
{content.length > 0 && (
|
|
157
|
+
{!isThinking && content.length > 0 && (
|
|
204
158
|
<FeedbackWrapper>
|
|
205
159
|
<SearchAiActionButtons
|
|
206
160
|
content={content}
|
|
@@ -261,7 +215,6 @@ export const SearchAiMessage = memo(SearchAiMessageComponent, (prevProps, nextPr
|
|
|
261
215
|
prevProps.feedback === nextProps.feedback &&
|
|
262
216
|
prevProps.onFeedbackChange === nextProps.onFeedbackChange &&
|
|
263
217
|
areResourcesEqual(prevProps.resources, nextProps.resources) &&
|
|
264
|
-
prevProps.toolCalls?.length === nextProps.toolCalls?.length &&
|
|
265
218
|
prevProps.contentSegments === nextProps.contentSegments
|
|
266
219
|
);
|
|
267
220
|
});
|
|
@@ -275,9 +228,10 @@ const SearchAiMessageWrapper = styled.div<{ $role: string }>`
|
|
|
275
228
|
$role === AiSearchConversationRole.USER ? 'flex-end' : 'flex-start'};
|
|
276
229
|
`;
|
|
277
230
|
|
|
278
|
-
const MessageContentWrapper = styled.div
|
|
231
|
+
const MessageContentWrapper = styled.div<{ $isFullWidth: boolean }>`
|
|
279
232
|
display: flex;
|
|
280
233
|
flex-direction: column;
|
|
234
|
+
flex-grow: ${({ $isFullWidth }) => ($isFullWidth ? 1 : 0)};
|
|
281
235
|
gap: var(--spacing-sm);
|
|
282
236
|
max-width: 80%;
|
|
283
237
|
min-width: 0;
|
|
@@ -300,13 +254,13 @@ const ResponseText = styled(Markdown)`
|
|
|
300
254
|
}
|
|
301
255
|
`;
|
|
302
256
|
|
|
303
|
-
const MessageWrapper = styled.div<{ $role: string }>`
|
|
257
|
+
const MessageWrapper = styled.div<{ $role: string; $isFullWidth?: boolean }>`
|
|
304
258
|
padding: ${({ $role }) =>
|
|
305
259
|
$role === AiSearchConversationRole.USER
|
|
306
260
|
? 'var(--spacing-sm)'
|
|
307
261
|
: 'var(--spacing-sm) var(--spacing-sm) var(--spacing-xs) var(--spacing-sm)'};
|
|
308
262
|
border-radius: var(--border-radius-xl);
|
|
309
|
-
width: fit-content;
|
|
263
|
+
width: ${({ $isFullWidth }) => ($isFullWidth ? 'auto' : 'fit-content')};
|
|
310
264
|
max-width: 100%;
|
|
311
265
|
word-wrap: break-word;
|
|
312
266
|
white-space: pre-wrap;
|
|
@@ -327,12 +281,14 @@ const ResourcesWrapper = styled.div`
|
|
|
327
281
|
display: flex;
|
|
328
282
|
flex-direction: column;
|
|
329
283
|
margin: 0;
|
|
284
|
+
${searchAiAppearAnimation}
|
|
330
285
|
`;
|
|
331
286
|
const FeedbackWrapper = styled.div`
|
|
332
287
|
display: flex;
|
|
333
288
|
flex-direction: row;
|
|
334
289
|
gap: var(--search-ai-feedback-gap);
|
|
335
290
|
margin-top: var(--spacing-sm);
|
|
291
|
+
${searchAiAppearAnimation}
|
|
336
292
|
`;
|
|
337
293
|
|
|
338
294
|
const ResourcesTitle = styled.div`
|
|
@@ -367,43 +323,6 @@ const ResourceTag = styled(Tag).attrs({ textTransform: 'none' })`
|
|
|
367
323
|
}
|
|
368
324
|
`;
|
|
369
325
|
|
|
370
|
-
const ThinkingDotsWrapper = styled.div`
|
|
371
|
-
display: flex;
|
|
372
|
-
gap: var(--search-ai-thinking-dots-gap);
|
|
373
|
-
padding: var(--search-ai-thinking-dots-padding);
|
|
374
|
-
`;
|
|
375
|
-
|
|
376
|
-
const ThinkingDot = styled.div`
|
|
377
|
-
width: var(--search-ai-thinking-dot-size);
|
|
378
|
-
height: var(--search-ai-thinking-dot-size);
|
|
379
|
-
border-radius: 50%;
|
|
380
|
-
background: var(--search-ai-thinking-dot-color);
|
|
381
|
-
animation: bounce 1.4s infinite ease-in-out;
|
|
382
|
-
|
|
383
|
-
&:nth-child(1) {
|
|
384
|
-
animation-delay: -0.32s;
|
|
385
|
-
}
|
|
386
|
-
&:nth-child(2) {
|
|
387
|
-
animation-delay: -0.16s;
|
|
388
|
-
}
|
|
389
|
-
&:nth-child(3) {
|
|
390
|
-
animation-delay: 0s;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
@keyframes bounce {
|
|
394
|
-
0%,
|
|
395
|
-
80%,
|
|
396
|
-
100% {
|
|
397
|
-
opacity: 0.2;
|
|
398
|
-
transform: scale(0.8);
|
|
399
|
-
}
|
|
400
|
-
40% {
|
|
401
|
-
opacity: 1;
|
|
402
|
-
transform: scale(1);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
`;
|
|
406
|
-
|
|
407
326
|
const SuccessMessageWrapper = styled.div`
|
|
408
327
|
max-width: fit-content;
|
|
409
328
|
display: flex;
|
|
@@ -420,37 +339,10 @@ const SuccessMessageText = styled.div`
|
|
|
420
339
|
color: var(--color-success-darker);
|
|
421
340
|
`;
|
|
422
341
|
|
|
423
|
-
const
|
|
424
|
-
display: flex;
|
|
425
|
-
flex-direction: column;
|
|
426
|
-
gap: var(--spacing-xxs);
|
|
342
|
+
const ToolCallsActivity = styled(SearchAiActivity)`
|
|
427
343
|
margin: 0 0 var(--spacing-sm) 0;
|
|
428
|
-
font-size: var(--font-size-xs);
|
|
429
|
-
color: var(--search-ai-text-color);
|
|
430
|
-
opacity: 0.6;
|
|
431
|
-
`;
|
|
432
|
-
|
|
433
|
-
const ToolCallInfoItem = styled.div`
|
|
434
|
-
display: flex;
|
|
435
|
-
align-items: center;
|
|
436
|
-
gap: var(--spacing-xxs);
|
|
437
|
-
`;
|
|
438
|
-
|
|
439
|
-
const ToolCallText = styled.span<{ $isSearching?: boolean }>`
|
|
440
|
-
font-weight: var(--font-weight-regular);
|
|
441
344
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
animation: pulse 1.5s ease-in-out infinite;
|
|
446
|
-
|
|
447
|
-
@keyframes pulse {
|
|
448
|
-
0%, 100% {
|
|
449
|
-
opacity: 1;
|
|
450
|
-
}
|
|
451
|
-
50% {
|
|
452
|
-
opacity: 0.4;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
`}
|
|
345
|
+
&:last-child {
|
|
346
|
+
margin-bottom: var(--spacing-xxs);
|
|
347
|
+
}
|
|
456
348
|
`;
|
|
@@ -9,7 +9,7 @@ import { Link } from '@redocly/theme/components/Link/Link';
|
|
|
9
9
|
import { Image } from '@redocly/theme/components/Image/Image';
|
|
10
10
|
import { searchHighlight } from '@redocly/theme/components/Search/SearchHighlight';
|
|
11
11
|
import { Badge } from '@redocly/theme/components/Badge/Badge';
|
|
12
|
-
import { trimText, getOperationColor } from '@redocly/theme/core/utils';
|
|
12
|
+
import { assignRef, trimText, getOperationColor } from '@redocly/theme/core/utils';
|
|
13
13
|
import { ReturnKeyIcon } from '@redocly/theme/icons/ReturnKeyIcon/ReturnKeyIcon';
|
|
14
14
|
import { useThemeHooks } from '@redocly/theme/core/hooks';
|
|
15
15
|
|
|
@@ -61,14 +61,7 @@ export function SearchItem({
|
|
|
61
61
|
tabIndex={0}
|
|
62
62
|
innerRef={(el: HTMLAnchorElement) => {
|
|
63
63
|
ref.current = el;
|
|
64
|
-
|
|
65
|
-
if (!innerRef) return;
|
|
66
|
-
|
|
67
|
-
if (typeof innerRef === 'function') {
|
|
68
|
-
innerRef(el);
|
|
69
|
-
} else {
|
|
70
|
-
innerRef.current = el;
|
|
71
|
-
}
|
|
64
|
+
assignRef(innerRef, el);
|
|
72
65
|
}}
|
|
73
66
|
data-component-name="Search/SearchItem"
|
|
74
67
|
>
|
|
@@ -199,8 +199,21 @@ export const search = css`
|
|
|
199
199
|
--search-ai-suggestions-title-font-weight: var(--font-weight-light);
|
|
200
200
|
--search-ai-suggestions-text-color: var(--text-color-description);
|
|
201
201
|
|
|
202
|
-
--search-ai-
|
|
203
|
-
--search-ai-
|
|
202
|
+
--search-ai-activity-font-size: var(--font-size-base);
|
|
203
|
+
--search-ai-activity-line-height: var(--line-height-sm);
|
|
204
|
+
--search-ai-activity-count-font-size: var(--font-size-sm);
|
|
205
|
+
--search-ai-activity-count-bg-color: var(--border-color-secondary);
|
|
206
|
+
--search-ai-activity-text-color: var(--text-color-description);
|
|
207
|
+
--search-ai-activity-text-color-hover: var(--text-color-primary);
|
|
208
|
+
--search-ai-activity-detail-color: var(--text-color-secondary);
|
|
209
|
+
--search-ai-activity-shimmer-color: var(--text-color-primary);
|
|
210
|
+
--search-ai-activity-rail-color: var(--border-color-primary);
|
|
211
|
+
--search-ai-activity-icon-size: 14px;
|
|
212
|
+
--search-ai-activity-fade-duration: 200ms;
|
|
213
|
+
--search-ai-activity-expand-duration: 250ms;
|
|
214
|
+
--search-ai-activity-shimmer-duration: 2s;
|
|
215
|
+
--search-ai-activity-easing: cubic-bezier(0.32, 0.72, 0, 1);
|
|
216
|
+
|
|
204
217
|
--search-ai-thinking-dot-size: 6px;
|
|
205
218
|
--search-ai-thinking-dot-color: var(--search-ai-gradient);
|
|
206
219
|
|
|
@@ -6,6 +6,7 @@ import type { SelectOption } from '@redocly/theme/core/types';
|
|
|
6
6
|
import { Tag } from '@redocly/theme/components/Tag/Tag';
|
|
7
7
|
import { CloseIcon } from '@redocly/theme/icons/CloseIcon/CloseIcon';
|
|
8
8
|
import { Button } from '@redocly/theme/components/Button/Button';
|
|
9
|
+
import { assignRef } from '@redocly/theme/core/utils';
|
|
9
10
|
|
|
10
11
|
type SelectInputProps<T> = {
|
|
11
12
|
id?: string;
|
|
@@ -125,14 +126,7 @@ export function SelectInput<T>(props: SelectInputProps<T>): React.ReactNode {
|
|
|
125
126
|
if (!input) return;
|
|
126
127
|
|
|
127
128
|
inputRef.current = input;
|
|
128
|
-
|
|
129
|
-
if (!forwardedInputRef) return;
|
|
130
|
-
|
|
131
|
-
if (typeof forwardedInputRef === 'function') {
|
|
132
|
-
forwardedInputRef(input);
|
|
133
|
-
} else {
|
|
134
|
-
forwardedInputRef.current = input;
|
|
135
|
-
}
|
|
129
|
+
assignRef(forwardedInputRef, input);
|
|
136
130
|
}}
|
|
137
131
|
$width={multiple ? (!searchValue && selectedOptions.length ? '10px' : 'auto') : '100%'}
|
|
138
132
|
tabIndex={0}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useRef } from 'react';
|
|
2
2
|
import styled from 'styled-components';
|
|
3
3
|
|
|
4
4
|
import type { ComponentProps, JSX } from 'react';
|
|
@@ -45,9 +45,14 @@ export function SplitButton(props: SplitButtonProps): JSX.Element {
|
|
|
45
45
|
} = props;
|
|
46
46
|
|
|
47
47
|
const hasMenu = typeof children === 'function' || React.Children.toArray(children).length > 0;
|
|
48
|
+
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
48
49
|
|
|
49
50
|
return (
|
|
50
|
-
<SplitButtonWrapper
|
|
51
|
+
<SplitButtonWrapper
|
|
52
|
+
data-component-name="SplitButton/SplitButton"
|
|
53
|
+
className={className}
|
|
54
|
+
ref={wrapperRef}
|
|
55
|
+
>
|
|
51
56
|
<ButtonGroup variant={variant} size={size}>
|
|
52
57
|
{button}
|
|
53
58
|
{hasMenu ? (
|
|
@@ -56,6 +61,7 @@ export function SplitButton(props: SplitButtonProps): JSX.Element {
|
|
|
56
61
|
placement={placement}
|
|
57
62
|
alignment={alignment}
|
|
58
63
|
portalled={portalled}
|
|
64
|
+
alignmentAnchorRef={wrapperRef}
|
|
59
65
|
withArrow
|
|
60
66
|
onOpen={onOpen}
|
|
61
67
|
onClose={onClose}
|
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import type { MCPOption } from '../types/mcp';
|
|
2
|
-
|
|
3
1
|
export const DEFAULT_MCP_SERVER_NAME = 'mcp-server';
|
|
4
2
|
|
|
5
3
|
export const MCP_ROUTE = '/mcp';
|
|
6
4
|
|
|
7
5
|
export const PUBLIC_MCP_ROUTE = '/mcp-public';
|
|
8
6
|
|
|
9
|
-
export const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
'claude-code',
|
|
13
|
-
'codex',
|
|
14
|
-
'copy',
|
|
15
|
-
];
|
|
7
|
+
export const MCP_OPTIONS = ['cursor', 'vscode', 'claude-code', 'codex', 'copy'] as const;
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_MCP_OPTIONS = [...MCP_OPTIONS];
|
|
@@ -53,7 +53,7 @@ export const TOOL_CALL_DISPLAY_TEXT: Partial<
|
|
|
53
53
|
> = {
|
|
54
54
|
[ToolCallName.SearchDocumentation]: {
|
|
55
55
|
inProgressText: 'Searching documentation...',
|
|
56
|
-
completedText: '
|
|
56
|
+
completedText: 'Searched the documentation',
|
|
57
57
|
},
|
|
58
58
|
[ToolCallName.ListApis]: {
|
|
59
59
|
inProgressText: 'Browsing APIs...',
|
|
@@ -75,6 +75,10 @@ export const TOOL_CALL_DISPLAY_TEXT: Partial<
|
|
|
75
75
|
inProgressText: 'Reading API description...',
|
|
76
76
|
completedText: 'Read API description',
|
|
77
77
|
},
|
|
78
|
+
[ToolCallName.Execute]: {
|
|
79
|
+
inProgressText: 'Running code...',
|
|
80
|
+
completedText: 'Ran code',
|
|
81
|
+
},
|
|
78
82
|
[ToolCallName.DescribeTools]: {
|
|
79
83
|
inProgressText: 'Discovering available tools...',
|
|
80
84
|
completedText: 'Discovered available tools',
|
|
@@ -23,6 +23,7 @@ export * from '../menu/use-mobile-menu-levels';
|
|
|
23
23
|
export * from '../feedback/use-report-dialog';
|
|
24
24
|
export * from '../use-product-picker';
|
|
25
25
|
export * from '../search/use-search-dialog';
|
|
26
|
+
export * from '../search/use-search-ai-activity';
|
|
26
27
|
export * from '../use-language-picker';
|
|
27
28
|
export * from './use-element-size';
|
|
28
29
|
export * from './use-time-ago';
|
package/src/core/hooks/index.ts
CHANGED
|
@@ -30,6 +30,7 @@ export * from './use-product-picker';
|
|
|
30
30
|
export * from './search/use-search-dialog';
|
|
31
31
|
export * from './search/use-search-filter';
|
|
32
32
|
export * from './search/use-feedback-tooltip';
|
|
33
|
+
export * from './search/use-search-ai-activity';
|
|
33
34
|
export * from './use-controlled-state';
|
|
34
35
|
export * from './use-codeblock-tabs-controls';
|
|
35
36
|
export * from './code-walkthrough/use-code-walkthrough';
|
|
@@ -207,5 +207,5 @@ export function useEvent<T extends (...args: any[]) => any>(callback?: T): T {
|
|
|
207
207
|
ref.current = callback;
|
|
208
208
|
});
|
|
209
209
|
|
|
210
|
-
return useCallback((
|
|
210
|
+
return useCallback((...args: Parameters<T>) => ref.current?.(...args), []) as T;
|
|
211
211
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { SearchAiActivityStatus, SearchAiActivityStep, ToolCall } from '../../types/search';
|
|
2
|
+
|
|
3
|
+
import { useThemeHooks } from '../use-theme-hooks';
|
|
4
|
+
import { getSearchAiActivityStatus, getSearchAiActivityStep } from '../../utils/search-ai-activity';
|
|
5
|
+
|
|
6
|
+
type UseSearchAiActivityParams = {
|
|
7
|
+
toolCalls: ToolCall[];
|
|
8
|
+
isActive: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type UseSearchAiActivityReturn = {
|
|
12
|
+
status: SearchAiActivityStatus;
|
|
13
|
+
steps: SearchAiActivityStep[];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function useSearchAiActivity({
|
|
17
|
+
toolCalls,
|
|
18
|
+
isActive,
|
|
19
|
+
}: UseSearchAiActivityParams): UseSearchAiActivityReturn {
|
|
20
|
+
const { useTranslate } = useThemeHooks();
|
|
21
|
+
const { translate } = useTranslate();
|
|
22
|
+
|
|
23
|
+
const runningToolCall = isActive ? toolCalls.find((toolCall) => !toolCall.result) : undefined;
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
status: getSearchAiActivityStatus(toolCalls, isActive, runningToolCall, translate),
|
|
27
|
+
steps: toolCalls.map((toolCall) =>
|
|
28
|
+
getSearchAiActivityStep(toolCall, isActive && !toolCall.result, translate),
|
|
29
|
+
),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -7,7 +7,7 @@ import { useThemeHooks } from './use-theme-hooks';
|
|
|
7
7
|
import { useMCPConfig } from './use-mcp-config';
|
|
8
8
|
import { ClipboardService } from '../utils/clipboard-service';
|
|
9
9
|
import { DEFAULT_MCP_OPTIONS } from '../constants/mcp';
|
|
10
|
-
import { generateClaudeCodeCommand, generateCodexCommand } from '../utils/mcp';
|
|
10
|
+
import { generateClaudeCodeCommand, generateCodexCommand, isMCPOption } from '../utils/mcp';
|
|
11
11
|
import { getBaseDataAttributes } from '../utils/telemetry/get-base-data-attributes';
|
|
12
12
|
|
|
13
13
|
export type McpButtonOptions = {
|
|
@@ -80,7 +80,12 @@ export function useConnectMCPButton({
|
|
|
80
80
|
[translate],
|
|
81
81
|
);
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
// Markdoc reports attribute errors but still renders the tag, so `options` can be any value.
|
|
84
|
+
const visibleOptions = useMemo(
|
|
85
|
+
() =>
|
|
86
|
+
Array.isArray(options) ? [...new Set(options.filter(isMCPOption))] : [...DEFAULT_MCP_OPTIONS],
|
|
87
|
+
[options],
|
|
88
|
+
);
|
|
84
89
|
|
|
85
90
|
return {
|
|
86
91
|
copiedOption,
|
|
@@ -3,9 +3,9 @@ import throttle from 'lodash.throttle';
|
|
|
3
3
|
|
|
4
4
|
import type { RefObject } from 'react';
|
|
5
5
|
|
|
6
|
-
export function useFullHeight(
|
|
6
|
+
export function useFullHeight(elementRef: RefObject<HTMLElement | null>): void {
|
|
7
7
|
const handler = useCallback(() => {
|
|
8
|
-
if (!
|
|
8
|
+
if (!elementRef.current) {
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -14,19 +14,19 @@ export function useFullHeight(element: RefObject<HTMLElement | null>): void {
|
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const elementTop =
|
|
17
|
+
const elementTop = elementRef.current.getBoundingClientRect().top;
|
|
18
18
|
const footerOffset = footer.offsetTop - window.scrollY - elementTop;
|
|
19
19
|
if (footerOffset > window.innerHeight) {
|
|
20
|
-
|
|
20
|
+
elementRef.current.style.maxHeight = `${window.innerHeight - elementTop}px`;
|
|
21
21
|
} else {
|
|
22
|
-
|
|
22
|
+
elementRef.current.style.maxHeight = `${footerOffset}px`;
|
|
23
23
|
}
|
|
24
|
-
}, [
|
|
24
|
+
}, [elementRef]);
|
|
25
25
|
|
|
26
26
|
const throttledHandler = useMemo(() => throttle(handler, 150), [handler]);
|
|
27
27
|
|
|
28
28
|
useEffect(() => {
|
|
29
|
-
if (typeof window === 'undefined' || !
|
|
29
|
+
if (typeof window === 'undefined' || !elementRef.current) {
|
|
30
30
|
return () => {
|
|
31
31
|
// nope
|
|
32
32
|
};
|
|
@@ -45,5 +45,5 @@ export function useFullHeight(element: RefObject<HTMLElement | null>): void {
|
|
|
45
45
|
window.removeEventListener('scroll', throttledHandler);
|
|
46
46
|
window.removeEventListener('resize', throttledHandler);
|
|
47
47
|
};
|
|
48
|
-
}, [throttledHandler,
|
|
48
|
+
}, [throttledHandler, elementRef, handler]);
|
|
49
49
|
}
|