Skip to content

Commit 22760cd

Browse files
feat(generative-ai): add gradient while a generate request is in progress COMPASS-7836 (#5738)
* feat(generative-ai): add gradient while a generate request is in progress COMPASS-7836 * refactor: clean up
1 parent 0dbc109 commit 22760cd

File tree

1 file changed

+187
-111
lines changed

1 file changed

+187
-111
lines changed

packages/compass-generative-ai/src/components/generative-ai-input.tsx

Lines changed: 187 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -24,46 +24,115 @@ import { AIFeedback } from './ai-feedback';
2424
import { AIGuideCue } from './ai-guide-cue';
2525

2626
const containerStyles = css({
27-
display: 'flex',
27+
width: '100%',
2828
flexDirection: 'column',
29-
gap: spacing[1],
29+
gap: spacing[25],
3030
marginBottom: spacing[300],
3131
});
3232

3333
const inputBarContainerStyles = css({
34+
display: 'flex',
35+
width: '100%',
3436
paddingTop: spacing[100],
3537
gap: spacing[2],
36-
flexGrow: 1,
38+
});
39+
40+
const gradientWidth = spacing[50];
41+
const gradientOffset = spacing[25];
42+
43+
const gradientAnimationStyles = css`
44+
&:before,
45+
&:after {
46+
content: '';
47+
position: absolute;
48+
top: -${gradientWidth + gradientOffset}px;
49+
left: -${gradientWidth + gradientOffset}px;
50+
width: calc(100% + ${(gradientWidth + gradientOffset) * 2}px);
51+
height: calc(100% + ${(gradientWidth + gradientOffset) * 2}px);
52+
border-radius: 12px;
53+
background-color: ${palette.blue.light1};
54+
background-size: 400% 400%;
55+
background-position: 800% 800%;
56+
}
57+
58+
&:after {
59+
animation: 4s animateBg linear;
60+
}
61+
62+
&:before {
63+
filter: blur(4px) opacity(0.6);
64+
animation: 4s animateBg, animateShadow linear infinite;
65+
opacity: 0;
66+
}
67+
68+
@keyframes animateBg {
69+
0% {
70+
background-position: 400% 400%;
71+
background-image: linear-gradient(
72+
20deg,
73+
${palette.blue.light1} 0%,
74+
${palette.blue.light1} 30%,
75+
#00ede0 45%,
76+
#00ebc1 75%,
77+
#0498ec
78+
);
79+
}
80+
100% {
81+
background-position: 0% 0%;
82+
background-image: linear-gradient(
83+
20deg,
84+
${palette.blue.light1} 0%,
85+
${palette.blue.light1} 30%,
86+
#00ede0 45%,
87+
#00ebc1 75%,
88+
#0498ec
89+
);
90+
}
91+
}
92+
93+
@keyframes animateShadow {
94+
0% {
95+
opacity: 1;
96+
}
97+
100% {
98+
opacity: 0;
99+
}
100+
}
101+
`;
102+
103+
const contentWrapperStyles = css({
104+
width: '100%',
37105
display: 'flex',
106+
flexDirection: 'column',
107+
position: 'relative',
108+
zIndex: 2,
38109
});
39110

40111
const inputContainerStyles = css({
41-
display: 'flex',
42-
flexGrow: 1,
112+
width: '100%',
43113
position: 'relative',
44114
});
45115

46116
const textInputStyles = css({
47117
flexGrow: 1,
48-
// Override LeafyGreen input's padding to space for our robot.
49118
input: {
50-
paddingLeft: spacing[5],
51-
paddingRight: spacing[6] * 2 + spacing[2],
119+
paddingLeft: spacing[800],
120+
paddingRight: spacing[1600] * 2 + spacing[200],
52121
},
53122
});
54123

55124
const errorSummaryContainer = css({
56-
marginTop: spacing[1],
125+
marginTop: spacing[100],
57126
});
58127

59128
const floatingButtonsContainerStyles = css({
60129
position: 'absolute',
61-
right: spacing[1],
130+
right: spacing[100],
62131
display: 'flex',
63-
gap: spacing[2],
132+
gap: spacing[200],
64133
alignItems: 'center',
65134
// Match the whole textbox.
66-
height: spacing[4] + spacing[1],
135+
height: spacing[600] + spacing[100],
67136
});
68137

69138
const successIndicatorDarkModeStyles = css({
@@ -80,7 +149,7 @@ const successIndicatorLightModeStyles = css({
80149

81150
const generateButtonStyles = css({
82151
border: 'none',
83-
height: spacing[4] - spacing[1],
152+
height: spacing[600] - spacing[100],
84153
display: 'flex',
85154
fontSize: '12px',
86155
borderRadius: spacing[1],
@@ -96,7 +165,7 @@ const buttonHighlightStyles = css({
96165
// Custom button styles.
97166
height: `${highlightSize}px`,
98167
lineHeight: `${highlightSize}px`,
99-
padding: `0px ${spacing[1]}px`,
168+
padding: `0px ${spacing[100]}px`,
100169
borderRadius: '2px',
101170
});
102171

@@ -111,9 +180,9 @@ const buttonHighlightLightModeStyles = css({
111180
});
112181

113182
const loaderContainerStyles = css({
114-
padding: spacing[1],
183+
padding: spacing[100],
115184
display: 'inline-flex',
116-
width: DEFAULT_AI_ENTRY_SIZE + spacing[2],
185+
width: DEFAULT_AI_ENTRY_SIZE + spacing[200],
117186
justifyContent: 'space-around',
118187
});
119188

@@ -126,10 +195,10 @@ const buttonResetStyles = css({
126195
});
127196

128197
const closeAIButtonStyles = css(buttonResetStyles, focusRing, {
129-
height: spacing[4] + spacing[1],
198+
height: spacing[600] + spacing[100],
130199
display: 'flex',
131200
alignItems: 'center',
132-
padding: `${spacing[1]}px ${spacing[2]}px`,
201+
padding: `${spacing[100]}px ${spacing[200]}px`,
133202
position: 'absolute',
134203
});
135204

@@ -261,102 +330,109 @@ function GenerativeAIInput({
261330
<div className={containerStyles}>
262331
<div className={inputBarContainerStyles}>
263332
<div className={inputContainerStyles}>
264-
<TextInput
265-
className={textInputStyles}
266-
ref={promptTextInputRef}
267-
sizeVariant="small"
268-
data-testid="ai-user-text-input"
269-
aria-label="Enter a plain text query that the AI will translate into MongoDB query language."
270-
placeholder={placeholder}
271-
value={aiPromptText}
272-
onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
273-
onChangeAIPromptText(evt.currentTarget.value)
274-
}
275-
onKeyDown={onTextInputKeyDown}
276-
/>
277-
<div className={floatingButtonsContainerStyles}>
278-
{isFetching ? (
279-
<div className={loaderContainerStyles}>
280-
<SpinLoader />
281-
</div>
282-
) : showSuccess ? (
283-
<div className={loaderContainerStyles}>
284-
<Icon
285-
className={
286-
darkMode
287-
? successIndicatorDarkModeStyles
288-
: successIndicatorLightModeStyles
289-
}
290-
glyph="CheckmarkWithCircle"
291-
/>
292-
</div>
293-
) : (
294-
<>
295-
{aiPromptText && (
296-
<IconButton
297-
aria-label="Clear prompt"
298-
onClick={() => onChangeAIPromptText('')}
299-
data-testid="ai-text-clear-prompt"
300-
>
301-
<Icon glyph="X" />
302-
</IconButton>
303-
)}
304-
</>
305-
)}
306-
<Button
307-
size="small"
308-
className={cx(
309-
generateButtonStyles,
310-
!darkMode && generateButtonLightModeStyles
311-
)}
312-
disabled={!aiPromptText}
313-
data-testid="ai-generate-button"
314-
onClick={() =>
315-
isFetching ? onCancelRequest() : handleSubmit(aiPromptText)
316-
}
333+
<div className={cx(isFetching ? gradientAnimationStyles : null)}>
334+
<div
335+
className={contentWrapperStyles}
336+
data-testid="ai-user-text-input-wrapper"
317337
>
318-
{isFetching ? (
319-
<>
320-
<div>Cancel</div>
321-
<span
322-
className={cx(
323-
buttonHighlightStyles,
324-
darkMode
325-
? buttonHighlightDarkModeStyles
326-
: buttonHighlightLightModeStyles
338+
<TextInput
339+
className={textInputStyles}
340+
ref={promptTextInputRef}
341+
sizeVariant="small"
342+
data-testid="ai-user-text-input"
343+
aria-label="Enter a plain text query that the AI will translate into MongoDB query language."
344+
placeholder={placeholder}
345+
value={aiPromptText}
346+
onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
347+
onChangeAIPromptText(evt.currentTarget.value)
348+
}
349+
onKeyDown={onTextInputKeyDown}
350+
/>
351+
<div className={floatingButtonsContainerStyles}>
352+
{isFetching ? (
353+
<div className={loaderContainerStyles}>
354+
<SpinLoader />
355+
</div>
356+
) : showSuccess ? (
357+
<div className={loaderContainerStyles}>
358+
<Icon
359+
className={
360+
darkMode
361+
? successIndicatorDarkModeStyles
362+
: successIndicatorLightModeStyles
363+
}
364+
glyph="CheckmarkWithCircle"
365+
/>
366+
</div>
367+
) : (
368+
<>
369+
{aiPromptText && (
370+
<IconButton
371+
aria-label="Clear prompt"
372+
onClick={() => onChangeAIPromptText('')}
373+
data-testid="ai-text-clear-prompt"
374+
>
375+
<Icon glyph="X" />
376+
</IconButton>
327377
)}
328-
>
329-
esc
330-
</span>
331-
</>
332-
) : (
333-
<>
334-
<div>Generate</div>
335-
<SubmitArrowSVG darkMode={darkMode} />
336-
</>
337-
)}
338-
</Button>
378+
</>
379+
)}
380+
<Button
381+
size="small"
382+
className={cx(
383+
generateButtonStyles,
384+
!darkMode && generateButtonLightModeStyles
385+
)}
386+
disabled={!aiPromptText}
387+
data-testid="ai-generate-button"
388+
onClick={() =>
389+
isFetching ? onCancelRequest() : handleSubmit(aiPromptText)
390+
}
391+
>
392+
{isFetching ? (
393+
<>
394+
<div>Cancel</div>
395+
<span
396+
className={cx(
397+
buttonHighlightStyles,
398+
darkMode
399+
? buttonHighlightDarkModeStyles
400+
: buttonHighlightLightModeStyles
401+
)}
402+
>
403+
esc
404+
</span>
405+
</>
406+
) : (
407+
<>
408+
<div>Generate</div>
409+
<SubmitArrowSVG darkMode={darkMode} />
410+
</>
411+
)}
412+
</Button>
413+
</div>
414+
<button
415+
className={closeAIButtonStyles}
416+
data-testid="close-ai-button"
417+
aria-label={closeText}
418+
title={closeText}
419+
onClick={() => onClose()}
420+
>
421+
<AIGuideCue
422+
showGuideCue={isAggregationGeneratedFromQuery}
423+
onCloseGuideCue={() => {
424+
onResetIsAggregationGeneratedFromQuery?.();
425+
}}
426+
refEl={guideCueRef}
427+
title="Aggregation generated"
428+
description="Your query requires stages from MongoDB's aggregation framework. Continue to work on it in our Aggregation Pipeline Builder"
429+
/>
430+
<span className={aiEntryContainerStyles} ref={guideCueRef}>
431+
<Icon glyph="Sparkle" />
432+
</span>
433+
</button>
434+
</div>
339435
</div>
340-
<button
341-
className={closeAIButtonStyles}
342-
data-testid="close-ai-button"
343-
aria-label={closeText}
344-
title={closeText}
345-
onClick={() => onClose()}
346-
>
347-
<AIGuideCue
348-
showGuideCue={isAggregationGeneratedFromQuery}
349-
onCloseGuideCue={() => {
350-
onResetIsAggregationGeneratedFromQuery?.();
351-
}}
352-
refEl={guideCueRef}
353-
title="Aggregation generated"
354-
description="Your query requires stages from MongoDB's aggregation framework. Continue to work on it in our Aggregation Pipeline Builder"
355-
/>
356-
<span className={aiEntryContainerStyles} ref={guideCueRef}>
357-
<Icon glyph="Sparkle" />
358-
</span>
359-
</button>
360436
</div>
361437
{didSucceed && onSubmitFeedback && (
362438
<AIFeedback onSubmitFeedback={onSubmitFeedback} />

0 commit comments

Comments
 (0)