@redocly/theme 0.70.0-next.5 → 0.70.0-next.7
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/Portal/Portal.js +37 -5
- package/lib/components/Search/SearchAiActionButtons.js +8 -3
- 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/SearchDialog.js +10 -0
- 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/contexts/PortalContainerContext.d.ts +2 -0
- package/lib/core/contexts/PortalContainerContext.js +7 -0
- package/lib/core/hooks/index.d.ts +2 -0
- package/lib/core/hooks/index.js +2 -0
- package/lib/core/hooks/menu/use-collapse.js +1 -1
- package/lib/core/hooks/search/use-persisted-quick-filter.d.ts +21 -0
- package/lib/core/hooks/search/use-persisted-quick-filter.js +92 -0
- 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 +3 -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/Portal/Portal.tsx +4 -2
- package/src/components/Search/SearchAiActionButtons.tsx +31 -19
- 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/SearchDialog.tsx +11 -0
- 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/contexts/PortalContainerContext.tsx +4 -0
- package/src/core/hooks/__mocks__/index.ts +2 -0
- package/src/core/hooks/index.ts +2 -0
- package/src/core/hooks/menu/use-collapse.ts +1 -1
- package/src/core/hooks/search/use-persisted-quick-filter.ts +125 -0
- 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 +4 -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
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import React, { useId, useState, type ComponentType, type JSX } from 'react';
|
|
2
|
+
import styled, { css, keyframes } from 'styled-components';
|
|
3
|
+
|
|
4
|
+
import type { ToolCall, SearchAiActivityKind } from '@redocly/theme/core/types';
|
|
5
|
+
import type { IconProps } from '@redocly/theme/icons/types';
|
|
6
|
+
|
|
7
|
+
import { useSearchAiActivity } from '@redocly/theme/core/hooks';
|
|
8
|
+
import { searchAiAppearAnimation } from '@redocly/theme/core/styles';
|
|
9
|
+
import { AiStarsIcon } from '@redocly/theme/icons/AiStarsIcon/AiStarsIcon';
|
|
10
|
+
import { ChatIcon } from '@redocly/theme/icons/ChatIcon/ChatIcon';
|
|
11
|
+
import { ChevronDownIcon } from '@redocly/theme/icons/ChevronDownIcon/ChevronDownIcon';
|
|
12
|
+
import { CodeIcon } from '@redocly/theme/icons/CodeIcon/CodeIcon';
|
|
13
|
+
import { SearchIcon } from '@redocly/theme/icons/SearchIcon/SearchIcon';
|
|
14
|
+
|
|
15
|
+
const ACTIVITY_KIND_ICONS: Record<SearchAiActivityKind, ComponentType<IconProps>> = {
|
|
16
|
+
thinking: AiStarsIcon,
|
|
17
|
+
search: SearchIcon,
|
|
18
|
+
support: ChatIcon,
|
|
19
|
+
code: CodeIcon,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type SearchAiActivityProps = {
|
|
23
|
+
/** One run of consecutive tool calls; an empty list means the assistant has not called a tool yet. */
|
|
24
|
+
toolCalls: ToolCall[];
|
|
25
|
+
/** The assistant works on this group now; once it moves on, a call without a result counts as finished. */
|
|
26
|
+
isActive: boolean;
|
|
27
|
+
className?: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function SearchAiActivity({
|
|
31
|
+
toolCalls,
|
|
32
|
+
isActive,
|
|
33
|
+
className,
|
|
34
|
+
}: SearchAiActivityProps): JSX.Element {
|
|
35
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
36
|
+
const listId = useId();
|
|
37
|
+
const { status, steps } = useSearchAiActivity({ toolCalls, isActive });
|
|
38
|
+
const StatusIcon = ACTIVITY_KIND_ICONS[status.kind];
|
|
39
|
+
|
|
40
|
+
const header = (
|
|
41
|
+
<>
|
|
42
|
+
<IconWrapper aria-hidden>
|
|
43
|
+
<StatusIcon size="var(--search-ai-activity-icon-size)" />
|
|
44
|
+
</IconWrapper>
|
|
45
|
+
<StatusLabel
|
|
46
|
+
key={`${status.label}${status.detail ?? ''}`}
|
|
47
|
+
title={status.detail ? `${status.label} “${status.detail}”` : status.label}
|
|
48
|
+
$isRunning={status.isRunning}
|
|
49
|
+
>
|
|
50
|
+
{status.label}
|
|
51
|
+
{status.detail && <StatusDetail> “{status.detail}”</StatusDetail>}
|
|
52
|
+
</StatusLabel>
|
|
53
|
+
{steps.length > 1 && (
|
|
54
|
+
<>
|
|
55
|
+
{' '}
|
|
56
|
+
<ToolCallCount>{steps.length}</ToolCallCount>
|
|
57
|
+
</>
|
|
58
|
+
)}
|
|
59
|
+
{steps.length > 0 && (
|
|
60
|
+
<ExpandIcon size="var(--search-ai-activity-icon-size)" $isExpanded={isExpanded} />
|
|
61
|
+
)}
|
|
62
|
+
</>
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<SearchAiActivityWrapper
|
|
67
|
+
data-component-name="Search/SearchAiActivity"
|
|
68
|
+
data-testid="tool-calls-info"
|
|
69
|
+
className={className}
|
|
70
|
+
>
|
|
71
|
+
{steps.length > 0 ? (
|
|
72
|
+
<>
|
|
73
|
+
<HeaderButton
|
|
74
|
+
type="button"
|
|
75
|
+
aria-expanded={isExpanded}
|
|
76
|
+
aria-controls={listId}
|
|
77
|
+
onClick={() => setIsExpanded((expanded) => !expanded)}
|
|
78
|
+
>
|
|
79
|
+
{header}
|
|
80
|
+
</HeaderButton>
|
|
81
|
+
<StepsWrapper id={listId} $isExpanded={isExpanded}>
|
|
82
|
+
<StepList>
|
|
83
|
+
{steps.map((step) => {
|
|
84
|
+
const StepIcon = ACTIVITY_KIND_ICONS[step.kind];
|
|
85
|
+
return (
|
|
86
|
+
<StepItem key={step.id}>
|
|
87
|
+
<StepIconWrapper aria-hidden>
|
|
88
|
+
<StepIcon size="var(--search-ai-activity-icon-size)" />
|
|
89
|
+
</StepIconWrapper>
|
|
90
|
+
<StepText $isRunning={step.isRunning && isExpanded}>{step.label}</StepText>
|
|
91
|
+
</StepItem>
|
|
92
|
+
);
|
|
93
|
+
})}
|
|
94
|
+
</StepList>
|
|
95
|
+
</StepsWrapper>
|
|
96
|
+
</>
|
|
97
|
+
) : (
|
|
98
|
+
<HeaderWrapper>{header}</HeaderWrapper>
|
|
99
|
+
)}
|
|
100
|
+
</SearchAiActivityWrapper>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const labelIn = keyframes`
|
|
105
|
+
from {
|
|
106
|
+
opacity: 0.35;
|
|
107
|
+
}
|
|
108
|
+
`;
|
|
109
|
+
|
|
110
|
+
const shimmerSweep = keyframes`
|
|
111
|
+
from {
|
|
112
|
+
background-position: 100% 0;
|
|
113
|
+
}
|
|
114
|
+
to {
|
|
115
|
+
background-position: 0 0;
|
|
116
|
+
}
|
|
117
|
+
`;
|
|
118
|
+
|
|
119
|
+
const shimmer = css`
|
|
120
|
+
@supports (background-clip: text) or (-webkit-background-clip: text) {
|
|
121
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
122
|
+
background:
|
|
123
|
+
linear-gradient(
|
|
124
|
+
90deg,
|
|
125
|
+
transparent 0%,
|
|
126
|
+
transparent 40%,
|
|
127
|
+
var(--search-ai-activity-shimmer-color) 50%,
|
|
128
|
+
transparent 60%,
|
|
129
|
+
transparent 100%
|
|
130
|
+
)
|
|
131
|
+
100% 0 / 250% 100% no-repeat,
|
|
132
|
+
linear-gradient(currentColor, currentColor);
|
|
133
|
+
-webkit-background-clip: text;
|
|
134
|
+
background-clip: text;
|
|
135
|
+
-webkit-text-fill-color: transparent;
|
|
136
|
+
animation:
|
|
137
|
+
${labelIn} var(--search-ai-activity-fade-duration) ease-out,
|
|
138
|
+
${shimmerSweep} var(--search-ai-activity-shimmer-duration) linear
|
|
139
|
+
infinite;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
`;
|
|
143
|
+
|
|
144
|
+
const SearchAiActivityWrapper = styled.div`
|
|
145
|
+
font-size: var(--search-ai-activity-font-size);
|
|
146
|
+
line-height: var(--search-ai-activity-line-height);
|
|
147
|
+
color: var(--search-ai-activity-text-color);
|
|
148
|
+
`;
|
|
149
|
+
|
|
150
|
+
const headerStyles = css`
|
|
151
|
+
display: flex;
|
|
152
|
+
align-items: center;
|
|
153
|
+
gap: var(--spacing-xs);
|
|
154
|
+
max-width: 100%;
|
|
155
|
+
min-height: var(--search-ai-activity-line-height);
|
|
156
|
+
white-space: nowrap;
|
|
157
|
+
`;
|
|
158
|
+
|
|
159
|
+
const HeaderWrapper = styled.div`
|
|
160
|
+
${headerStyles}
|
|
161
|
+
`;
|
|
162
|
+
|
|
163
|
+
const HeaderButton = styled.button`
|
|
164
|
+
${headerStyles}
|
|
165
|
+
margin: 0;
|
|
166
|
+
padding: 0;
|
|
167
|
+
border: 0;
|
|
168
|
+
border-radius: var(--border-radius);
|
|
169
|
+
background: none;
|
|
170
|
+
font: inherit;
|
|
171
|
+
color: inherit;
|
|
172
|
+
text-align: start;
|
|
173
|
+
cursor: pointer;
|
|
174
|
+
transition: color var(--search-ai-activity-fade-duration) ease-out;
|
|
175
|
+
|
|
176
|
+
&:hover {
|
|
177
|
+
color: var(--search-ai-activity-text-color-hover);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
&:focus-visible {
|
|
181
|
+
outline: 2px solid var(--button-border-color-focused);
|
|
182
|
+
outline-offset: 2px;
|
|
183
|
+
}
|
|
184
|
+
`;
|
|
185
|
+
|
|
186
|
+
const IconWrapper = styled.span`
|
|
187
|
+
display: inline-flex;
|
|
188
|
+
flex-shrink: 0;
|
|
189
|
+
width: var(--search-ai-activity-icon-size);
|
|
190
|
+
height: var(--search-ai-activity-icon-size);
|
|
191
|
+
`;
|
|
192
|
+
|
|
193
|
+
const StatusLabel = styled.span<{ $isRunning: boolean }>`
|
|
194
|
+
min-width: 0;
|
|
195
|
+
overflow: hidden;
|
|
196
|
+
text-overflow: ellipsis;
|
|
197
|
+
font-weight: var(--font-weight-regular);
|
|
198
|
+
animation: ${labelIn} var(--search-ai-activity-fade-duration) ease-out;
|
|
199
|
+
|
|
200
|
+
@media (prefers-reduced-motion: reduce) {
|
|
201
|
+
animation: none;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
${({ $isRunning }) => $isRunning && shimmer}
|
|
205
|
+
`;
|
|
206
|
+
|
|
207
|
+
const StatusDetail = styled.span`
|
|
208
|
+
color: var(--search-ai-activity-detail-color);
|
|
209
|
+
`;
|
|
210
|
+
|
|
211
|
+
const ToolCallCount = styled.span`
|
|
212
|
+
flex-shrink: 0;
|
|
213
|
+
min-width: calc(var(--search-ai-activity-line-height) - 4px);
|
|
214
|
+
padding: 0 var(--spacing-xxs);
|
|
215
|
+
border-radius: calc(var(--search-ai-activity-line-height) / 2);
|
|
216
|
+
background: var(--search-ai-activity-count-bg-color);
|
|
217
|
+
font-size: var(--search-ai-activity-count-font-size);
|
|
218
|
+
line-height: calc(var(--search-ai-activity-line-height) - 4px);
|
|
219
|
+
font-variant-numeric: tabular-nums;
|
|
220
|
+
text-align: center;
|
|
221
|
+
`;
|
|
222
|
+
|
|
223
|
+
const ExpandIcon = styled(ChevronDownIcon)<{ $isExpanded: boolean }>`
|
|
224
|
+
flex-shrink: 0;
|
|
225
|
+
margin-inline-start: calc(var(--spacing-xxs) * -1);
|
|
226
|
+
transform: rotate(${({ $isExpanded }) => ($isExpanded ? '180deg' : '0deg')});
|
|
227
|
+
transition: transform var(--search-ai-activity-expand-duration)
|
|
228
|
+
var(--search-ai-activity-easing);
|
|
229
|
+
|
|
230
|
+
@media (prefers-reduced-motion: reduce) {
|
|
231
|
+
transition: none;
|
|
232
|
+
}
|
|
233
|
+
`;
|
|
234
|
+
|
|
235
|
+
const StepsWrapper = styled.div<{ $isExpanded: boolean }>`
|
|
236
|
+
display: grid;
|
|
237
|
+
grid-template-rows: ${({ $isExpanded }) => ($isExpanded ? '1fr' : '0fr')};
|
|
238
|
+
opacity: ${({ $isExpanded }) => ($isExpanded ? 1 : 0)};
|
|
239
|
+
visibility: ${({ $isExpanded }) => ($isExpanded ? 'visible' : 'hidden')};
|
|
240
|
+
${({ $isExpanded }) =>
|
|
241
|
+
!$isExpanded &&
|
|
242
|
+
css`
|
|
243
|
+
contain: inline-size;
|
|
244
|
+
`}
|
|
245
|
+
transition:
|
|
246
|
+
grid-template-rows var(--search-ai-activity-expand-duration) var(--search-ai-activity-easing),
|
|
247
|
+
opacity var(--search-ai-activity-expand-duration) ease-out,
|
|
248
|
+
visibility var(--search-ai-activity-expand-duration);
|
|
249
|
+
|
|
250
|
+
@media (prefers-reduced-motion: reduce) {
|
|
251
|
+
transition: none;
|
|
252
|
+
}
|
|
253
|
+
`;
|
|
254
|
+
|
|
255
|
+
const STEP_GAP = 'var(--spacing-xs)';
|
|
256
|
+
const ICON_OFFSET =
|
|
257
|
+
'calc((var(--search-ai-activity-line-height) - var(--search-ai-activity-icon-size)) / 2)';
|
|
258
|
+
const RAIL_GAP = '3px';
|
|
259
|
+
const RAIL_INSET = 'calc(var(--search-ai-activity-icon-size) / 2 - 0.5px)';
|
|
260
|
+
|
|
261
|
+
const StepList = styled.ol`
|
|
262
|
+
min-height: 0;
|
|
263
|
+
overflow: hidden;
|
|
264
|
+
margin: 0;
|
|
265
|
+
padding: 0;
|
|
266
|
+
list-style: none;
|
|
267
|
+
|
|
268
|
+
/* Padding would stay visible while the list is collapsed; the spacer collapses with it. */
|
|
269
|
+
&::before {
|
|
270
|
+
content: "";
|
|
271
|
+
display: block;
|
|
272
|
+
height: ${STEP_GAP};
|
|
273
|
+
}
|
|
274
|
+
`;
|
|
275
|
+
|
|
276
|
+
const StepItem = styled.li`
|
|
277
|
+
position: relative;
|
|
278
|
+
display: flex;
|
|
279
|
+
gap: var(--spacing-xs);
|
|
280
|
+
padding-bottom: ${STEP_GAP};
|
|
281
|
+
${searchAiAppearAnimation}
|
|
282
|
+
|
|
283
|
+
&:last-child {
|
|
284
|
+
padding-bottom: 0;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
&:first-child::before,
|
|
288
|
+
&:not(:last-child)::after {
|
|
289
|
+
content: "";
|
|
290
|
+
position: absolute;
|
|
291
|
+
inset-inline-start: ${RAIL_INSET};
|
|
292
|
+
width: 1px;
|
|
293
|
+
background: var(--search-ai-activity-rail-color);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
&:first-child::before {
|
|
297
|
+
top: calc(${STEP_GAP} * -1 - ${ICON_OFFSET} + ${RAIL_GAP});
|
|
298
|
+
height: calc(${STEP_GAP} + ${ICON_OFFSET} * 2 - ${RAIL_GAP} * 2);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
&:not(:last-child)::after {
|
|
302
|
+
top: calc(
|
|
303
|
+
${ICON_OFFSET} + var(--search-ai-activity-icon-size) + ${RAIL_GAP}
|
|
304
|
+
);
|
|
305
|
+
bottom: calc(${RAIL_GAP} - ${ICON_OFFSET});
|
|
306
|
+
}
|
|
307
|
+
`;
|
|
308
|
+
|
|
309
|
+
const StepIconWrapper = styled(IconWrapper)`
|
|
310
|
+
margin-top: ${ICON_OFFSET};
|
|
311
|
+
`;
|
|
312
|
+
|
|
313
|
+
const StepText = styled.span<{ $isRunning: boolean }>`
|
|
314
|
+
min-width: 0;
|
|
315
|
+
overflow-wrap: anywhere;
|
|
316
|
+
|
|
317
|
+
${({ $isRunning }) => $isRunning && shimmer}
|
|
318
|
+
`;
|
|
@@ -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
|
`;
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
useSearchFilter,
|
|
20
20
|
useRecentSearches,
|
|
21
21
|
useModalScrollLock,
|
|
22
|
+
usePersistedQuickFilter,
|
|
22
23
|
} from '@redocly/theme/core/hooks';
|
|
23
24
|
import { Tag } from '@redocly/theme/components/Tag/Tag';
|
|
24
25
|
import { CloseIcon } from '@redocly/theme/icons/CloseIcon/CloseIcon';
|
|
@@ -81,6 +82,16 @@ export function SearchDialog({
|
|
|
81
82
|
onFacetReset,
|
|
82
83
|
onQuickFilterReset,
|
|
83
84
|
} = useSearchFilter(filter, setFilter);
|
|
85
|
+
usePersistedQuickFilter({
|
|
86
|
+
filter,
|
|
87
|
+
setFilter,
|
|
88
|
+
facets,
|
|
89
|
+
groupField,
|
|
90
|
+
query,
|
|
91
|
+
isSearchLoading,
|
|
92
|
+
searchError,
|
|
93
|
+
enabled: initialMode === 'search',
|
|
94
|
+
});
|
|
84
95
|
const { addSearchHistoryItem } = useRecentSearches();
|
|
85
96
|
const aiSearch = useAiSearch({ filter: filter, product: product?.name });
|
|
86
97
|
useModalScrollLock(true);
|
|
@@ -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
|
|