@gitlab/ui 92.0.0 → 92.1.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # [92.1.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v92.0.0...v92.1.0) (2024-09-10)
2
+
3
+
4
+ ### Features
5
+
6
+ * **GlDuoChat:** add /include command, integrate context-item-menu ([3f664f8](https://gitlab.com/gitlab-org/gitlab-ui/commit/3f664f89a35aa426bb043f1563cd2bdb9722878c))
7
+
1
8
  # [92.0.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v91.15.0...v92.0.0) (2024-09-10)
2
9
 
3
10
 
@@ -179,6 +179,11 @@ var script = {
179
179
  return;
180
180
  }
181
181
  this.selectedCategory = null;
182
+
183
+ /**
184
+ * Emitted when the parent GlDuoChat component should refocus on the main prompt input
185
+ */
186
+ this.$emit('focus-prompt');
182
187
  break;
183
188
  }
184
189
  },
@@ -82,7 +82,7 @@ var script = {
82
82
  const __vue_script__ = script;
83
83
 
84
84
  /* template */
85
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"gl-mb-3 gl-flex gl-flex-col"},[_c('button',{staticClass:"gl-flex gl-w-full gl-items-center gl-border-0 gl-bg-transparent gl-p-0 gl-text-left gl-text-xs gl-lowercase gl-text-gray-500",attrs:{"data-testid":"chat-context-selections-title"},on:{"click":_vm.toggleCollapse}},[_c('gl-icon',{attrs:{"name":_vm.collapseIconName,"data-testid":"chat-context-collapse-icon"}}),_vm._v(" "+_vm._s(_vm.title)+"\n ")],1),_vm._v(" "),_c('div',{directives:[{name:"show",rawName:"v-show",value:(!_vm.isCollapsed),expression:"!isCollapsed"}],staticClass:"gl-mt-1 gl-flex gl-grow gl-flex-wrap",attrs:{"data-testid":"chat-context-tokens-wrapper"}},_vm._l((_vm.selections),function(contextItem){return _c('gl-token',{key:contextItem.id,staticClass:"gl-mb-2 gl-mr-2",attrs:{"view-only":!_vm.removable,"variant":"default"},on:{"close":function($event){return _vm.onRemoveItem(contextItem)}}},[_c('div',{staticClass:"gl-flex gl-items-center",attrs:{"id":("context-item-" + (contextItem.id) + "-" + _vm.selectionsId)}},[_c('gl-icon',{staticClass:"gl-mr-1",attrs:{"name":_vm.getIconName(contextItem.type),"size":12}}),_vm._v("\n "+_vm._s(contextItem.metadata.name)+"\n ")],1),_vm._v(" "),_c('gl-duo-chat-context-item-popover',{attrs:{"context-item":contextItem,"target":("context-item-" + (contextItem.id) + "-" + _vm.selectionsId),"placement":"bottom"}})],1)}),1)])};
85
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"gl-mb-3 gl-flex gl-flex-col"},[_c('button',{staticClass:"gl-flex gl-w-full gl-items-center gl-border-0 gl-bg-transparent gl-p-0 gl-text-left gl-text-xs gl-lowercase gl-text-gray-500",attrs:{"data-testid":"chat-context-selections-title","type":"button"},on:{"click":_vm.toggleCollapse}},[_c('gl-icon',{attrs:{"name":_vm.collapseIconName,"data-testid":"chat-context-collapse-icon"}}),_vm._v(" "+_vm._s(_vm.title)+"\n ")],1),_vm._v(" "),_c('div',{directives:[{name:"show",rawName:"v-show",value:(!_vm.isCollapsed),expression:"!isCollapsed"}],staticClass:"gl-mt-1 gl-flex gl-grow gl-flex-wrap",attrs:{"data-testid":"chat-context-tokens-wrapper"}},_vm._l((_vm.selections),function(contextItem){return _c('gl-token',{key:contextItem.id,staticClass:"gl-mb-2 gl-mr-2",attrs:{"view-only":!_vm.removable,"variant":"default"},on:{"close":function($event){return _vm.onRemoveItem(contextItem)}}},[_c('div',{staticClass:"gl-flex gl-items-center",attrs:{"id":("context-item-" + (contextItem.id) + "-" + _vm.selectionsId)}},[_c('gl-icon',{staticClass:"gl-mr-1",attrs:{"name":_vm.getIconName(contextItem.type),"size":12}}),_vm._v("\n "+_vm._s(contextItem.metadata.name)+"\n ")],1),_vm._v(" "),_c('gl-duo-chat-context-item-popover',{attrs:{"context-item":contextItem,"target":("context-item-" + (contextItem.id) + "-" + _vm.selectionsId),"placement":"bottom"}})],1)}),1)])};
86
86
  var __vue_staticRenderFns__ = [];
87
87
 
88
88
  /* style */
@@ -1,6 +1,7 @@
1
1
  const CHAT_RESET_MESSAGE = '/reset';
2
2
  const CHAT_CLEAN_MESSAGE = '/clean';
3
3
  const CHAT_CLEAR_MESSAGE = '/clear';
4
+ const CHAT_INCLUDE_MESSAGE = '/include';
4
5
  const LOADING_TRANSITION_DURATION = 7500;
