@gitlab/ui 104.2.0 → 105.0.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.
Files changed (82) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/index.css +1 -1
  3. package/dist/index.css.map +1 -1
  4. package/dist/index.js +0 -5
  5. package/dist/tailwind.css +1 -1
  6. package/dist/tailwind.css.map +1 -1
  7. package/package.json +1 -3
  8. package/src/index.js +0 -5
  9. package/src/scss/components.scss +0 -3
  10. package/translations.js +0 -57
  11. package/dist/components/experimental/duo/chat/components/duo_chat_context/constants.js +0 -21
  12. package/dist/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_details_modal/duo_chat_context_item_details_modal.js +0 -159
  13. package/dist/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu.js +0 -273
  14. package/dist/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu_category_items.js +0 -77
  15. package/dist/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu_search_item.js +0 -89
  16. package/dist/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu_search_items.js +0 -147
  17. package/dist/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu_search_items_loading.js +0 -61
  18. package/dist/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_popover/duo_chat_context_item_popover.js +0 -137
  19. package/dist/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections.js +0 -163
  20. package/dist/components/experimental/duo/chat/components/duo_chat_context/mock_context_data.js +0 -308
  21. package/dist/components/experimental/duo/chat/components/duo_chat_context/utils.js +0 -140
  22. package/dist/components/experimental/duo/chat/components/duo_chat_conversation/duo_chat_conversation.js +0 -109
  23. package/dist/components/experimental/duo/chat/components/duo_chat_loader/duo_chat_loader.js +0 -111
  24. package/dist/components/experimental/duo/chat/components/duo_chat_message/buttons_utils.js +0 -33
  25. package/dist/components/experimental/duo/chat/components/duo_chat_message/constants.js +0 -14
  26. package/dist/components/experimental/duo/chat/components/duo_chat_message/copy_code_element.js +0 -24
  27. package/dist/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.js +0 -300
  28. package/dist/components/experimental/duo/chat/components/duo_chat_message/insert_code_snippet_element.js +0 -56
  29. package/dist/components/experimental/duo/chat/components/duo_chat_message/utils.js +0 -17
  30. package/dist/components/experimental/duo/chat/components/duo_chat_message_sources/duo_chat_message_sources.js +0 -115
  31. package/dist/components/experimental/duo/chat/components/duo_chat_predefined_prompts/duo_chat_predefined_prompts.js +0 -72
  32. package/dist/components/experimental/duo/chat/constants.js +0 -35
  33. package/dist/components/experimental/duo/chat/duo_chat.js +0 -553
  34. package/dist/components/experimental/duo/chat/markdown_renderer.js +0 -25
  35. package/dist/components/experimental/duo/chat/mock_data.js +0 -170
  36. package/dist/components/experimental/duo/user_feedback/user_feedback.js +0 -106
  37. package/dist/components/experimental/duo/user_feedback/user_feedback_modal.js +0 -154
  38. package/dist/components/experimental/duo/workflow/components/duo_workflow_panel/duo_workflow_panel.js +0 -106
  39. package/dist/components/experimental/duo/workflow/components/duo_workflow_prompt/duo_workflow_prompt.js +0 -246
  40. package/src/components/experimental/duo/chat/components/duo_chat_context/constants.js +0 -21
  41. package/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_details_modal/duo_chat_context_item_details_modal.vue +0 -182
  42. package/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu.md +0 -44
  43. package/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu.vue +0 -288
  44. package/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu_category_items.vue +0 -54
  45. package/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu_search_item.vue +0 -86
  46. package/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu_search_items.vue +0 -168
  47. package/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu_search_items_loading.vue +0 -43
  48. package/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_popover/duo_chat_context_item_popover.vue +0 -170
  49. package/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections.vue +0 -196
  50. package/src/components/experimental/duo/chat/components/duo_chat_context/mock_context_data.js +0 -362
  51. package/src/components/experimental/duo/chat/components/duo_chat_context/utils.js +0 -169
  52. package/src/components/experimental/duo/chat/components/duo_chat_conversation/duo_chat_conversation.md +0 -27
  53. package/src/components/experimental/duo/chat/components/duo_chat_conversation/duo_chat_conversation.vue +0 -99
  54. package/src/components/experimental/duo/chat/components/duo_chat_loader/duo_chat_loader.md +0 -10
  55. package/src/components/experimental/duo/chat/components/duo_chat_loader/duo_chat_loader.scss +0 -44
  56. package/src/components/experimental/duo/chat/components/duo_chat_loader/duo_chat_loader.vue +0 -112
  57. package/src/components/experimental/duo/chat/components/duo_chat_message/buttons_utils.js +0 -39
  58. package/src/components/experimental/duo/chat/components/duo_chat_message/constants.js +0 -12
  59. package/src/components/experimental/duo/chat/components/duo_chat_message/copy_code_element.js +0 -24
  60. package/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.md +0 -69
  61. package/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.scss +0 -105
  62. package/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.vue +0 -363
  63. package/src/components/experimental/duo/chat/components/duo_chat_message/insert_code_snippet_element.js +0 -51
  64. package/src/components/experimental/duo/chat/components/duo_chat_message/utils.js +0 -18
  65. package/src/components/experimental/duo/chat/components/duo_chat_message_sources/duo_chat_message_sources.md +0 -10
  66. package/src/components/experimental/duo/chat/components/duo_chat_message_sources/duo_chat_message_sources.vue +0 -91
  67. package/src/components/experimental/duo/chat/components/duo_chat_predefined_prompts/duo_chat_predefined_prompts.md +0 -10
  68. package/src/components/experimental/duo/chat/components/duo_chat_predefined_prompts/duo_chat_predefined_prompts.vue +0 -45
  69. package/src/components/experimental/duo/chat/constants.js +0 -37
  70. package/src/components/experimental/duo/chat/duo_chat.md +0 -202
  71. package/src/components/experimental/duo/chat/duo_chat.scss +0 -413
  72. package/src/components/experimental/duo/chat/duo_chat.vue +0 -751
  73. package/src/components/experimental/duo/chat/markdown_renderer.js +0 -29
  74. package/src/components/experimental/duo/chat/mock_data.js +0 -187
  75. package/src/components/experimental/duo/chat/variables.scss +0 -9
  76. package/src/components/experimental/duo/user_feedback/user_feedback.md +0 -90
  77. package/src/components/experimental/duo/user_feedback/user_feedback.vue +0 -95
  78. package/src/components/experimental/duo/user_feedback/user_feedback_modal.vue +0 -167
  79. package/src/components/experimental/duo/workflow/components/duo_workflow_panel/duo_workflow_panel.md +0 -42
  80. package/src/components/experimental/duo/workflow/components/duo_workflow_panel/duo_workflow_panel.vue +0 -96
  81. package/src/components/experimental/duo/workflow/components/duo_workflow_prompt/duo_workflow_prompt.md +0 -46
  82. package/src/components/experimental/duo/workflow/components/duo_workflow_prompt/duo_workflow_prompt.vue +0 -270
