@gitlab/ui 66.32.1 → 66.34.0

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.
@@ -0,0 +1,216 @@
1
+ import GlButton from '../../../base/button/button.vue';
2
+ import GlAlert from '../../../base/alert/alert.vue';
3
+ import { setStoryTimeout } from '../../../../utils/test_utils';
4
+ import { makeContainer } from '../../../../utils/story_decorators/container';
5
+ import GlDuoChat from './duo_chat.vue';
6
+ import readme from './duo_chat.md';
7
+ import {
8
+ MOCK_RESPONSE_MESSAGE,
9
+ MOCK_USER_PROMPT_MESSAGE,
10
+ generateMockResponseChunks,
11
+ } from './mock_data';
12
+
13
+ const renderMarkdown = (content) => content;
14
+ const renderGFM = () => {};
15
+
16
+ const defaultValue = (prop) =>
17
+ typeof GlDuoChat.props[prop].default === 'function'
18
+ ? GlDuoChat.props[prop].default()
19
+ : GlDuoChat.props[prop].default;
20
+
21
+ const generateProps = ({
22
+ title = defaultValue('title'),
23
+ messages = defaultValue('messages'),
24
+ error = defaultValue('error'),
25
+ isLoading = defaultValue('isLoading'),
26
+ isChatAvailable = defaultValue('isChatAvailable'),
27
+ predefinedPrompts = defaultValue('predefinedPrompts'),
28
+ experimentHelpPageUrl = defaultValue('experimentHelpPageUrl'),
29
+ toolName = defaultValue('toolName'),
30
+ } = {}) => ({
31
+ title,
32
+ messages,
33
+ error,
34
+ isLoading,
35
+ isChatAvailable,
36
+ predefinedPrompts,
37
+ experimentHelpPageUrl,
38
+ toolName,
39
+ });
40
+
41
+ export const Default = (args, { argTypes }) => ({
42
+ components: { GlDuoChat },
43
+ props: Object.keys(argTypes),
44
+ provide: {
45
+ renderMarkdown,
46
+ renderGFM,
47
+ },
48
+ template: `
49
+ <gl-duo-chat
50
+ :title="title"
51
+ :messages="messages"
52
+ :error="error"
53
+ :is-loading="isLoading"
54
+ :is-chat-available="isChatAvailable"
55
+ :predefined-prompts="predefinedPrompts"
56
+ :experiment-help-page-url="experimentHelpPageUrl"
57
+ :tool-name="toolName"
58
+ />`,
59
+ });
60
+ Default.args = generateProps({
61
+ messages: [MOCK_USER_PROMPT_MESSAGE, MOCK_RESPONSE_MESSAGE],
62
+ });
63
+ Default.decorators = [makeContainer({ height: '800px' })];
64
+
65
+ export const Interactive = (args, { argTypes }) => ({
66
+ components: { GlDuoChat, GlButton },
67
+ props: Object.keys(argTypes),
68
+ provide: {
69
+ renderMarkdown,
70
+ renderGFM,
71
+ },
72
+ data() {
73
+ return {
74
+ isHidden: false,
75
+ loggerInfo: '',
76
+ promptInFlight: false,
77
+ msgs: args.messages,
78
+ chunks: [],
79
+ timeout: null,
80
+ requestId: 1,
81
+ };
82
+ },
83
+ methods: {
84
+ onSendChatPrompt(prompt) {
85
+ const newPrompt = {
86
+ ...MOCK_USER_PROMPT_MESSAGE,
87
+ contentHtml: '',
88
+ content: prompt,
89
+ requestId: this.requestId,
90
+ };
91
+ this.loggerInfo += `New prompt: ${JSON.stringify(newPrompt)}\n\n`;
92
+ this.msgs.push(newPrompt);
93
+ this.promptInFlight = true;
94
+ },
95
+ onChatHidden() {
96
+ this.isHidden = true;
97
+ this.loggerInfo += `Chat closed\n\n`;
98
+ },
99
+ showChat() {
100
+ this.isHidden = false;
101
+ this.loggerInfo += `Chat opened\n\n`;
102
+ },
103
+ onResponseRequested() {
104
+ this.timeout = null;
105
+ this.chunks = generateMockResponseChunks(this.requestId);
106
+ this.mockResponseFromAi();
107
+ this.requestId += 1;
108
+ },
109
+ mockResponseFromAi() {
110
+ this.promptInFlight = false;
111
+ if (this.chunks.length) {
112
+ const newResponse = this.chunks.shift();
113
+ const existingMessageIndex = this.msgs.findIndex(
114
+ (msg) => msg.requestId === newResponse.requestId && msg.role === newResponse.role
115
+ );
116
+ const existingMessage = this.msgs[existingMessageIndex];
117
+ if (existingMessage) {
118
+ this.msgs.splice(existingMessageIndex, 1, {
119
+ ...existingMessage,
120
+ content: existingMessage.content + newResponse.content,
121
+ });
122
+ } else {
123
+ this.msgs.push(newResponse);
124
+ }
125
+ this.logerInfo += `New response: ${JSON.stringify(newResponse)}\n\n`;
126
+ this.timeout = setStoryTimeout(() => {
127
+ this.mockResponseFromAi();
128
+ }, Math.floor(Math.random() * 251) + 50);
129
+ }
130
+ },
131
+ },
132
+ template: `
133
+ <div style="height: 800px">
134
+ <div id="logger" class="gl-w-half">
135
+ <pre class="gl-font-sm" style="text-wrap: wrap">
136
+ <code>{{ loggerInfo }}</code>
137
+ </pre>
138
+ <gl-button v-if="promptInFlight" @click="onResponseRequested">Mock the response</gl-button>
139
+ </div>
140
+ <gl-button v-if="isHidden" @click="showChat">Show chat</gl-button>
141
+ <gl-duo-chat
142
+ v-if="!isHidden"
143
+ :title="title"
144
+ :messages="msgs"
145
+ :error="error"
146
+ :is-loading="promptInFlight"
147
+ :is-chat-available="isChatAvailable"
148
+ :predefined-prompts="predefinedPrompts"
149
+ :experiment-help-page-url="experimentHelpPageUrl"
150
+ :tool-name="toolName"
151
+ @send-chat-prompt="onSendChatPrompt"
152
+ @chat-hidden="onChatHidden"
153
+ />
154
+ </div>`,
155
+ });
156
+ Interactive.args = generateProps({});
157
+
158
+ export const Slots = (args, { argTypes }) => ({
159
+ components: { GlDuoChat, GlAlert },
160
+ props: Object.keys(argTypes),
161
+ provide: {
162
+ renderMarkdown,
163
+ renderGFM,
164
+ },
165
+ template: `
166
+ <div>
167
+ <gl-duo-chat
168
+ :title="title"
169
+ :messages="messages"
170
+ :error="error"
171
+ :is-loading="isLoading"
172
+ :is-chat-available="isChatAvailable"
173
+ :predefined-prompts="predefinedPrompts"
174
+ :experiment-help-page-url="experimentHelpPageUrl"
175
+ :tool-name="toolName">
176
+
177
+ <template #hero>
178
+ <pre class="code-block rounded code highlight gl-border-b gl-rounded-0! gl-mb-0 gl-overflow-y-auto solarized-light" style="max-height: 20rem; overflow-y: auto;">
179
+ if (firstUserPromptIndex >= 0 && lastUserPromptIndex > firstUserPromptIndex) {
180
+ messages.splice(firstUserPromptIndex, 1);
181
+ return truncateChatPrompt(messages);
182
+ }
183
+ </pre>
184
+ </template>
185
+ <template #subheader>
186
+ <gl-alert
187
+ :dismissible="false"
188
+ variant="warning"
189
+ class="gl-font-sm gl-border-t"
190
+ role="alert"
191
+ data-testid="chat-legal-warning-gitlab-usage"
192
+ primary-button-link="https://internal-handbook.gitlab.io/handbook/product/ai-strategy/ai-integration-effort/legal_restrictions/"
193
+ primary-button-text="Read more"
194
+ >
195
+ <p>
196
+ You are not allowed to copy any part of this output into issues, comments, GitLab source code, commit messages, merge requests or any other user interface in the <code>/gitlab-org</code> or <code>/gitlab-com</code> groups.
197
+ </p>
198
+ </gl-alert>
199
+ </template>
200
+ </gl-duo-chat>
201
+ </div>
202
+ `,
203
+ });
204
+ Slots.decorators = [makeContainer({ height: '800px' })];
205
+
206
+ export default {
207
+ title: 'experimental/duo/chat/duo-chat',
208
+ component: GlDuoChat,
209
+ parameters: {
210
+ docs: {
211
+ description: {
212
+ component: readme,
213
+ },
214
+ },
215
+ },
216
+ };
@@ -0,0 +1,372 @@
1
+ <script>
2
+ import throttle from 'lodash/throttle';
3
+ import emptySvg from '@gitlab/svgs/dist/illustrations/empty-state/empty-activity-md.svg';
4
+ import GlEmptyState from '../../../regions/empty_state/empty_state.vue';
5
+ import GlButton from '../../../base/button/button.vue';
6
+ import GlAlert from '../../../base/alert/alert.vue';
7
+ import GlFormInputGroup from '../../../base/form/form_input_group/form_input_group.vue';
8
+ import GlFormTextarea from '../../../base/form/form_textarea/form_textarea.vue';
9
+ import GlForm from '../../../base/form/form.vue';
10
+ import GlFormText from '../../../base/form/form_text/form_text.vue';
11
+ import GlExperimentBadge from '../../experiment_badge/experiment_badge.vue';
12
+ import { SafeHtmlDirective as SafeHtml } from '../../../../directives/safe_html/safe_html';
13
+ import GlDuoChatLoader from './components/duo_chat_loader/duo_chat_loader.vue';
14
+ import GlDuoChatPredefinedPrompts from './components/duo_chat_predefined_prompts/duo_chat_predefined_prompts.vue';
15
+ import GlDuoChatConversation from './components/duo_chat_conversation/duo_chat_conversation.vue';
16
+ import { CHAT_RESET_MESSAGE } from './constants';
17
+
18
+ export const i18n = {
19
+ CHAT_DEFAULT_TITLE: 'GitLab Duo Chat',
20
+ CHAT_CLOSE_LABEL: 'Close the Code Explanation',
21
+ CHAT_LEGAL_GENERATED_BY_AI: 'Responses generated by AI',
22
+ CHAT_EMPTY_STATE_TITLE: 'Ask a question',
23
+ CHAT_EMPTY_STATE_DESC: 'AI generated explanations will appear here.',
24
+ CHAT_PROMPT_PLACEHOLDER: 'GitLab Duo Chat',
25
+ CHAT_SUBMIT_LABEL: 'Send chat message.',
26
+ CHAT_LEGAL_DISCLAIMER:
27
+ "May provide inappropriate responses not representative of GitLab's views. Do not input personal data.",
28
+ CHAT_DEFAULT_PREDEFINED_PROMPTS: [
29
+ 'How do I change my password in GitLab?',
30
+ 'How do I fork a project?',
31
+ 'How do I clone a repository?',
32
+ 'How do I create a template?',
33
+ ],
34
+ };
35
+
36
+ const isMessage = (item) => Boolean(item) && item?.role;
37
+
38
+ const itemsValidator = (items) => items.every(isMessage);
39
+
40
+ export default {
41
+ name: 'GlDuoChat',
42
+ components: {
43
+ GlEmptyState,
44
+ GlButton,
45
+ GlAlert,
46
+ GlFormInputGroup,
47
+ GlFormTextarea,
48
+ GlForm,
49
+ GlFormText,
50
+ GlExperimentBadge,
51
+ GlDuoChatLoader,
52
+ GlDuoChatPredefinedPrompts,
53
+ GlDuoChatConversation,
54
+ },
55
+ directives: {
56
+ SafeHtml,
57
+ },
58
+ props: {
59
+ /**
60
+ * The title of the chat/feature.
61
+ */
62
+ title: {
63
+ type: String,
64
+ required: false,
65
+ default: i18n.CHAT_DEFAULT_TITLE,
66
+ },
67
+ /**
68
+ * Array of messages to display in the chat.
69
+ */
70
+ messages: {
71
+ type: Array,
72
+ required: false,
73
+ default: () => [],
74
+ validator: itemsValidator,
75
+ },
76
+ /**
77
+ * A non-recoverable error message to display in the chat.
78
+ */
79
+ error: {
80
+ type: String,
81
+ required: false,
82
+ default: '',
83
+ },
84
+ /**
85
+ * Whether the chat is currently fetching a response from AI.
86
+ */
87
+ isLoading: {
88
+ type: Boolean,
89
+ required: false,
90
+ default: false,
91
+ },
92
+ /**
93
+ * Whether the conversational interfaces should be enabled.
94
+ */
95
+ isChatAvailable: {
96
+ type: Boolean,
97
+ required: false,
98
+ default: true,
99
+ },
100
+ /**
101
+ * Array of predefined prompts to display in the chat to start a conversation.
102
+ */
103
+ predefinedPrompts: {
104
+ type: Array,
105
+ required: false,
106
+ default: () => i18n.CHAT_DEFAULT_PREDEFINED_PROMPTS,
107
+ },
108
+ /**
109
+ * URL to the experiment help page. This is passed down to the `GlExperimentBadge` component. Refer that component for more information.
110
+ */
111
+ experimentHelpPageUrl: {
112
+ type: String,
113
+ required: false,
114
+ default: '',
115
+ },
116
+ /**
117
+ * The current tool's name to display in the loading message while waiting for a response from AI. Refer the `GlDuoChatLoader` component for more information.
118
+ */
119
+ toolName: {
120
+ type: String,
121
+ required: false,
122
+ default: i18n.CHAT_DEFAULT_TITLE,
123
+ },
124
+ },
125
+ data() {
126
+ return {
127
+ isHidden: false,
128
+ prompt: '',
129
+ scrolledToBottom: true,
130
+ };
131
+ },
132
+ computed: {
133
+ hasMessages() {
134
+ return this.messages?.length > 0;
135
+ },
136
+ conversations() {
137
+ if (!this.hasMessages) return [];
138
+
139
+ return this.messages.reduce(
140
+ (acc, message) => {
141
+ if (message.content === CHAT_RESET_MESSAGE) {
142
+ acc.push([]);
143
+ } else {
144
+ acc[acc.length - 1].push(message);
145
+ }
146
+ return acc;
147
+ },
148
+ [[]]
149
+ );
150
+ },
151
+ resetDisabled() {
152
+ if (this.isLoading || !this.hasMessages) {
153
+ return true;
154
+ }
155
+
156
+ const lastMessage = this.messages[this.messages.length - 1];
157
+ return lastMessage.content === CHAT_RESET_MESSAGE;
158
+ },
159
+ },
160
+ watch: {
161
+ isLoading() {
162
+ this.isHidden = false;
163
+ this.scrollToBottom();
164
+ },
165
+ messages() {
166
+ this.prompt = '';
167
+ },
168
+ },
169
+ created() {
170
+ this.handleScrollingTrottled = throttle(this.handleScrolling, 200); // Assume a 200ms throttle for example
171
+ },
172
+ mounted() {
173
+ this.scrollToBottom();
174
+ },
175
+ methods: {
176
+ hideChat() {
177
+ this.isHidden = true;
178
+ /**
179
+ * Emitted when clicking the cross in the title and the chat gets closed.
180
+ */
181
+ this.$emit('chat-hidden');
182
+ },
183
+ sendChatPrompt() {
184
+ if (this.prompt) {
185
+ if (this.prompt === CHAT_RESET_MESSAGE && this.resetDisabled) {
186
+ return;
187
+ }
188
+ /**
189
+ * Emitted when a new user prompt should be sent out.
190
+ *
191
+ * @param {String} prompt The user prompt to send.
192
+ */
193
+ this.$emit('send-chat-prompt', this.prompt);
194
+ }
195
+ },
196
+ sendPredefinedPrompt(prompt) {
197
+ this.prompt = prompt;
198
+ this.sendChatPrompt();
199
+ },
200
+ handleScrolling() {
201
+ const { scrollTop, offsetHeight, scrollHeight } = this.$refs.drawer;
202
+ this.scrolledToBottom = scrollTop + offsetHeight >= scrollHeight;
203
+ },
204
+ async scrollToBottom() {
205
+ await this.$nextTick();
206
+
207
+ if (this.$refs.drawer) {
208
+ this.$refs.drawer.scrollTop = this.$refs.drawer.scrollHeight;
209
+ }
210
+ },
211
+ onTrackFeedback(event) {
212
+ /**
213
+ * Notify listeners about the feedback form submission on a response message.
214
+ * @param {*} event An event, containing the feedback choices and the extended feedback text.
215
+ */
216
+ this.$emit('track-feedback', event);
217
+ },
218
+ },
219
+ i18n,
220
+ emptySvg,
221
+ };
222
+ </script>
223
+ <template>
224
+ <aside
225
+ v-if="!isHidden"
226
+ id="chat-component"
227
+ ref="drawer"
228
+ class="markdown-code-block gl-drawer gl-drawer-default gl-max-h-full gl-bottom-0 gl-shadow-none gl-border-l gl-border-t duo-chat gl-h-auto"
229
+ role="complementary"
230
+ data-testid="chat-component"
231
+ @scroll="handleScrollingTrottled"
232
+ >
233
+ <header class="gl-drawer-header gl-drawer-header-sticky gl-z-index-200 gl-p-0! gl-border-b-0">
234
+ <div
235
+ class="drawer-title gl-display-flex gl-justify-content-start gl-align-items-center gl-p-5"
236
+ >
237
+ <h3 class="gl-my-0 gl-font-size-h2">{{ title }}</h3>
238
+ <gl-experiment-badge
239
+ :experiment-help-page-url="experimentHelpPageUrl"
240
+ container-id="chat-component"
241
+ />
242
+ <gl-button
243
+ category="tertiary"
244
+ variant="default"
245
+ icon="close"
246
+ size="small"
247
+ class="gl-p-0! gl-ml-auto"
248
+ data-testid="chat-close-button"
249
+ :aria-label="$options.i18n.CHAT_CLOSE_LABEL"
250
+ @click="hideChat"
251
+ />
252
+ </div>
253
+
254
+ <gl-alert
255
+ :dismissible="false"
256
+ variant="tip"
257
+ :show-icon="false"
258
+ class="gl-text-center gl-border-t gl-p-4 gl-text-gray-700 gl-bg-gray-50 legal-warning gl-max-w-full"
259
+ role="alert"
260
+ data-testid="chat-legal-warning"
261
+ >{{ $options.i18n.CHAT_LEGAL_GENERATED_BY_AI }}</gl-alert
262
+ >
263
+
264
+ <!--
265
+ @slot Subheader to be rendered right after the title. It is sticky and stays on top of the chat no matter the number of messages.
266
+ -->
267
+ <slot name="subheader"></slot>
268
+ </header>
269
+
270
+ <div class="gl-drawer-body gl-display-flex gl-flex-direction-column">
271
+ <!-- @slot 'Hero' information to be rendered at the top of the chat before any message. It gets pushed away from the view by incomming messages
272
+ -->
273
+ <slot name="hero"></slot>
274
+
275
+ <gl-alert
276
+ v-if="error"
277
+ key="error"
278
+ :dismissible="false"
279
+ variant="danger"
280
+ class="gl-mb-0 gl-pl-9!"
281
+ role="alert"
282
+ data-testid="chat-error"
283
+ ><span v-safe-html="error"></span
284
+ ></gl-alert>
285
+
286
+ <section
287
+ class="gl-display-flex gl-flex-direction-column gl-justify-content-end gl-flex-grow-1 gl-border-b-0 gl-bg-gray-10"
288
+ >
289
+ <transition-group
290
+ tag="div"
291
+ name="message"
292
+ class="gl-display-flex gl-flex-direction-column gl-justify-content-end"
293
+ :class="[
294
+ {
295
+ 'gl-h-full': !hasMessages,
296
+ 'gl-h-auto': hasMessages,
297
+ },
298
+ ]"
299
+ >
300
+ <gl-duo-chat-conversation
301
+ v-for="(conversation, index) in conversations"
302
+ :key="`conversation-${index}`"
303
+ :messages="conversation"
304
+ :show-delimiter="index > 0"
305
+ @track-feedback="onTrackFeedback"
306
+ />
307
+ <template v-if="!hasMessages && !isLoading">
308
+ <div key="empty-state" class="gl-display-flex gl-flex-grow-1 gl-mr-auto gl-ml-auto">
309
+ <gl-empty-state
310
+ :svg-path="$options.emptySvg"
311
+ :svg-height="145"
312
+ :title="$options.i18n.CHAT_EMPTY_STATE_TITLE"
313
+ :description="$options.i18n.CHAT_EMPTY_STATE_DESC"
314
+ class="gl-align-self-center"
315
+ />
316
+ </div>
317
+ <gl-duo-chat-predefined-prompts
318
+ key="predefined-prompts"
319
+ :prompts="predefinedPrompts"
320
+ @click="sendPredefinedPrompt"
321
+ />
322
+ </template>
323
+ </transition-group>
324
+ <transition name="loader">
325
+ <gl-duo-chat-loader v-if="isLoading" :tool-name="toolName" />
326
+ </transition>
327
+ </section>
328
+ </div>
329
+ <footer
330
+ v-if="isChatAvailable"
331
+ data-testid="chat-footer"
332
+ class="gl-drawer-footer gl-drawer-footer-sticky gl-p-5 gl-border-t gl-bg-gray-10"
333
+ :class="{ 'gl-drawer-body-scrim-on-footer': !scrolledToBottom }"
334
+ >
335
+ <gl-form data-testid="chat-prompt-form" @submit.stop.prevent="sendChatPrompt">
336
+ <gl-form-input-group>
337
+ <div
338
+ class="duo-chat-input gl-flex-grow-1 gl-vertical-align-top gl-max-w-full gl-min-h-8 gl-inset-border-1-gray-400 gl-rounded-base gl-bg-white"
339
+ :data-value="prompt"
340
+ >
341
+ <gl-form-textarea
342
+ v-model="prompt"
343
+ data-testid="chat-prompt-input"
344
+ class="gl-absolute gl-h-full! gl-py-4! gl-bg-transparent! gl-rounded-top-right-none gl-rounded-bottom-right-none gl-shadow-none!"
345
+ :class="{ 'gl-text-truncate': !prompt }"
346
+ :placeholder="$options.i18n.CHAT_PROMPT_PLACEHOLDER"
347
+ :disabled="isLoading"
348
+ autofocus
349
+ @keydown.enter.exact.native.prevent="sendChatPrompt"
350
+ />
351
+ </div>
352
+ <template #append>
353
+ <gl-button
354
+ icon="paper-airplane"
355
+ category="primary"
356
+ variant="confirm"
357
+ class="gl-absolute! gl-bottom-2 gl-right-2 gl-rounded-base!"
358
+ type="submit"
359
+ :aria-label="$options.i18n.CHAT_SUBMIT_LABEL"
360
+ :disabled="isLoading"
361
+ />
362
+ </template>
363
+ </gl-form-input-group>
364
+ <gl-form-text
365
+ class="gl-text-gray-400 gl-line-height-20 gl-mt-3"
366
+ data-testid="chat-legal-disclaimer"
367
+ >{{ $options.i18n.CHAT_LEGAL_DISCLAIMER }}</gl-form-text
368
+ >
369
+ </gl-form>
370
+ </footer>
371
+ </aside>
372
+ </template>
@@ -65,6 +65,25 @@ export const MOCK_RESPONSE_MESSAGE_FOR_STREAMING = {
65
65
  timestamp: '2021-04-21T12:00:00.000Z',
66
66
  };