5
6
  const DOCUMENTATION_SOURCE_TYPES = {
6
7
  HANDBOOK: {
@@ -22,4 +23,4 @@ const MESSAGE_MODEL_ROLES = {
22
23
  assistant: 'assistant'
23
24
  };
24
25
 
25
- export { CHAT_CLEAN_MESSAGE, CHAT_CLEAR_MESSAGE, CHAT_RESET_MESSAGE, DOCUMENTATION_SOURCE_TYPES, LOADING_TRANSITION_DURATION, MESSAGE_MODEL_ROLES };
26
+ export { CHAT_CLEAN_MESSAGE, CHAT_CLEAR_MESSAGE, CHAT_INCLUDE_MESSAGE, CHAT_RESET_MESSAGE, DOCUMENTATION_SOURCE_TYPES, LOADING_TRANSITION_DURATION, MESSAGE_MODEL_ROLES };
@@ -14,7 +14,8 @@ import { SafeHtmlDirective } from '../../../../directives/safe_html/safe_html';
14
14
  import GlDuoChatLoader from './components/duo_chat_loader/duo_chat_loader';
15
15
  import GlDuoChatPredefinedPrompts from './components/duo_chat_predefined_prompts/duo_chat_predefined_prompts';
16
16
  import GlDuoChatConversation from './components/duo_chat_conversation/duo_chat_conversation';
17
- import { CHAT_RESET_MESSAGE, CHAT_CLEAN_MESSAGE, CHAT_CLEAR_MESSAGE } from './constants';
17
+ import { CHAT_RESET_MESSAGE, CHAT_INCLUDE_MESSAGE, CHAT_CLEAN_MESSAGE, CHAT_CLEAR_MESSAGE } from './constants';
18
+ import { INCLUDE_SLASH_COMMAND } from './mock_data';
18
19
  import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
19
20
 
20
21
  const i18n = {
@@ -205,7 +206,9 @@ var script = {
205
206
  scrolledToBottom: true,
206
207
  activeCommandIndex: 0,
207
208
  displaySubmitButton: true,
208
- compositionJustEnded: false
209
+ compositionJustEnded: false,
210
+ contextItemsMenuIsOpen: false,
211
+ contextItemMenuRef: null
209
212
  };
210
213
  },
211
214
  computed: {
@@ -231,6 +234,9 @@ var script = {
231
234
  var _this$messages2;
232
235
  return (_this$messages2 = this.messages) === null || _this$messages2 === void 0 ? void 0 : _this$messages2[this.messages.length - 1];
233
236
  },
237
+ caseInsensitivePrompt() {
238
+ return this.prompt.toLowerCase().trim();
239
+ },
234
240
  resetDisabled() {
235
241
  var _this$lastMessage;
236
242
  if (this.isLoading || !this.hasMessages) {
@@ -246,21 +252,38 @@ var script = {
246
252
  return Boolean(((_this$lastMessage3 = this.lastMessage) === null || _this$lastMessage3 === void 0 ? void 0 : (_this$lastMessage3$ch = _this$lastMessage3.chunks) === null || _this$lastMessage3$ch === void 0 ? void 0 : _this$lastMessage3$ch.length) > 0 && !((_this$lastMessage4 = this.lastMessage) !== null && _this$lastMessage4 !== void 0 && _this$lastMessage4.content) || typeof ((_this$lastMessage5 = this.lastMessage) === null || _this$lastMessage5 === void 0 ? void 0 : _this$lastMessage5.chunkId) === 'number');
247
253
  },
248
254
  filteredSlashCommands() {
249
- const caseInsensitivePrompt = this.prompt.toLowerCase();
250
- return this.slashCommands.filter(c => c.name.toLowerCase().startsWith(caseInsensitivePrompt));
255
+ return this.slashCommands.filter(c => c.name.toLowerCase().startsWith(this.caseInsensitivePrompt)).filter(c => {
256
+ if (c.name === CHAT_INCLUDE_MESSAGE) {
257
+ return this.hasContextItemSelectionMenu;
258
+ }
259
+ return true;
260
+ });
251
261
  },
252
262
  shouldShowSlashCommands() {
253
- if (!this.withSlashCommands) return false;
254
- const caseInsensitivePrompt = this.prompt.toLowerCase();
255
- const startsWithSlash = caseInsensitivePrompt.startsWith('/');
256
- const startsWithSlashCommand = this.slashCommands.some(c => caseInsensitivePrompt.startsWith(c.name));
263
+ if (!this.withSlashCommands || this.contextItemsMenuIsOpen) return false;
264
+ const startsWithSlash = this.caseInsensitivePrompt.startsWith('/');
265
+ const startsWithSlashCommand = this.slashCommands.some(c => this.caseInsensitivePrompt.startsWith(c.name));
257
266
  return startsWithSlash && this.filteredSlashCommands.length && !startsWithSlashCommand;
258
267
  },
268
+ shouldShowContextItemSelectionMenu() {
269
+ if (!this.hasContextItemSelectionMenu) {
270
+ return false;
271
+ }
272
+ const isSlash = this.caseInsensitivePrompt === '/';
273
+ if (!this.caseInsensitivePrompt || isSlash) {
274
+ // 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
275
+ return false;
276
+ }
277
+ return INCLUDE_SLASH_COMMAND.name.startsWith(this.caseInsensitivePrompt);
278
+ },
259
279
  inputPlaceholder() {
260
280
  if (this.chatPromptPlaceholder) {
261
281
  return this.chatPromptPlaceholder;
262
282
  }
263
283
  return this.withSlashCommands ? i18n.CHAT_PROMPT_PLACEHOLDER_WITH_COMMANDS : i18n.CHAT_PROMPT_PLACEHOLDER_DEFAULT;
284
+ },
285
+ hasContextItemSelectionMenu() {
286
+ return Boolean(this.contextItemMenuRef);
264
287
  }
265
288
  },
266
289
  watch: {
@@ -304,27 +327,32 @@ var script = {
304
327
  this.setPromptAndFocus();
305
328
  },
306
329
  sendChatPrompt() {
307
- if (!this.displaySubmitButton) {
330
+ if (!this.displaySubmitButton || this.contextItemsMenuIsOpen) {
308
331
  return;
309
332
  }
310
333
  if (this.prompt) {
311
- if (this.prompt === CHAT_RESET_MESSAGE && this.resetDisabled) {
334
+ if (this.caseInsensitivePrompt === CHAT_RESET_MESSAGE && this.resetDisabled) {
335
+ return;
336
+ }
337
+ if (this.caseInsensitivePrompt.startsWith(CHAT_INCLUDE_MESSAGE) && this.hasContextItemSelectionMenu) {
338
+ this.contextItemsMenuIsOpen = true;
312
339
  return;
313
340
  }
341
+ if (![CHAT_RESET_MESSAGE, CHAT_CLEAN_MESSAGE, CHAT_CLEAR_MESSAGE].includes(this.caseInsensitivePrompt)) {
342
+ this.displaySubmitButton = false;
343
+ }
344
+
314
345
  /**
315
346
  * Emitted when a new user prompt should be sent out.
316
347
  *
317
348
  * @param {String} prompt The user prompt to send.
318
349
  */
319
-
320
- if (![CHAT_RESET_MESSAGE, CHAT_CLEAN_MESSAGE, CHAT_CLEAR_MESSAGE].includes(this.prompt)) {
321
- this.displaySubmitButton = false;
322
- }
323
350
  this.$emit('send-chat-prompt', this.prompt.trim());
324
351
  this.setPromptAndFocus();
325
352
  }
326
353
  },
327
354
  sendPredefinedPrompt(prompt) {
355
+ this.contextItemsMenuIsOpen = false;
328
356
  this.prompt = prompt;
329
357
  this.sendChatPrompt();
330
358
  },
@@ -371,6 +399,18 @@ var script = {
371
399
  const {
372
400
  key
373
401
  } = e;
402
+ if (this.contextItemsMenuIsOpen) {
403
+ var _this$contextItemMenu;
404
+ if (!this.shouldShowContextItemSelectionMenu) {
405
+ this.contextItemsMenuIsOpen = false;
406
+ }
407
+ (_this$contextItemMenu = this.contextItemMenuRef) === null || _this$contextItemMenu === void 0 ? void 0 : _this$contextItemMenu.handleKeyUp(e);
408
+ return;
409
+ }
410
+ if (this.caseInsensitivePrompt === INCLUDE_SLASH_COMMAND.name) {
411
+ this.contextItemsMenuIsOpen = true;
412
+ return;
413
+ }
374
414
  if (this.shouldShowSlashCommands) {
375
415
  e.preventDefault();
376
416
  if (key === 'Enter') {
@@ -416,6 +456,9 @@ var script = {
416
456
  this.sendChatPrompt();
417
457
  } else {
418
458
  this.setPromptAndFocus(`${command.name} `);
459
+ if (command.name === CHAT_INCLUDE_MESSAGE && this.hasContextItemSelectionMenu) {
460
+ this.contextItemsMenuIsOpen = true;
461
+ }
419
462
  }
420
463
  },
421
464
  onInsertCodeSnippet(e) {
@@ -424,6 +467,13 @@ var script = {
424
467
  * @param {*} event An event containing code string in the "detail.code" field.
425
468
  */
426
469
  this.$emit('insert-code-snippet', e);
470
+ },
471
+ closeContextItemsMenuOpen() {
472
+ this.contextItemsMenuIsOpen = false;
473
+ this.setPromptAndFocus();
474
+ },
475
+ setContextItemsMenuRef(ref) {
476
+ this.contextItemMenuRef = ref;
427
477
  }
428
478
  },
429
479
  i18n,
@@ -438,7 +488,7 @@ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=
438
488
  {
439
489
  'gl-h-full': !_vm.hasMessages,
440
490
  'force-scroll-bar': _vm.hasMessages,
441
- } ],attrs:{"tag":"section","name":"message"}},[_vm._l((_vm.conversations),function(conversation,index){return _c('gl-duo-chat-conversation',{key:("conversation-" + index),attrs:{"enable-code-insertion":_vm.enableCodeInsertion,"messages":conversation,"canceled-request-ids":_vm.canceledRequestIds,"show-delimiter":index > 0},on:{"track-feedback":_vm.onTrackFeedback,"insert-code-snippet":_vm.onInsertCodeSnippet}})}),_vm._v(" "),(!_vm.hasMessages && !_vm.isLoading)?[_c('gl-empty-state',{key:"empty-state",staticClass:"gl-flex-grow gl-justify-center",attrs:{"svg-path":_vm.$options.emptySvg,"svg-height":145,"title":_vm.emptyStateTitle},scopedSlots:_vm._u([{key:"description",fn:function(){return [_c('p',{staticClass:"gl-mb-3",attrs:{"data-testid":"gl-duo-chat-empty-state-description"}},[_vm._v("\n "+_vm._s(_vm.emptyStateDescription)+"\n ")]),_vm._v(" "),_c('p',{staticClass:"gl-mt-3 gl-text-sm gl-text-subtle",attrs:{"data-testid":"gl-duo-chat-empty-state-secondary-description"}},[_vm._v("\n "+_vm._s(_vm.emptyStateSecondaryDescription)+"\n ")])]},proxy:true}],null,false,460840487)}),_vm._v(" "),_c('gl-duo-chat-predefined-prompts',{key:"predefined-prompts",attrs:{"prompts":_vm.predefinedPrompts},on:{"click":_vm.sendPredefinedPrompt}})]:_vm._e(),_vm._v(" "),(_vm.isLoading)?_c('gl-duo-chat-loader',{key:"loader",attrs:{"tool-name":_vm.toolName}}):_vm._e(),_vm._v(" "),_c('div',{key:"anchor",ref:"anchor",staticClass:"scroll-anchor"})],2)],1),_vm._v(" "),(_vm.isChatAvailable)?_c('footer',{staticClass:"duo-chat-drawer-footer duo-chat-drawer-footer-sticky gl-border-t gl-bg-gray-10 gl-p-5",class:{ 'duo-chat-drawer-body-scrim-on-footer': !_vm.scrolledToBottom },attrs:{"data-testid":"chat-footer"}},[_c('gl-form',{attrs:{"data-testid":"chat-prompt-form"},on:{"submit":function($event){$event.stopPropagation();$event.preventDefault();return _vm.sendChatPrompt.apply(null, arguments)}}},[_c('gl-form-input-group',{scopedSlots:_vm._u([{key:"append",fn:function(){return [(_vm.displaySubmitButton)?_c('gl-button',{staticClass:"!gl-absolute gl-bottom-2 gl-right-2 !gl-rounded-base",attrs:{"icon":"paper-airplane","category":"primary","variant":"confirm","type":"submit","data-testid":"chat-prompt-submit-button","aria-label":_vm.$options.i18n.CHAT_SUBMIT_LABEL}}):_c('gl-button',{staticClass:"!gl-absolute gl-bottom-2 gl-right-2 !gl-rounded-base",attrs:{"icon":"stop","category":"primary","variant":"default","data-testid":"chat-prompt-cancel-button","aria-label":_vm.$options.i18n.CHAT_CANCEL_LABEL},on:{"click":_vm.cancelPrompt}})]},proxy:true}],null,false,677611116)},[_c('div',{staticClass:"duo-chat-input gl-min-h-8 gl-max-w-full gl-grow gl-rounded-base gl-bg-white gl-align-top gl-shadow-inner-1-gray-400",attrs:{"data-value":_vm.prompt}},[(_vm.shouldShowSlashCommands)?_c('gl-card',{ref:"commands",staticClass:"slash-commands !gl-absolute gl-w-full -gl-translate-y-full gl-list-none gl-pl-0 gl-shadow-md",attrs:{"body-class":"!gl-p-2"}},_vm._l((_vm.filteredSlashCommands),function(command,index){return _c('gl-dropdown-item',{key:command.name,class:{ 'active-command': index === _vm.activeCommandIndex },on:{"click":function($event){return _vm.selectSlashCommand(index)}},nativeOn:{"mouseenter":function($event){_vm.activeCommandIndex = index;}}},[_c('span',{staticClass:"gl-flex gl-justify-between"},[_c('span',{staticClass:"gl-block"},[_vm._v(_vm._s(command.name))]),_vm._v(" "),_c('small',{staticClass:"gl-pl-3 gl-text-right gl-italic gl-text-gray-500"},[_vm._v(_vm._s(command.description))])])])}),1):_vm._e(),_vm._v(" "),_c('gl-form-textarea',{ref:"prompt",staticClass:"gl-absolute !gl-h-full gl-rounded-br-none gl-rounded-tr-none !gl-bg-transparent !gl-py-4 !gl-shadow-none",class:{ 'gl-truncate': !_vm.prompt },attrs:{"data-testid":"chat-prompt-input","placeholder":_vm.inputPlaceholder,"autofocus":""},on:{"compositionend":_vm.compositionEnd},nativeOn:{"keydown":function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"enter",13,$event.key,"Enter")){ return null; }if($event.ctrlKey||$event.shiftKey||$event.altKey||$event.metaKey){ return null; }$event.preventDefault();},"keyup":function($event){return _vm.onInputKeyup.apply(null, arguments)}},model:{value:(_vm.prompt),callback:function ($$v) {_vm.prompt=$$v;},expression:"prompt"}})],1)]),_vm._v(" "),_c('p',{staticClass:"gl-mb-0 gl-mt-3 gl-text-sm gl-leading-20 gl-text-subtle",attrs:{"data-testid":"chat-legal-disclaimer"}},[_vm._v("\n "+_vm._s(_vm.$options.i18n.CHAT_LEGAL_DISCLAIMER)+"\n ")])],1)],1):_vm._e()]):_vm._e()};
491
+ } ],attrs:{"tag":"section","name":"message"}},[_vm._l((_vm.conversations),function(conversation,index){return _c('gl-duo-chat-conversation',{key:("conversation-" + index),attrs:{"enable-code-insertion":_vm.enableCodeInsertion,"messages":conversation,"canceled-request-ids":_vm.canceledRequestIds,"show-delimiter":index > 0},on:{"track-feedback":_vm.onTrackFeedback,"insert-code-snippet":_vm.onInsertCodeSnippet}})}),_vm._v(" "),(!_vm.hasMessages && !_vm.isLoading)?[_c('gl-empty-state',{key:"empty-state",staticClass:"gl-flex-grow gl-justify-center",attrs:{"svg-path":_vm.$options.emptySvg,"svg-height":145,"title":_vm.emptyStateTitle},scopedSlots:_vm._u([{key:"description",fn:function(){return [_c('p',{staticClass:"gl-mb-3",attrs:{"data-testid":"gl-duo-chat-empty-state-description"}},[_vm._v("\n "+_vm._s(_vm.emptyStateDescription)+"\n ")]),_vm._v(" "),_c('p',{staticClass:"gl-mt-3 gl-text-sm gl-text-subtle",attrs:{"data-testid":"gl-duo-chat-empty-state-secondary-description"}},[_vm._v("\n "+_vm._s(_vm.emptyStateSecondaryDescription)+"\n ")])]},proxy:true}],null,false,460840487)}),_vm._v(" "),_c('gl-duo-chat-predefined-prompts',{key:"predefined-prompts",attrs:{"prompts":_vm.predefinedPrompts},on:{"click":_vm.sendPredefinedPrompt}})]:_vm._e(),_vm._v(" "),(_vm.isLoading)?_c('gl-duo-chat-loader',{key:"loader",attrs:{"tool-name":_vm.toolName}}):_vm._e(),_vm._v(" "),_c('div',{key:"anchor",ref:"anchor",staticClass:"scroll-anchor"})],2)],1),_vm._v(" "),(_vm.isChatAvailable)?_c('footer',{staticClass:"duo-chat-drawer-footer duo-chat-drawer-footer-sticky gl-border-t gl-bg-gray-10 gl-p-5",class:{ 'duo-chat-drawer-body-scrim-on-footer': !_vm.scrolledToBottom },attrs:{"data-testid":"chat-footer"}},[_c('gl-form',{attrs:{"data-testid":"chat-prompt-form"},on:{"submit":function($event){$event.stopPropagation();$event.preventDefault();return _vm.sendChatPrompt.apply(null, arguments)}}},[_c('div',{staticClass:"gl-relative gl-max-w-full"},[_vm._t("context-items-menu",null,{"isOpen":_vm.contextItemsMenuIsOpen,"onClose":_vm.closeContextItemsMenuOpen,"setRef":_vm.setContextItemsMenuRef,"focusPrompt":_vm.focusChatInput})],2),_vm._v(" "),_c('gl-form-input-group',{scopedSlots:_vm._u([{key:"append",fn:function(){return [(_vm.displaySubmitButton)?_c('gl-button',{staticClass:"!gl-absolute gl-bottom-2 gl-right-2 !gl-rounded-base",attrs:{"icon":"paper-airplane","category":"primary","variant":"confirm","type":"submit","data-testid":"chat-prompt-submit-button","aria-label":_vm.$options.i18n.CHAT_SUBMIT_LABEL}}):_c('gl-button',{staticClass:"!gl-absolute gl-bottom-2 gl-right-2 !gl-rounded-base",attrs:{"icon":"stop","category":"primary","variant":"default","data-testid":"chat-prompt-cancel-button","aria-label":_vm.$options.i18n.CHAT_CANCEL_LABEL},on:{"click":_vm.cancelPrompt}})]},proxy:true}],null,false,677611116)},[_c('div',{staticClass:"duo-chat-input gl-min-h-8 gl-max-w-full gl-grow gl-rounded-base gl-bg-white gl-align-top gl-shadow-inner-1-gray-400",attrs:{"data-value":_vm.prompt}},[(_vm.shouldShowSlashCommands)?_c('gl-card',{ref:"commands",staticClass:"slash-commands !gl-absolute gl-w-full -gl-translate-y-full gl-list-none gl-pl-0 gl-shadow-md",attrs:{"body-class":"!gl-p-2"}},_vm._l((_vm.filteredSlashCommands),function(command,index){return _c('gl-dropdown-item',{key:command.name,class:{ 'active-command': index === _vm.activeCommandIndex },on:{"click":function($event){return _vm.selectSlashCommand(index)}},nativeOn:{"mouseenter":function($event){_vm.activeCommandIndex = index;}}},[_c('span',{staticClass:"gl-flex gl-justify-between"},[_c('span',{staticClass:"gl-block"},[_vm._v(_vm._s(command.name))]),_vm._v(" "),_c('small',{staticClass:"gl-pl-3 gl-text-right gl-italic gl-text-gray-500"},[_vm._v(_vm._s(command.description))])])])}),1):_vm._e(),_vm._v(" "),_c('gl-form-textarea',{ref:"prompt",staticClass:"gl-absolute !gl-h-full gl-rounded-br-none gl-rounded-tr-none !gl-bg-transparent !gl-py-4 !gl-shadow-none",class:{ 'gl-truncate': !_vm.prompt },attrs:{"data-testid":"chat-prompt-input","placeholder":_vm.inputPlaceholder,"autofocus":""},on:{"compositionend":_vm.compositionEnd},nativeOn:{"keydown":function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"enter",13,$event.key,"Enter")){ return null; }if($event.ctrlKey||$event.shiftKey||$event.altKey||$event.metaKey){ return null; }$event.preventDefault();},"keyup":function($event){return _vm.onInputKeyup.apply(null, arguments)}},model:{value:(_vm.prompt),callback:function ($$v) {_vm.prompt=$$v;},expression:"prompt"}})],1)]),_vm._v(" "),_c('p',{staticClass:"gl-mb-0 gl-mt-3 gl-text-sm gl-leading-20 gl-text-subtle",attrs:{"data-testid":"chat-legal-disclaimer"}},[_vm._v("\n "+_vm._s(_vm.$options.i18n.CHAT_LEGAL_DISCLAIMER)+"\n ")])],1)],1):_vm._e()]):_vm._e()};
442
492
  var __vue_staticRenderFns__ = [];