@@ -1,751 +0,0 @@
1
- <script>
2
- /**
3
- * This component has been migrated to the Duo-UI library (https://gitlab.com/gitlab-org/duo-ui).
4
- *
5
- * Please use the corresponding component in Duo-UI going forward.
6
- * All future development and maintenance for Duo components should take place in Duo-UI.
7
- *
8
- * For more details, see the migration epic: https://gitlab.com/groups/gitlab-org/-/epics/15344 or reach out to the Duo-Chat team in #g_duo_chat.
9
- */
10
-
11
- import throttle from 'lodash/throttle';
12
- import emptySvg from '@gitlab/svgs/dist/illustrations/empty-state/empty-activity-md.svg';
13
- import GlDropdownItem from '../../../base/dropdown/dropdown_item.vue';
14
- import GlCard from '../../../base/card/card.vue';
15
- import GlEmptyState from '../../../regions/empty_state/empty_state.vue';
16
- import GlButton from '../../../base/button/button.vue';
17
- import GlAlert from '../../../base/alert/alert.vue';
18
- import GlFormInputGroup from '../../../base/form/form_input_group/form_input_group.vue';
19
- import GlFormTextarea from '../../../base/form/form_textarea/form_textarea.vue';
20
- import GlForm from '../../../base/form/form.vue';
21
- import GlExperimentBadge from '../../experiment_badge/experiment_badge.vue';
22
- import { badgeTypes, badgeTypeValidator } from '../../experiment_badge/constants';
23
- import { SafeHtmlDirective as SafeHtml } from '../../../../directives/safe_html/safe_html';
24
- import { translate } from '../../../../utils/i18n';
25
- import GlDuoChatLoader from './components/duo_chat_loader/duo_chat_loader.vue';
26
- import GlDuoChatPredefinedPrompts from './components/duo_chat_predefined_prompts/duo_chat_predefined_prompts.vue';
27
- import GlDuoChatConversation from './components/duo_chat_conversation/duo_chat_conversation.vue';
28
- import {
29
- CHAT_RESET_MESSAGE,
30
- CHAT_CLEAR_MESSAGE,
31
- CHAT_INCLUDE_MESSAGE,
32
- MESSAGE_MODEL_ROLES,
33
- } from './constants';
34
-
35
- export const i18n = {
36
- CHAT_DEFAULT_TITLE: translate('GlDuoChat.chatDefaultTitle', 'GitLab Duo Chat'),
37
- CHAT_CLOSE_LABEL: translate('GlDuoChat.chatCloseLabel', 'Close the Code Explanation'),
38
- CHAT_EMPTY_STATE_TITLE: translate('GlDuoChat.chatEmptyStateTitle', 'Ask a question'),
39
- CHAT_EMPTY_STATE_DESC: translate(
40
- 'GlDuoChat.chatEmptyStateDesc',
41
- 'GitLab Duo Chat is your AI-powered assistant.'
42
- ),
43
- CHAT_EMPTY_STATE_SECONDARY_DESC: translate(
44
- 'GlDuoChat.chatEmptyStateSecondaryDesc',
45
- 'Responses may be inaccurate. Verify before use.'
46
- ),
47
- CHAT_PROMPT_PLACEHOLDER_DEFAULT: translate(
48
- 'GlDuoChat.chatPromptPlaceholderDefault',
49
- 'GitLab Duo Chat'
50
- ),
51
- CHAT_PROMPT_PLACEHOLDER_WITH_COMMANDS: translate(
52
- 'GlDuoChat.chatPromptPlaceholderWithCommands',
53
- 'Type /help to learn more'
54
- ),
55
- CHAT_SUBMIT_LABEL: translate('GlDuoChat.chatSubmitLabel', 'Send chat message.'),
56
- CHAT_CANCEL_LABEL: translate('GlDuoChat.chatCancelLabel', 'Cancel'),
57
- CHAT_DEFAULT_PREDEFINED_PROMPTS: [
58
- translate(
59
- 'GlDuoChat.chatDefaultPredefinedPromptsChangePassword',
60
- 'How do I change my password in GitLab?'
61
- ),
62
- translate('GlDuoChat.chatDefaultPredefinedPromptsForkProject', 'How do I fork a project?'),
63
- translate(
64
- 'GlDuoChat.chatDefaultPredefinedPromptsCloneRepository',
65
- 'How do I clone a repository?'
66
- ),
67
- translate(
68
- 'GlDuoChat.chatDefaultPredefinedPromptsCreateTemplate',
69
- 'How do I create a template?'
70
- ),
71
- ],
72
- };
73
-
74
- const isMessage = (item) => Boolean(item) && item?.role;
75
- const isSlashCommand = (command) => Boolean(command) && command?.name && command.description;
76
-
77
- // eslint-disable-next-line unicorn/no-array-callback-reference
78
- const itemsValidator = (items) => items.every(isMessage);
79
- // eslint-disable-next-line unicorn/no-array-callback-reference
80
- const slashCommandsValidator = (commands) => commands.every(isSlashCommand);
81
-
82
- export default {
83
- name: 'GlDuoChat',
84
- components: {
85
- GlEmptyState,
86
- GlButton,
87
- GlAlert,
88
- GlFormInputGroup,
89
- GlFormTextarea,
90
- GlForm,
91
- GlExperimentBadge,
92
- GlDuoChatLoader,
93
- GlDuoChatPredefinedPrompts,
94
- GlDuoChatConversation,
95
- GlCard,
96
- GlDropdownItem,
97
- },
98
- directives: {
99
- SafeHtml,
100
- },
101
- props: {
102
- /**
103
- * The title of the chat/feature.
104
- */
105
- title: {
106
- type: String,
107
- required: false,
108
- default: i18n.CHAT_DEFAULT_TITLE,
109
- },
110
- /**
111
- * Array of messages to display in the chat.
112
- */
113
- messages: {
114
- type: Array,
115
- required: false,
116
- default: () => [],
117
- validator: itemsValidator,
118
- },
119
- /**
120
- * Array of RequestIds that have been canceled.
121
- */
122
- canceledRequestIds: {
123
- type: Array,
124
- required: false,
125
- default: () => [],
126
- },
127
- /**
128
- * A non-recoverable error message to display in the chat.
129
- */
130
- error: {
131
- type: String,
132
- required: false,
133
- default: '',
134
- },
135
- /**
136
- * Whether the chat is currently fetching a response from AI.
137
- */
138
- isLoading: {
139
- type: Boolean,
140
- required: false,
141
- default: false,
142
- },
143
- /**
144
- * Whether the conversational interfaces should be enabled.
145
- */
146
- isChatAvailable: {
147
- type: Boolean,
148
- required: false,
149
- default: true,
150
- },
151
- /**
152
- * Whether the insertCode feature should be available.
153
- */
154
- enableCodeInsertion: {
155
- type: Boolean,
156
- required: false,
157
- default: false,
158
- },
159
- /**
160
- * Array of predefined prompts to display in the chat to start a conversation.
161
- */
162
- predefinedPrompts: {
163
- type: Array,
164
- required: false,
165
- default: () => i18n.CHAT_DEFAULT_PREDEFINED_PROMPTS,
166
- },
167
- /**
168
- * URL to the help page. This is passed down to the `GlExperimentBadge` component.
169
- */
170
- badgeHelpPageUrl: {
171
- type: String,
172
- required: false,
173
- default: '',
174
- },
175
- /**
176
- * The type of the badge. This is passed down to the `GlExperimentBadge` component. Refer that component for more information.
177
- */
178
- badgeType: {
179
- type: String || null,
180
- required: false,
181
- default: badgeTypes[0],
182
- validator: badgeTypeValidator,
183
- },
184
- /**
185
- * 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.
186
- */
187
- toolName: {
188
- type: String,
189
- required: false,
190
- default: i18n.CHAT_DEFAULT_TITLE,
191
- },
192
- /**
193
- * Array of slash commands to display in the chat.
194
- */
195
- slashCommands: {
196
- type: Array,
197
- required: false,
198
- default: () => [],
199
- validator: slashCommandsValidator,
200
- },
201
- /**
202
- * Whether the header should be displayed.
203
- */
204
- showHeader: {
205
- type: Boolean,
206
- required: false,
207
- default: true,
208
- },
209
- /**
210
- * Override the default empty state title text.
211
- */
212
- emptyStateTitle: {
213
- type: String,
214
- required: false,
215
- default: i18n.CHAT_EMPTY_STATE_TITLE,
216
- },
217
- /**
218
- * Override the default empty state description text.
219
- */
220
- emptyStateDescription: {
221
- type: String,
222
- required: false,
223
- default: i18n.CHAT_EMPTY_STATE_DESC,
224
- },
225
- /**
226
- * Override the default empty state description secondary text.
227
- */
228
- emptyStateSecondaryDescription: {
229
- type: String,
230
- required: false,
231
- default: i18n.CHAT_EMPTY_STATE_SECONDARY_DESC,
232
- },
233
- /**
234
- * Override the default chat prompt placeholder text.
235
- */
236
- chatPromptPlaceholder: {
237
- type: String,
238
- required: false,
239
- default: '',
240
- },
241
- },
242
- data() {
243
- return {
244
- isHidden: false,
245
- prompt: '',
246
- scrolledToBottom: true,
247
- activeCommandIndex: 0,
248
- displaySubmitButton: true,
249
- compositionJustEnded: false,
250
- contextItemsMenuIsOpen: false,
251
- contextItemMenuRef: null,
252
- };
253
- },
254
- computed: {
255
- withSlashCommands() {
256
- return this.slashCommands.length > 0;
257
- },
258
- hasMessages() {
259
- return this.messages?.length > 0;
260
- },
261
- conversations() {
262
- if (!this.hasMessages) return [];
263
-
264
- return this.messages.reduce(
265
- (acc, message) => {
266
- if (message.content === CHAT_RESET_MESSAGE) {
267
- acc.push([]);
268
- } else {
269
- acc[acc.length - 1].push(message);
270
- }
271
- return acc;
272
- },
273
- [[]]
274
- );
275
- },
276
- lastMessage() {
277
- return this.messages?.[this.messages.length - 1];
278
- },
279
- caseInsensitivePrompt() {
280
- return this.prompt.toLowerCase().trim();
281
- },
282
- resetDisabled() {
283
- if (this.isLoading || !this.hasMessages) {
284
- return true;
285
- }
286
- return this.lastMessage?.content === CHAT_RESET_MESSAGE;
287
- },
288
- isStreaming() {
289
- if (this.canceledRequestIds.includes(this.lastMessage?.requestId)) {
290
- return false;
291
- }
292
- return Boolean(
293
- (this.lastMessage?.chunks?.length > 0 && !this.lastMessage?.content) ||
294
- typeof this.lastMessage?.chunkId === 'number'
295
- );
296
- },
297
- filteredSlashCommands() {
298
- return this.slashCommands
299
- .filter((c) => c.name.toLowerCase().startsWith(this.caseInsensitivePrompt))
300
- .filter((c) => {
301
- if (c.name === CHAT_INCLUDE_MESSAGE) {
302
- return this.hasContextItemSelectionMenu;
303
- }
304
- return true;
305
- });
306
- },
307
- shouldShowSlashCommands() {
308
- if (!this.withSlashCommands || this.contextItemsMenuIsOpen) return false;
309
- const startsWithSlash = this.caseInsensitivePrompt.startsWith('/');
310
- const startsWithSlashCommand = this.slashCommands.some((c) =>
311
- this.caseInsensitivePrompt.startsWith(c.name)
312
- );
313
- return startsWithSlash && this.filteredSlashCommands.length && !startsWithSlashCommand;
314
- },
315
- shouldShowContextItemSelectionMenu() {
316
- if (!this.hasContextItemSelectionMenu) {
317
- return false;
318
- }
319
-
320
- const isSlash = this.caseInsensitivePrompt === '/';
321
- if (!this.caseInsensitivePrompt || isSlash) {
322
- // if user has removed entire command (or whole command except for '/') we should close context item menu and allow slash command menu to show again
323
- return false;
324
- }
325
-
326
- return CHAT_INCLUDE_MESSAGE.startsWith(this.caseInsensitivePrompt);
327
- },
328
- inputPlaceholder() {
329
- if (this.chatPromptPlaceholder) {
330
- return this.chatPromptPlaceholder;
331
- }
332
-
333
- return this.withSlashCommands
334
- ? i18n.CHAT_PROMPT_PLACEHOLDER_WITH_COMMANDS
335
- : i18n.CHAT_PROMPT_PLACEHOLDER_DEFAULT;
336
- },
337
- hasContextItemSelectionMenu() {
338
- return Boolean(this.contextItemMenuRef);
339
- },
340
- },
341
- watch: {
342
- isLoading(newVal) {
343
- if (!newVal && !this.isStreaming) {
344
- this.displaySubmitButton = true; // Re-enable submit button when loading stops
345
- }
346
- this.isHidden = false;
347
- },
348
- isStreaming(newVal) {
349
- if (!newVal && !this.isLoading) {
350
- this.displaySubmitButton = true; // Re-enable submit button when streaming stops
351
- }
352
- },
353
- lastMessage(newMessage) {
354
- if (this.scrolledToBottom || newMessage.role.toLowerCase() === MESSAGE_MODEL_ROLES.user) {
355
- // only scroll to bottom on new message if the user hasn't explicitly scrolled up to view an earlier message
356
- // or if the user has just submitted a new message
357
- this.scrollToBottom();
358
- }
359
- },
360
- shouldShowSlashCommands(shouldShow) {
361
- if (shouldShow) {
362
- this.onShowSlashCommands();
363
- }
364
- },
365
- },
366
- created() {
367
- this.handleScrollingTrottled = throttle(this.handleScrolling, 200); // Assume a 200ms throttle for example
368
- },
369
- mounted() {
370
- this.scrollToBottom();
371
- },
372
- methods: {
373
- compositionEnd() {
374
- this.compositionJustEnded = true;
375
- },
376
- hideChat() {
377
- this.isHidden = true;
378
- /**
379
- * Emitted when clicking the cross in the title and the chat gets closed.
380
- */
381
- this.$emit('chat-hidden');
382
- },
383
- cancelPrompt() {
384
- /**
385
- * Emitted when user clicks the stop button in the textarea
386
- */
387
-
388
- this.displaySubmitButton = true;
389
- this.$emit('chat-cancel');
390
- this.setPromptAndFocus();
391
- },
392
- sendChatPrompt() {
393
- if (!this.displaySubmitButton || this.contextItemsMenuIsOpen) {
394
- return;
395
- }
396
- if (this.prompt) {
397
- if (this.caseInsensitivePrompt === CHAT_RESET_MESSAGE && this.resetDisabled) {
398
- return;
399
- }
400
-
401
- if (
402
- this.caseInsensitivePrompt.startsWith(CHAT_INCLUDE_MESSAGE) &&
403
- this.hasContextItemSelectionMenu
404
- ) {
405
- this.contextItemsMenuIsOpen = true;
406
- return;
407
- }
408
-
409
- if (![CHAT_RESET_MESSAGE, CHAT_CLEAR_MESSAGE].includes(this.caseInsensitivePrompt)) {
410
- this.displaySubmitButton = false;
411
- }
412
-
413
- /**
414
- * Emitted when a new user prompt should be sent out.
415
- *
416
- * @param {String} prompt The user prompt to send.
417
- */
418
- this.$emit('send-chat-prompt', this.prompt.trim());
419
-
420
- this.setPromptAndFocus();
421
- }
422
- },
423
- sendPredefinedPrompt(prompt) {
424
- this.contextItemsMenuIsOpen = false;
425
- this.prompt = prompt;
426
- this.sendChatPrompt();
427
- },
428
- handleScrolling(event) {
429
- const { scrollTop, offsetHeight, scrollHeight } = event.target;
430
- this.scrolledToBottom = scrollTop + offsetHeight >= scrollHeight;
431
- },
432
- async scrollToBottom() {
433
- await this.$nextTick();
434
-
435
- this.$refs.anchor?.scrollIntoView?.();
436
- },
437
- focusChatInput() {
438
- // This method is also called directly by consumers of this component
439
- // https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/blob/dae2d4669ab4da327921492a2962beae8a05c290/webviews/vue2/gitlab_duo_chat/src/App.vue#L109
440
- this.$refs.prompt?.$el?.focus();
441
- },
442
- onTrackFeedback(event) {
443
- /**
444
- * Notify listeners about the feedback form submission on a response message.
445
- * @param {*} event An event, containing the feedback choices and the extended feedback text.
446
- */
447
- this.$emit('track-feedback', event);
448
- },
449
- onShowSlashCommands() {
450
- /**
451
- * Emitted when user opens the slash commands menu
452
- */
453
- this.$emit('chat-slash');
454
- },
455
- sendChatPromptOnEnter(e) {
456
- const { metaKey, ctrlKey, altKey, shiftKey, isComposing } = e;
457
- const isModifierKey = metaKey || ctrlKey || altKey || shiftKey;
458
-
459
- return !(isModifierKey || isComposing || this.compositionJustEnded);
460
- },
461
- onInputKeyup(e) {
462
- const { key } = e;
463
-
464
- if (this.contextItemsMenuIsOpen) {
465
- if (!this.shouldShowContextItemSelectionMenu) {
466
- this.contextItemsMenuIsOpen = false;
467
- }
468
- this.contextItemMenuRef?.handleKeyUp(e);
469
- return;
470
- }
471
- if (this.caseInsensitivePrompt === CHAT_INCLUDE_MESSAGE) {
472
- this.contextItemsMenuIsOpen = true;
473
- return;
474
- }
475
-
476
- if (this.shouldShowSlashCommands) {
477
- e.preventDefault();
478
-
479
- if (key === 'Enter') {
480
- this.selectSlashCommand(this.activeCommandIndex);
481
- } else if (key === 'ArrowUp') {
482
- this.prevCommand();
483
- } else if (key === 'ArrowDown') {
484
- this.nextCommand();
485
- } else {
486
- this.activeCommandIndex = 0;
487
- }
488
- } else if (key === 'Enter' && this.sendChatPromptOnEnter(e)) {
489
- e.preventDefault();
490
-
491
- this.sendChatPrompt();
492
- }
493
-
494
- this.compositionJustEnded = false;
495
- },
496
- prevCommand() {
497
- this.activeCommandIndex -= 1;
498
- this.wrapCommandIndex();
499
- },
500
- nextCommand() {
501
- this.activeCommandIndex += 1;
502
- this.wrapCommandIndex();
503
- },
504
- wrapCommandIndex() {
505
- if (this.activeCommandIndex < 0) {
506
- this.activeCommandIndex = this.filteredSlashCommands.length - 1;
507
- } else if (this.activeCommandIndex >= this.filteredSlashCommands.length) {
508
- this.activeCommandIndex = 0;
509
- }
510
- },
511
- async setPromptAndFocus(prompt = '') {
512
- this.prompt = prompt;
513
- await this.$nextTick();
514
- this.focusChatInput();
515
- },
516
- selectSlashCommand(index) {
517
- const command = this.filteredSlashCommands[index];
518
- if (command.shouldSubmit) {
519
- this.prompt = command.name;
520
- this.sendChatPrompt();
521
- } else {
522
- this.setPromptAndFocus(`${command.name} `);
523
-
524
- if (command.name === CHAT_INCLUDE_MESSAGE && this.hasContextItemSelectionMenu) {
525
- this.contextItemsMenuIsOpen = true;
526
- }
527
- }
528
- },
529
- onInsertCodeSnippet(e) {
530
- /**
531
- * Emit insert-code-snippet event that clients can use to interact with a suggested code.
532
- * @param {*} event An event containing code string in the "detail.code" field.
533
- */
534
- this.$emit('insert-code-snippet', e);
535
- },
536
- onGetContextItemContent(event) {
537
- /**
538
- * Emit get-context-item-content event that tells clients to load the full file content for a selected context item.
539
- * The fully hydrated context item should be updated in the chat message context item.
540
- * @param {*} event An event containing the message ID and context item to hydrate
541
- */
542
- this.$emit('get-context-item-content', event);
543
- },
544
- closeContextItemsMenuOpen() {
545
- this.contextItemsMenuIsOpen = false;
546
- this.setPromptAndFocus();
547
- },
548
- setContextItemsMenuRef(ref) {
549
- this.contextItemMenuRef = ref;
550
- },
551
- },
552
- i18n,
553
- emptySvg,
554
- };
555
- </script>
556
- <template>
557
- <aside
558
- v-if="!isHidden"
559
- id="chat-component"
560
- class="markdown-code-block duo-chat-drawer duo-chat gl-bottom-0 gl-max-h-full"
561
- role="complementary"
562
- data-testid="chat-component"
563
- >
564
- <header
565
- v-if="showHeader"
566
- data-testid="chat-header"
567
- class="duo-chat-drawer-header duo-chat-drawer-header-sticky gl-z-200 gl-border-0 gl-bg-default !gl-p-0"
568
- >
569
- <div class="drawer-title gl-flex gl-items-center gl-justify-start gl-p-5">
570
- <h3 class="gl-my-0 gl-text-size-h2">{{ title }}</h3>
571
- <gl-experiment-badge
572
- v-if="badgeType"
573
- :help-page-url="badgeHelpPageUrl"
574
- :type="badgeType"
575
- container-id="chat-component"
576
- />
577
- <gl-button
578
- category="tertiary"
579
- variant="default"
580
- icon="close"
581
- size="small"
582
- class="gl-ml-auto"
583
- data-testid="chat-close-button"
584
- :aria-label="$options.i18n.CHAT_CLOSE_LABEL"
585
- @click="hideChat"
586
- />
587
- </div>
588
-
589
- <!--
590
- @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.
591
- -->
592
- <slot name="subheader"></slot>
593
-
594
- <!-- Ensure that the global error is not scrolled away -->
595
- <gl-alert
596
- v-if="error"
597
- key="error"
598
- :dismissible="false"
599
- variant="danger"
600
- class="!gl-pl-9"
601
- role="alert"
602
- data-testid="chat-error"
603
- >
604
- <span v-safe-html="error"></span>
605
- </gl-alert>
606
- </header>
607
-
608
- <div
609
- class="duo-chat-drawer-body gl-bg-default"
610
- data-testid="chat-history"
611
- @scroll="handleScrollingTrottled"
612
- >
613
- <transition-group
614
- tag="section"
615
- name="message"
616
- class="duo-chat-history gl-flex gl-flex-col gl-justify-end"
617
- :class="[
618
- {
619
- 'gl-h-full': !hasMessages,
620
- 'force-scroll-bar': hasMessages,
621
- },
622
- ]"
623
- >
624
- <gl-duo-chat-conversation
625
- v-for="(conversation, index) in conversations"
626
- :key="`conversation-${index}`"
627
- :enable-code-insertion="enableCodeInsertion"
628
- :messages="conversation"
629
- :canceled-request-ids="canceledRequestIds"
630
- :show-delimiter="index > 0"
631
- @track-feedback="onTrackFeedback"
632
- @insert-code-snippet="onInsertCodeSnippet"
633
- @get-context-item-content="onGetContextItemContent"
634
- />
635
- <template v-if="!hasMessages && !isLoading">
636
- <gl-empty-state
637
- key="empty-state"
638
- :svg-path="$options.emptySvg"
639
- :svg-height="145"
640
- :title="emptyStateTitle"
641
- class="gl-flex-grow gl-justify-center"
642
- >
643
- <template #description>
644
- <p data-testid="gl-duo-chat-empty-state-description" class="gl-mb-3">
645
- {{ emptyStateDescription }}
646
- </p>
647
- <p
648
- data-testid="gl-duo-chat-empty-state-secondary-description"
649
- class="gl-mt-3 gl-text-sm gl-text-subtle"
650
- >
651
- {{ emptyStateSecondaryDescription }}
652
- </p>
653
- </template>
654
- </gl-empty-state>
655
- <gl-duo-chat-predefined-prompts
656
- key="predefined-prompts"
657
- :prompts="predefinedPrompts"
658
- @click="sendPredefinedPrompt"
659
- />
660
- </template>
661
- <gl-duo-chat-loader v-if="isLoading" key="loader" :tool-name="toolName" />
662
- <div key="anchor" ref="anchor" class="scroll-anchor"></div>
663
- </transition-group>
664
- </div>
665
- <footer
666
- v-if="isChatAvailable"
667
- data-testid="chat-footer"
668
- class="duo-chat-drawer-footer duo-chat-drawer-footer-sticky gl-border-0 gl-bg-default gl-p-5"
669
- :class="{ 'duo-chat-drawer-body-scrim-on-footer': !scrolledToBottom }"
670
- >
671
- <gl-form data-testid="chat-prompt-form" @submit.stop.prevent="sendChatPrompt">
672
- <div class="gl-relative gl-max-w-full">
673
- <!--
674
- @slot For integrating `<gl-context-items-menu>` component if pinned-context should be available. The following scopedSlot properties are provided: `isOpen`, `onClose`, `setRef`, `focusPrompt`, which should be passed to the `<gl-context-items-menu>` component when rendering, e.g. `<template #context-items-menu="{ isOpen, onClose, setRef, focusPrompt }">` `<gl-duo-chat-context-item-menu :ref="setRef" :open="isOpen" @close="onClose" @focus-prompt="focusPrompt" ...`
675
- -->
676
- <slot
677
- name="context-items-menu"
678
- :is-open="contextItemsMenuIsOpen"
679
- :on-close="closeContextItemsMenuOpen"
680
- :set-ref="setContextItemsMenuRef"
681
- :focus-prompt="focusChatInput"
682
- ></slot>
683
- </div>
684
-
685
- <gl-form-input-group>
686
- <div
687
- class="duo-chat-input gl-min-h-8 gl-max-w-full gl-grow gl-align-top"
688
- :data-value="prompt"
689
- >
690
- <gl-card
691
- v-if="shouldShowSlashCommands"
692
- ref="commands"
693
- class="slash-commands !gl-absolute gl-w-full -gl-translate-y-full gl-list-none gl-pl-0 gl-shadow-md"
694
- body-class="!gl-p-2"
695
- >
696
- <gl-dropdown-item
697
- v-for="(command, index) in filteredSlashCommands"
698
- :key="command.name"
699
- :class="{ 'active-command': index === activeCommandIndex }"
700
- @mouseenter.native="activeCommandIndex = index"
701
- @click="selectSlashCommand(index)"
702
- >
703
- <span class="gl-flex gl-justify-between">
704
- <span class="gl-block">{{ command.name }}</span>
705
- <small class="gl-pl-3 gl-text-right gl-italic gl-text-subtle">{{
706
- command.description
707
- }}</small>
708
- </span>
709
- </gl-dropdown-item>
710
- </gl-card>
711
-
712
- <gl-form-textarea
713
- ref="prompt"
714
- v-model="prompt"
715
- data-testid="chat-prompt-input"
716
- class="gl-absolute !gl-h-full gl-rounded-br-none gl-rounded-tr-none !gl-bg-transparent !gl-py-4 !gl-shadow-none"
717
- :class="{ 'gl-truncate': !prompt }"
718
- :placeholder="inputPlaceholder"
719
- autofocus
720
- @keydown.enter.exact.native.prevent
721
- @keyup.native="onInputKeyup"
722
- @compositionend="compositionEnd"
723
- />
724
- </div>
725
- <template #append>
726
- <gl-button
727
- v-if="displaySubmitButton"
728
- icon="paper-airplane"
729
- category="primary"
730
- variant="confirm"
731
- class="!gl-absolute gl-bottom-2 gl-right-2 !gl-rounded-full"
732
- type="submit"
733
- data-testid="chat-prompt-submit-button"
734
- :aria-label="$options.i18n.CHAT_SUBMIT_LABEL"
735
- />
736
- <gl-button
737
- v-else
738
- icon="stop"
739
- category="primary"
740
- variant="default"
741
- class="!gl-absolute gl-bottom-2 gl-right-2 !gl-rounded-full"
742
- data-testid="chat-prompt-cancel-button"
743
- :aria-label="$options.i18n.CHAT_CANCEL_LABEL"
744
- @click="cancelPrompt"
745
- />
746
- </template>
747
- </gl-form-input-group>
748
- </gl-form>
749
- </footer>
750
- </aside>
751
- </template>