67
67
 
68
+ export const generateMockResponseChunks = (requestId) => {
69
+ const chunks = [];
70
+ const chunkSize = 5;
71
+ const chunkCount = Math.ceil(MOCK_RESPONSE_MESSAGE_FOR_STREAMING.content.length / chunkSize);
72
+ for (let i = 0; i < chunkCount; i += 1) {
73
+ const chunk = {
74
+ ...MOCK_RESPONSE_MESSAGE_FOR_STREAMING,
75
+ requestId,
76
+ content: MOCK_RESPONSE_MESSAGE_FOR_STREAMING.content.substring(
77
+ i * chunkSize,
78
+ (i + 1) * chunkSize
79
+ ),
80
+ chunkId: i,
81
+ };
82
+ chunks.push(chunk);
83
+ }
84
+ return chunks;
85
+ };
86
+
68
87
  export const MOCK_USER_PROMPT_MESSAGE = {
69
88
  id: '456',
70
89
  content: 'How to create a new template?',
package/src/index.js CHANGED
@@ -99,6 +99,7 @@ export { default as GlCarouselSlide } from './components/base/carousel/carousel_
99
99
  // Experimental
100
100
  export { default as GlExperimentBadge } from './components/experimental/experiment_badge/experiment_badge.vue';
101
101
  export { default as GlDuoUserFeedback } from './components/experimental/duo/user_feedback/user_feedback.vue';
102
+ export { default as GlDuoChat } from './components/experimental/duo/chat/duo_chat.vue';
102
103
 
103
104
  // Utilities
104
105
  export { default as GlAnimatedNumber } from './components/utilities/animated_number/animated_number.vue';
@@ -3,6 +3,7 @@
3
3
  // @import '../components/base/dropdown/dropdown'
4
4
  //
5
5
  // ADD COMPONENT IMPORTS - needed for yarn generate:component. Do not remove
6
+ @import '../components/experimental/duo/chat/duo_chat';
6
7
  @import '../components/experimental/duo/chat/components/duo_chat_message/duo_chat_message';
7
8
  @import '../components/experimental/duo/chat/components/duo_chat_loader/duo_chat_loader';
8
9
  @import '../components/base/new_dropdowns/disclosure/disclosure_dropdown';
@@ -9305,6 +9305,14 @@ $gl-animate-skeleton-loader-max-width: 64 * $grid-size;
9305
9305
  z-index: 200 !important
9306
9306
  }
9307
9307
 
9308
+ .gl-z-index-999 {
9309
+ z-index: 999
9310
+ }
9311
+
9312
+ .gl-z-index-999\! {
9313
+ z-index: 999 !important
9314
+ }
9315
+
9308
9316
  .gl-z-index-9999 {
9309
9317
  z-index: 9999
9310
9318
  }
@@ -24,6 +24,10 @@
24
24
  z-index: 200;
25
25
  }
26
26
 
27
+ @mixin gl-z-index-999 {
28
+ z-index: 999;
29
+ }
30
+
27
31
  @mixin gl-z-index-9999 {
28
32
  z-index: 9999;
29
33
  }