443
493
 
444
494
  /* style */
@@ -1,5 +1,5 @@
1
1
  import { setStoryTimeout } from '../../../../utils/test_utils';
2
- import { DOCUMENTATION_SOURCE_TYPES, MESSAGE_MODEL_ROLES, CHAT_RESET_MESSAGE, CHAT_CLEAN_MESSAGE } from './constants';
2
+ import { DOCUMENTATION_SOURCE_TYPES, MESSAGE_MODEL_ROLES, CHAT_RESET_MESSAGE, CHAT_CLEAN_MESSAGE, CHAT_INCLUDE_MESSAGE } from './constants';
3
3
  import { getMockContextItems } from './components/duo_chat_context/mock_context_data';
4
4
 
5
5
  const MOCK_SOURCES = [{
@@ -154,5 +154,9 @@ const SLASH_COMMANDS = [{
154
154
  name: '/explain',
155
155
  description: 'Explain the selected snippet.'
156
156
  }];
157
+ const INCLUDE_SLASH_COMMAND = {
158
+ name: CHAT_INCLUDE_MESSAGE,
159
+ description: 'Include additional context in the conversation.'
160
+ };
157
161
 
158
- export { MOCK_RESPONSE_MESSAGE, MOCK_RESPONSE_MESSAGE_FOR_STREAMING, MOCK_USER_PROMPT_MESSAGE, SLASH_COMMANDS, generateMockResponseChunks, generateSeparateChunks, renderGFM, renderMarkdown };
162
+ export { INCLUDE_SLASH_COMMAND, MOCK_RESPONSE_MESSAGE, MOCK_RESPONSE_MESSAGE_FOR_STREAMING, MOCK_USER_PROMPT_MESSAGE, SLASH_COMMANDS, generateMockResponseChunks, generateSeparateChunks, renderGFM, renderMarkdown };
package/dist/index.js CHANGED
@@ -89,6 +89,7 @@ export { default as GlAccordionItem } from './components/base/accordion/accordio
89
89
  export { default as GlExperimentBadge } from './components/experimental/experiment_badge/experiment_badge';
90
90
  export { default as GlDuoUserFeedback } from './components/experimental/duo/user_feedback/user_feedback';
91
91
  export { default as GlDuoChat } from './components/experimental/duo/chat/duo_chat';
92
+ export { default as GlDuoChatContextItemMenu } from './components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu';
92
93
  export { default as GlAnimatedNumber } from './components/utilities/animated_number/animated_number';
93
94
  export { default as GlFriendlyWrap } from './components/utilities/friendly_wrap/friendly_wrap';
94
95
  export { default as GlIntersperse } from './components/utilities/intersperse/intersperse';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "92.0.0",
3
+ "version": "92.1.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -183,6 +183,11 @@ export default {
183
183
  }
184
184
 
185
185
  this.selectedCategory = null;
186
+
187
+ /**
188
+ * Emitted when the parent GlDuoChat component should refocus on the main prompt input
189
+ */
190
+ this.$emit('focus-prompt');
186
191
  break;
187
192
  default:
188
193
  break;
@@ -88,6 +88,7 @@ export default {
88
88
  <button
89
89
  class="gl-flex gl-w-full gl-items-center gl-border-0 gl-bg-transparent gl-p-0 gl-text-left gl-text-xs gl-lowercase gl-text-gray-500"
90
90
  data-testid="chat-context-selections-title"
91
+ type="button"
91
92
  @click="toggleCollapse"
92
93
  >
93
94
  <gl-icon :name="collapseIconName" data-testid="chat-context-collapse-icon" /> {{ title }}
@@ -1,6 +1,7 @@
1
1
  export const CHAT_RESET_MESSAGE = '/reset';
2
2
  export const CHAT_CLEAN_MESSAGE = '/clean';
3
3
  export const CHAT_CLEAR_MESSAGE = '/clear';
4
+ export const CHAT_INCLUDE_MESSAGE = '/include';
4
5
 
5
6
  export const LOADING_TRANSITION_DURATION = 7500;
6
7
 
@@ -15,7 +15,13 @@ import { SafeHtmlDirective as SafeHtml } from '../../../../directives/safe_html/
15
15
  import GlDuoChatLoader from './components/duo_chat_loader/duo_chat_loader.vue';
16
16
  import GlDuoChatPredefinedPrompts from './components/duo_chat_predefined_prompts/duo_chat_predefined_prompts.vue';
17
17
  import GlDuoChatConversation from './components/duo_chat_conversation/duo_chat_conversation.vue';
18
- import { CHAT_CLEAN_MESSAGE, CHAT_RESET_MESSAGE, CHAT_CLEAR_MESSAGE } from './constants';
18
+ import {
19
+ CHAT_CLEAN_MESSAGE,
20
+ CHAT_RESET_MESSAGE,
21
+ CHAT_CLEAR_MESSAGE,
22
+ CHAT_INCLUDE_MESSAGE,
23
+ } from './constants';
24
+ import { INCLUDE_SLASH_COMMAND } from './mock_data';
19
25
 
20
26
  export const i18n = {
21
27
  CHAT_DEFAULT_TITLE: 'GitLab Duo Chat',
@@ -214,6 +220,8 @@ export default {
214
220
  activeCommandIndex: 0,
215
221
  displaySubmitButton: true,
216
222
  compositionJustEnded: false,
223
+ contextItemsMenuIsOpen: false,
224
+ contextItemMenuRef: null,
217
225
  };
218
226
  },
219
227
  computed: {
@@ -241,6 +249,9 @@ export default {
241
249
  lastMessage() {
242
250
  return this.messages?.[this.messages.length - 1];
243
251
  },
252
+ caseInsensitivePrompt() {
253
+ return this.prompt.toLowerCase().trim();
254
+ },
244
255
  resetDisabled() {
245
256
  if (this.isLoading || !this.hasMessages) {
246
257
  return true;
@@ -257,20 +268,36 @@ export default {
257
268
  );
258
269
  },
259
270
  filteredSlashCommands() {
260
- const caseInsensitivePrompt = this.prompt.toLowerCase();
261
- return this.slashCommands.filter((c) =>
262
- c.name.toLowerCase().startsWith(caseInsensitivePrompt)
263
- );
271
+ return this.slashCommands
272
+ .filter((c) => c.name.toLowerCase().startsWith(this.caseInsensitivePrompt))
273
+ .filter((c) => {
274
+ if (c.name === CHAT_INCLUDE_MESSAGE) {
275
+ return this.hasContextItemSelectionMenu;
276
+ }
277
+ return true;
278
+ });
264
279
  },
265
280
  shouldShowSlashCommands() {
266
- if (!this.withSlashCommands) return false;
267
- const caseInsensitivePrompt = this.prompt.toLowerCase();
268
- const startsWithSlash = caseInsensitivePrompt.startsWith('/');
281
+ if (!this.withSlashCommands || this.contextItemsMenuIsOpen) return false;
282
+ const startsWithSlash = this.caseInsensitivePrompt.startsWith('/');
269
283
  const startsWithSlashCommand = this.slashCommands.some((c) =>
270
- caseInsensitivePrompt.startsWith(c.name)
284
+ this.caseInsensitivePrompt.startsWith(c.name)
271
285
  );
272
286
  return startsWithSlash && this.filteredSlashCommands.length && !startsWithSlashCommand;
273
287
  },
288
+ shouldShowContextItemSelectionMenu() {
289
+ if (!this.hasContextItemSelectionMenu) {
290
+ return false;
291
+ }
292
+
293
+ const isSlash = this.caseInsensitivePrompt === '/';
294
+ if (!this.caseInsensitivePrompt || isSlash) {
295
+ // 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
296
+ return false;
297
+ }
298
+
299
+ return INCLUDE_SLASH_COMMAND.name.startsWith(this.caseInsensitivePrompt);
300
+ },
274
301
  inputPlaceholder() {
275
302
  if (this.chatPromptPlaceholder) {
276
303
  return this.chatPromptPlaceholder;
@@ -280,6 +307,9 @@ export default {
280
307
  ? i18n.CHAT_PROMPT_PLACEHOLDER_WITH_COMMANDS
281
308
  : i18n.CHAT_PROMPT_PLACEHOLDER_DEFAULT;
282
309
  },
310
+ hasContextItemSelectionMenu() {
311
+ return Boolean(this.contextItemMenuRef);
312
+ },
283
313
  },
284
314
  watch: {
285
315
  isLoading(newVal) {
@@ -322,28 +352,42 @@ export default {
322
352
  this.setPromptAndFocus();
323
353
  },
324
354
  sendChatPrompt() {
325
- if (!this.displaySubmitButton) {
355
+ if (!this.displaySubmitButton || this.contextItemsMenuIsOpen) {
326
356
  return;
327
357
  }
328
358
  if (this.prompt) {
329
- if (this.prompt === CHAT_RESET_MESSAGE && this.resetDisabled) {
359
+ if (this.caseInsensitivePrompt === CHAT_RESET_MESSAGE && this.resetDisabled) {
360
+ return;
361
+ }
362
+
363
+ if (
364
+ this.caseInsensitivePrompt.startsWith(CHAT_INCLUDE_MESSAGE) &&
365
+ this.hasContextItemSelectionMenu
366
+ ) {
367
+ this.contextItemsMenuIsOpen = true;
330
368
  return;
331
369
  }
370
+
371
+ if (
372
+ ![CHAT_RESET_MESSAGE, CHAT_CLEAN_MESSAGE, CHAT_CLEAR_MESSAGE].includes(
373
+ this.caseInsensitivePrompt
374
+ )
375
+ ) {
376
+ this.displaySubmitButton = false;
377
+ }
378
+
332
379
  /**
333
380
  * Emitted when a new user prompt should be sent out.
334
381
  *
335
382
  * @param {String} prompt The user prompt to send.
336
383
  */
337
-
338
- if (![CHAT_RESET_MESSAGE, CHAT_CLEAN_MESSAGE, CHAT_CLEAR_MESSAGE].includes(this.prompt)) {
339
- this.displaySubmitButton = false;
340
- }
341
384
  this.$emit('send-chat-prompt', this.prompt.trim());
342
385
 
343
386
  this.setPromptAndFocus();
344
387
  }
345
388
  },
346
389
  sendPredefinedPrompt(prompt) {
390
+ this.contextItemsMenuIsOpen = false;
347
391
  this.prompt = prompt;
348
392
  this.sendChatPrompt();
349
393
  },
@@ -379,6 +423,18 @@ export default {
379
423
  onInputKeyup(e) {
380
424
  const { key } = e;
381
425
 
426
+ if (this.contextItemsMenuIsOpen) {
427
+ if (!this.shouldShowContextItemSelectionMenu) {
428
+ this.contextItemsMenuIsOpen = false;
429
+ }
430
+ this.contextItemMenuRef?.handleKeyUp(e);
431
+ return;
432
+ }
433
+ if (this.caseInsensitivePrompt === INCLUDE_SLASH_COMMAND.name) {
434
+ this.contextItemsMenuIsOpen = true;
435
+ return;
436
+ }
437
+
382
438
  if (this.shouldShowSlashCommands) {
383
439
  e.preventDefault();
384
440
 
@@ -426,6 +482,10 @@ export default {
426
482
  this.sendChatPrompt();
427
483
  } else {
428
484
  this.setPromptAndFocus(`${command.name} `);
485
+
486
+ if (command.name === CHAT_INCLUDE_MESSAGE && this.hasContextItemSelectionMenu) {
487
+ this.contextItemsMenuIsOpen = true;
488
+ }
429
489
  }
430
490
  },
431
491
  onInsertCodeSnippet(e) {
@@ -435,6 +495,13 @@ export default {
435
495
  */
436
496
  this.$emit('insert-code-snippet', e);
437
497
  },
498
+ closeContextItemsMenuOpen() {
499
+ this.contextItemsMenuIsOpen = false;
500
+ this.setPromptAndFocus();
501
+ },
502
+ setContextItemsMenuRef(ref) {
503
+ this.contextItemMenuRef = ref;
504
+ },
438
505
  },
439
506
  i18n,
440
507
  emptySvg,
@@ -561,6 +628,19 @@ export default {
561
628
  :class="{ 'duo-chat-drawer-body-scrim-on-footer': !scrolledToBottom }"
562
629
  >
563
630
  <gl-form data-testid="chat-prompt-form" @submit.stop.prevent="sendChatPrompt">
631
+ <div class="gl-relative gl-max-w-full">
632
+ <!--
633
+ @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" ...`
634
+ -->
635
+ <slot
636
+ name="context-items-menu"
637
+ :is-open="contextItemsMenuIsOpen"
638
+ :on-close="closeContextItemsMenuOpen"
639
+ :set-ref="setContextItemsMenuRef"
640
+ :focus-prompt="focusChatInput"
641
+ ></slot>
642
+ </div>
643
+
564
644
  <gl-form-input-group>
565
645
  <div
566
646
  class="duo-chat-input gl-min-h-8 gl-max-w-full gl-grow gl-rounded-base gl-bg-white gl-align-top gl-shadow-inner-1-gray-400"
@@ -4,6 +4,7 @@ import {
4
4
  MESSAGE_MODEL_ROLES,
5
5
  CHAT_RESET_MESSAGE,
6
6
  CHAT_CLEAN_MESSAGE,
7
+ CHAT_INCLUDE_MESSAGE,
7
8
  } from './constants';
8
9
  import { getMockContextItems } from './components/duo_chat_context/mock_context_data';
9
10
 
@@ -170,3 +171,8 @@ export const SLASH_COMMANDS = [
170
171
  description: 'Explain the selected snippet.',
171
172
  },
172
173
  ];
174
+
175
+ export const INCLUDE_SLASH_COMMAND = {
176
+ name: CHAT_INCLUDE_MESSAGE,
177
+ description: 'Include additional context in the conversation.',
178
+ };
package/src/index.js CHANGED
@@ -100,6 +100,7 @@ export { default as GlAccordionItem } from './components/base/accordion/accordio
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
102
  export { default as GlDuoChat } from './components/experimental/duo/chat/duo_chat.vue';
103
+ export { default as GlDuoChatContextItemMenu } from './components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu.vue';
103
104
 
104
105
  // Utilities
105
106
  export { default as GlAnimatedNumber } from './components/utilities/animated_number/animated_number.vue';