@gitlab/duo-ui 8.1.0 → 8.2.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,80 @@
1
+ # [8.2.0](https://gitlab.com/gitlab-org/duo-ui/compare/v8.1.0...v8.2.0) (2025-02-04)
2
+
3
+
4
+ ### Features
5
+
6
+ * Call clipboard API when env has permissions ([7be69e7](https://gitlab.com/gitlab-org/duo-ui/commit/7be69e752d955301e86bde3dafc1afb7db23248d))
7
+
8
+ # [8.2.0](https://gitlab.com/gitlab-org/duo-ui/compare/v8.1.0...v8.2.0) (2025-02-04)
9
+
10
+
11
+ ### Features
12
+
13
+ * Call clipboard API when env has permissions ([7be69e7](https://gitlab.com/gitlab-org/duo-ui/commit/7be69e752d955301e86bde3dafc1afb7db23248d))
14
+
15
+ # [8.2.0](https://gitlab.com/gitlab-org/duo-ui/compare/v8.1.0...v8.2.0) (2025-02-04)
16
+
17
+
18
+ ### Features
19
+
20
+ * Call clipboard API when env has permissions ([7be69e7](https://gitlab.com/gitlab-org/duo-ui/commit/7be69e752d955301e86bde3dafc1afb7db23248d))
21
+
22
+ # [8.2.0](https://gitlab.com/gitlab-org/duo-ui/compare/v8.1.0...v8.2.0) (2025-02-04)
23
+
24
+
25
+ ### Features
26
+
27
+ * Call clipboard API when env has permissions ([7be69e7](https://gitlab.com/gitlab-org/duo-ui/commit/7be69e752d955301e86bde3dafc1afb7db23248d))
28
+
29
+ # [8.2.0](https://gitlab.com/gitlab-org/duo-ui/compare/v8.1.0...v8.2.0) (2025-02-04)
30
+
31
+
32
+ ### Features
33
+
34
+ * Call clipboard API when env has permissions ([7be69e7](https://gitlab.com/gitlab-org/duo-ui/commit/7be69e752d955301e86bde3dafc1afb7db23248d))
35
+
36
+ # [8.2.0](https://gitlab.com/gitlab-org/duo-ui/compare/v8.1.0...v8.2.0) (2025-02-04)
37
+
38
+
39
+ ### Features
40
+
41
+ * Call clipboard API when env has permissions ([7be69e7](https://gitlab.com/gitlab-org/duo-ui/commit/7be69e752d955301e86bde3dafc1afb7db23248d))
42
+
43
+ # [8.2.0](https://gitlab.com/gitlab-org/duo-ui/compare/v8.1.0...v8.2.0) (2025-02-04)
44
+
45
+
46
+ ### Features
47
+
48
+ * Call clipboard API when env has permissions ([7be69e7](https://gitlab.com/gitlab-org/duo-ui/commit/7be69e752d955301e86bde3dafc1afb7db23248d))
49
+
50
+ # [8.2.0](https://gitlab.com/gitlab-org/duo-ui/compare/v8.1.0...v8.2.0) (2025-02-04)
51
+
52
+
53
+ ### Features
54
+
55
+ * Call clipboard API when env has permissions ([7be69e7](https://gitlab.com/gitlab-org/duo-ui/commit/7be69e752d955301e86bde3dafc1afb7db23248d))
56
+
57
+ # [8.2.0](https://gitlab.com/gitlab-org/duo-ui/compare/v8.1.0...v8.2.0) (2025-02-03)
58
+
59
+
60
+ ### Features
61
+
62
+ * Call clipboard API when env has permissions ([7be69e7](https://gitlab.com/gitlab-org/duo-ui/commit/7be69e752d955301e86bde3dafc1afb7db23248d))
63
+
64
+ # [8.2.0](https://gitlab.com/gitlab-org/duo-ui/compare/v8.1.0...v8.2.0) (2025-02-03)
65
+
66
+
67
+ ### Features
68
+
69
+ * Call clipboard API when env has permissions ([7be69e7](https://gitlab.com/gitlab-org/duo-ui/commit/7be69e752d955301e86bde3dafc1afb7db23248d))
70
+
71
+ # [8.2.0](https://gitlab.com/gitlab-org/duo-ui/compare/v8.1.0...v8.2.0) (2025-02-03)
72
+
73
+
74
+ ### Features
75
+
76
+ * Call clipboard API when env has permissions ([7be69e7](https://gitlab.com/gitlab-org/duo-ui/commit/7be69e752d955301e86bde3dafc1afb7db23248d))
77
+
1
78
  # [8.1.0](https://gitlab.com/gitlab-org/duo-ui/compare/v8.0.0...v8.1.0) (2025-01-30)
2
79
 
3
80
 
@@ -55,6 +55,9 @@ var script = {
55
55
  onInsertCodeSnippet(e) {
56
56
  this.$emit('insert-code-snippet', e);
57
57
  },
58
+ onCopyCodeSnippet(e) {
59
+ this.$emit('copy-code-snippet', e);
60
+ },
58
61
  onGetContextItemContent(e) {
59
62
  this.$emit('get-context-item-content', e);
60
63
  }
@@ -66,7 +69,7 @@ var script = {
66
69
  const __vue_script__ = script;
67
70
 
68
71
  /* template */
69
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{class:['gl-flex gl-flex-col gl-justify-end', { 'insert-code-hidden': !_vm.enableCodeInsertion }]},[(_vm.showDelimiter)?_c('div',{staticClass:"gl-my-5 gl-flex gl-items-center gl-gap-4 gl-text-gray-500",attrs:{"data-testid":"conversation-delimiter"}},[_c('hr',{staticClass:"gl-grow"}),_vm._v(" "),_c('span',[_vm._v(_vm._s(_vm.$options.i18n.CONVERSATION_NEW_CHAT))]),_vm._v(" "),_c('hr',{staticClass:"gl-grow"})]):_vm._e(),_vm._v(" "),_vm._l((_vm.messages),function(msg,index){return _c('duo-chat-message',{key:((msg.role) + "-" + index),attrs:{"message":msg,"is-cancelled":_vm.canceledRequestIds.includes(msg.requestId)},on:{"track-feedback":_vm.onTrackFeedback,"insert-code-snippet":_vm.onInsertCodeSnippet,"get-context-item-content":_vm.onGetContextItemContent}})})],2)};
72
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{class:['gl-flex gl-flex-col gl-justify-end', { 'insert-code-hidden': !_vm.enableCodeInsertion }]},[(_vm.showDelimiter)?_c('div',{staticClass:"gl-my-5 gl-flex gl-items-center gl-gap-4 gl-text-gray-500",attrs:{"data-testid":"conversation-delimiter"}},[_c('hr',{staticClass:"gl-grow"}),_vm._v(" "),_c('span',[_vm._v(_vm._s(_vm.$options.i18n.CONVERSATION_NEW_CHAT))]),_vm._v(" "),_c('hr',{staticClass:"gl-grow"})]):_vm._e(),_vm._v(" "),_vm._l((_vm.messages),function(msg,index){return _c('duo-chat-message',{key:((msg.role) + "-" + index),attrs:{"message":msg,"is-cancelled":_vm.canceledRequestIds.includes(msg.requestId)},on:{"track-feedback":_vm.onTrackFeedback,"insert-code-snippet":_vm.onInsertCodeSnippet,"copy-code-snippet":_vm.onCopyCodeSnippet,"get-context-item-content":_vm.onGetContextItemContent}})})],2)};
70
73
  var __vue_staticRenderFns__ = [];
71
74
 
72
75
  /* style */
@@ -1,6 +1,16 @@
1
1
  import { createButton } from './buttons_utils';
2
2
  import { createTooltip } from './tooltips_utils';
3
3
 
4
+ const checkClipboardPermissions = async () => {
5
+ try {
6
+ const permission = await navigator.permissions.query({
7
+ name: 'clipboard-write'
8
+ });
9
+ return permission.state === 'granted';
10
+ } catch (error) {
11
+ return false;
12
+ }
13
+ };
4
14
  class CopyCodeElement extends HTMLElement {
5
15
  constructor() {
6
16
  super();
@@ -15,7 +25,22 @@ class CopyCodeElement extends HTMLElement {
15
25
  const [codeElement] = wrapper.getElementsByTagName('code');
16
26
  btn.addEventListener('click', async () => {
17
27
  const textToCopy = codeElement.innerText;
18
- await navigator.clipboard.writeText(textToCopy);
28
+ const hasClipboardPermission = await checkClipboardPermissions();
29
+ try {
30
+ codeElement.dispatchEvent(new CustomEvent('copy-code-snippet', {
31
+ bubbles: true,
32
+ cancelable: true,
33
+ detail: {
34
+ code: textToCopy
35
+ }
36
+ }));
37
+ if (hasClipboardPermission) {
38
+ await navigator.clipboard.writeText(textToCopy);
39
+ }
40
+ } catch (e) {
41
+ // eslint-disable-next-line no-console
42
+ console.warn('Failed to copy snippet:', e);
43
+ }
19
44
  });
20
45
  }
21
46
  }
@@ -211,6 +211,9 @@ var script = {
211
211
  onInsertCodeSnippet(e) {
212
212
  this.$emit('insert-code-snippet', e);
213
213
  },
214
+ onCopyCodeSnippet(e) {
215
+ this.$emit('copy-code-snippet', e);
216
+ },
214
217
  onGetContextItemContent(contextItem) {
215
218
  this.$emit('get-context-item-content', {
216
219
  messageId: this.message.id,
@@ -251,7 +254,7 @@ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=
251
254
  _vm.isAssistantMessage,
252
255
  'gl-bg-subtle': _vm.isAssistantMessage && !_vm.error,
253
256
  'duo-chat-message-with-error gl-bg-red-50': _vm.error,
254
- },on:{"insert-code-snippet":_vm.onInsertCodeSnippet}},[(_vm.error)?_c('gl-icon',{staticClass:"error-icon gl-border gl-mr-3 gl-shrink-0 gl-rounded-full gl-border-red-500 gl-text-red-600",attrs:{"aria-label":_vm.$options.i18n.MESSAGE_ERROR,"name":"status_warning_borderless","size":16,"data-testid":"error"}}):_vm._e(),_vm._v(" "),_c('div',{ref:"content-wrapper",class:{ 'has-error': _vm.error }},[(_vm.displaySelectedContextItems && _vm.isAssistantMessage)?_c('duo-chat-context-item-selections',{attrs:{"selections":_vm.selectedContextItems,"title":_vm.selectedContextItemsTitle,"default-collapsed":_vm.selectedContextItemsDefaultCollapsed,"variant":"assistant"},on:{"get-content":_vm.onGetContextItemContent}}):_vm._e(),_vm._v(" "),(_vm.error)?_c('div',{directives:[{name:"safe-html",rawName:"v-safe-html:[$options.safeHtmlConfigExtension]",value:(_vm.renderedError),expression:"renderedError",arg:_vm.$options.safeHtmlConfigExtension}],ref:"error-message"}):_c('div',[_c('div',{directives:[{name:"safe-html",rawName:"v-safe-html:[$options.safeHtmlConfigExtension]",value:(_vm.messageContent),expression:"messageContent",arg:_vm.$options.safeHtmlConfigExtension}],ref:"content"}),_vm._v(" "),(_vm.isAssistantMessage)?[(_vm.sources)?_c('documentation-sources',{attrs:{"sources":_vm.sources}}):_vm._e(),_vm._v(" "),_c('div',{staticClass:"duo-chat-message-feedback gl-mt-4 gl-flex gl-items-end"},[(_vm.isChunkAndNotCancelled)?_c('gl-animated-loader-icon',{attrs:{"is-on":true}}):_vm._e(),_vm._v(" "),(_vm.isNotChunkOrCancelled)?_c('gl-duo-user-feedback',{attrs:{"feedback-received":_vm.hasFeedback,"modal-title":_vm.$options.i18n.MODAL.TITLE,"modal-alert":_vm.$options.i18n.MODAL.ALERT_TEXT},on:{"feedback":_vm.logEvent},scopedSlots:_vm._u([{key:"feedback-extra-fields",fn:function(){return [_c('gl-form-group',{attrs:{"label":_vm.$options.i18n.MODAL.DID_WHAT,"optional":""}},[_c('gl-form-textarea',{attrs:{"placeholder":_vm.$options.i18n.MODAL.INTERACTION},model:{value:(_vm.didWhat),callback:function ($$v) {_vm.didWhat=$$v;},expression:"didWhat"}})],1),_vm._v(" "),_c('gl-form-group',{attrs:{"label":_vm.$options.i18n.MODAL.IMPROVE_WHAT,"optional":""}},[_c('gl-form-textarea',{attrs:{"placeholder":_vm.$options.i18n.MODAL.BETTER_RESPONSE},model:{value:(_vm.improveWhat),callback:function ($$v) {_vm.improveWhat=$$v;},expression:"improveWhat"}})],1)]},proxy:true}],null,false,419229417)}):_vm._e()],1)]:_vm._e()],2),_vm._v(" "),(_vm.displaySelectedContextItems && _vm.isUserMessage)?_c('duo-chat-context-item-selections',{attrs:{"selections":_vm.selectedContextItems,"title":_vm.selectedContextItemsTitle,"default-collapsed":_vm.selectedContextItemsDefaultCollapsed,"variant":"user"},on:{"get-content":_vm.onGetContextItemContent}}):_vm._e()],1)],1)};
257
+ },on:{"insert-code-snippet":_vm.onInsertCodeSnippet,"copy-code-snippet":_vm.onCopyCodeSnippet}},[(_vm.error)?_c('gl-icon',{staticClass:"error-icon gl-border gl-mr-3 gl-shrink-0 gl-rounded-full gl-border-red-500 gl-text-red-600",attrs:{"aria-label":_vm.$options.i18n.MESSAGE_ERROR,"name":"status_warning_borderless","size":16,"data-testid":"error"}}):_vm._e(),_vm._v(" "),_c('div',{ref:"content-wrapper",class:{ 'has-error': _vm.error }},[(_vm.displaySelectedContextItems && _vm.isAssistantMessage)?_c('duo-chat-context-item-selections',{attrs:{"selections":_vm.selectedContextItems,"title":_vm.selectedContextItemsTitle,"default-collapsed":_vm.selectedContextItemsDefaultCollapsed,"variant":"assistant"},on:{"get-content":_vm.onGetContextItemContent}}):_vm._e(),_vm._v(" "),(_vm.error)?_c('div',{directives:[{name:"safe-html",rawName:"v-safe-html:[$options.safeHtmlConfigExtension]",value:(_vm.renderedError),expression:"renderedError",arg:_vm.$options.safeHtmlConfigExtension}],ref:"error-message"}):_c('div',[_c('div',{directives:[{name:"safe-html",rawName:"v-safe-html:[$options.safeHtmlConfigExtension]",value:(_vm.messageContent),expression:"messageContent",arg:_vm.$options.safeHtmlConfigExtension}],ref:"content"}),_vm._v(" "),(_vm.isAssistantMessage)?[(_vm.sources)?_c('documentation-sources',{attrs:{"sources":_vm.sources}}):_vm._e(),_vm._v(" "),_c('div',{staticClass:"duo-chat-message-feedback gl-mt-4 gl-flex gl-items-end"},[(_vm.isChunkAndNotCancelled)?_c('gl-animated-loader-icon',{attrs:{"is-on":true}}):_vm._e(),_vm._v(" "),(_vm.isNotChunkOrCancelled)?_c('gl-duo-user-feedback',{attrs:{"feedback-received":_vm.hasFeedback,"modal-title":_vm.$options.i18n.MODAL.TITLE,"modal-alert":_vm.$options.i18n.MODAL.ALERT_TEXT},on:{"feedback":_vm.logEvent},scopedSlots:_vm._u([{key:"feedback-extra-fields",fn:function(){return [_c('gl-form-group',{attrs:{"label":_vm.$options.i18n.MODAL.DID_WHAT,"optional":""}},[_c('gl-form-textarea',{attrs:{"placeholder":_vm.$options.i18n.MODAL.INTERACTION},model:{value:(_vm.didWhat),callback:function ($$v) {_vm.didWhat=$$v;},expression:"didWhat"}})],1),_vm._v(" "),_c('gl-form-group',{attrs:{"label":_vm.$options.i18n.MODAL.IMPROVE_WHAT,"optional":""}},[_c('gl-form-textarea',{attrs:{"placeholder":_vm.$options.i18n.MODAL.BETTER_RESPONSE},model:{value:(_vm.improveWhat),callback:function ($$v) {_vm.improveWhat=$$v;},expression:"improveWhat"}})],1)]},proxy:true}],null,false,419229417)}):_vm._e()],1)]:_vm._e()],2),_vm._v(" "),(_vm.displaySelectedContextItems && _vm.isUserMessage)?_c('duo-chat-context-item-selections',{attrs:{"selections":_vm.selectedContextItems,"title":_vm.selectedContextItemsTitle,"default-collapsed":_vm.selectedContextItemsDefaultCollapsed,"variant":"user"},on:{"get-content":_vm.onGetContextItemContent}}):_vm._e()],1)],1)};
255
258
  var __vue_staticRenderFns__ = [];
256
259
 
257
260
  /* style */
@@ -468,6 +468,13 @@ var script = {
468
468
  */
469
469
  this.$emit('insert-code-snippet', e);
470
470
  },
471
+ onCopyCodeSnippet(e) {
472
+ /**
473
+ * Emit copy-code-snippet event that clients can use to interact with a suggested code.
474
+ * @param {*} event An event containing code string in the "detail.code" field.
475
+ */
476
+ this.$emit('copy-code-snippet', e);
477
+ },
471
478
  onGetContextItemContent(event) {
472
479
  /**
473
480
  * Emit get-context-item-content event that tells clients to load the full file content for a selected context item.
@@ -503,7 +510,7 @@ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=
503
510
  {
504
511
  'gl-h-full': !_vm.hasMessages,
505
512
  'force-scroll-bar': _vm.hasMessages,
506
- } ],attrs:{"tag":"section","name":"message"}},[_vm._l((_vm.conversations),function(conversation,index){return _c('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,"get-context-item-content":_vm.onGetContextItemContent}})}),_vm._v(" "),(!_vm.hasMessages && !_vm.isLoading)?[_c('div',{key:"empty-state-message",staticClass:"duo-chat-message gl-rounded-bl-none gl-border-1 gl-border-solid gl-border-gray-50 gl-bg-gray-10 gl-p-4 gl-leading-20 gl-text-gray-900 gl-break-anywhere",attrs:{"data-testid":"gl-duo-chat-empty-state"}},[(_vm.emptyStateTitle)?_c('p',{staticClass:"gl-m-0",attrs:{"data-testid":"gl-duo-chat-empty-state-title"}},[_vm._v("\n "+_vm._s(_vm.emptyStateTitle)+"\n ")]):_vm._e(),_vm._v(" "),_c('duo-chat-predefined-prompts',{key:"predefined-prompts",attrs:{"prompts":_vm.predefinedPrompts},on:{"click":_vm.sendPredefinedPrompt}})],1)]:_vm._e(),_vm._v(" "),(_vm.isLoading)?_c('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-0 gl-bg-default gl-pb-3",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-full",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-full",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,608602988)},[_c('div',{staticClass:"duo-chat-input gl-min-h-8 gl-max-w-full gl-grow gl-align-top",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-subtle"},[_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)])],1),_vm._v(" "),_c('p',{staticClass:"gl-mb-0 gl-mt-3 gl-px-4 gl-text-sm gl-text-secondary"},[_vm._v("\n "+_vm._s(_vm.$options.i18n.CHAT_DISCLAMER)+"\n ")])],1):_vm._e()]):_vm._e()])};
513
+ } ],attrs:{"tag":"section","name":"message"}},[_vm._l((_vm.conversations),function(conversation,index){return _c('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,"copy-code-snippet":_vm.onCopyCodeSnippet,"get-context-item-content":_vm.onGetContextItemContent}})}),_vm._v(" "),(!_vm.hasMessages && !_vm.isLoading)?[_c('div',{key:"empty-state-message",staticClass:"duo-chat-message gl-rounded-bl-none gl-border-1 gl-border-solid gl-border-gray-50 gl-bg-gray-10 gl-p-4 gl-leading-20 gl-text-gray-900 gl-break-anywhere",attrs:{"data-testid":"gl-duo-chat-empty-state"}},[(_vm.emptyStateTitle)?_c('p',{staticClass:"gl-m-0",attrs:{"data-testid":"gl-duo-chat-empty-state-title"}},[_vm._v("\n "+_vm._s(_vm.emptyStateTitle)+"\n ")]):_vm._e(),_vm._v(" "),_c('duo-chat-predefined-prompts',{key:"predefined-prompts",attrs:{"prompts":_vm.predefinedPrompts},on:{"click":_vm.sendPredefinedPrompt}})],1)]:_vm._e(),_vm._v(" "),(_vm.isLoading)?_c('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-0 gl-bg-default gl-pb-3",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-full",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-full",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,608602988)},[_c('div',{staticClass:"duo-chat-input gl-min-h-8 gl-max-w-full gl-grow gl-align-top",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-subtle"},[_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)])],1),_vm._v(" "),_c('p',{staticClass:"gl-mb-0 gl-mt-3 gl-px-4 gl-text-sm gl-text-secondary"},[_vm._v("\n "+_vm._s(_vm.$options.i18n.CHAT_DISCLAMER)+"\n ")])],1):_vm._e()]):_vm._e()])};
507
514
  var __vue_staticRenderFns__ = [];
508
515
 
509
516
  /* style */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/duo-ui",
3
- "version": "8.1.0",
3
+ "version": "8.2.0",
4
4
  "description": "Duo UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -57,6 +57,9 @@ export default {
57
57
  onInsertCodeSnippet(e) {
58
58
  this.$emit('insert-code-snippet', e);
59
59
  },
60
+ onCopyCodeSnippet(e) {
61
+ this.$emit('copy-code-snippet', e);
62
+ },
60
63
  onGetContextItemContent(e) {
61
64
  this.$emit('get-context-item-content', e);
62
65
  },
@@ -84,6 +87,7 @@ export default {
84
87
  :is-cancelled="canceledRequestIds.includes(msg.requestId)"
85
88
  @track-feedback="onTrackFeedback"
86
89
  @insert-code-snippet="onInsertCodeSnippet"
90
+ @copy-code-snippet="onCopyCodeSnippet"
87
91
  @get-context-item-content="onGetContextItemContent"
88
92
  />
89
93
  </div>
@@ -1,6 +1,15 @@
1
1
  import { createButton } from './buttons_utils';
2
2
  import { createTooltip } from './tooltips_utils';
3
3
 
4
+ const checkClipboardPermissions = async () => {
5
+ try {
6
+ const permission = await navigator.permissions.query({ name: 'clipboard-write' });
7
+ return permission.state === 'granted';
8
+ } catch (error) {
9
+ return false;
10
+ }
11
+ };
12
+
4
13
  export class CopyCodeElement extends HTMLElement {
5
14
  constructor() {
6
15
  super();
@@ -19,7 +28,26 @@ export class CopyCodeElement extends HTMLElement {
19
28
 
20
29
  btn.addEventListener('click', async () => {
21
30
  const textToCopy = codeElement.innerText;
22
- await navigator.clipboard.writeText(textToCopy);
31
+ const hasClipboardPermission = await checkClipboardPermissions();
32
+
33
+ try {
34
+ codeElement.dispatchEvent(
35
+ new CustomEvent('copy-code-snippet', {
36
+ bubbles: true,
37
+ cancelable: true,
38
+ detail: {
39
+ code: textToCopy,
40
+ },
41
+ })
42
+ );
43
+
44
+ if (hasClipboardPermission) {
45
+ await navigator.clipboard.writeText(textToCopy);
46
+ }
47
+ } catch (e) {
48
+ // eslint-disable-next-line no-console
49
+ console.warn('Failed to copy snippet:', e);
50
+ }
23
51
  });
24
52
  }
25
53
  }
@@ -245,6 +245,9 @@ export default {
245
245
  onInsertCodeSnippet(e) {
246
246
  this.$emit('insert-code-snippet', e);
247
247
  },
248
+ onCopyCodeSnippet(e) {
249
+ this.$emit('copy-code-snippet', e);
250
+ },
248
251
  onGetContextItemContent(contextItem) {
249
252
  this.$emit('get-context-item-content', {
250
253
  messageId: this.message.id,
@@ -289,6 +292,7 @@ export default {
289
292
  'duo-chat-message-with-error gl-bg-red-50': error,
290
293
  }"
291
294
  @insert-code-snippet="onInsertCodeSnippet"
295
+ @copy-code-snippet="onCopyCodeSnippet"
292
296
  >
293
297
  <gl-icon
294
298
  v-if="error"
@@ -529,6 +529,13 @@ export default {
529
529
  */
530
530
  this.$emit('insert-code-snippet', e);
531
531
  },
532
+ onCopyCodeSnippet(e) {
533
+ /**
534
+ * Emit copy-code-snippet event that clients can use to interact with a suggested code.
535
+ * @param {*} event An event containing code string in the "detail.code" field.
536
+ */
537
+ this.$emit('copy-code-snippet', e);
538
+ },
532
539
  onGetContextItemContent(event) {
533
540
  /**
534
541
  * Emit get-context-item-content event that tells clients to load the full file content for a selected context item.
@@ -645,6 +652,7 @@ export default {
645
652
  :show-delimiter="index > 0"
646
653
  @track-feedback="onTrackFeedback"
647
654
  @insert-code-snippet="onInsertCodeSnippet"
655
+ @copy-code-snippet="onCopyCodeSnippet"
648
656
  @get-context-item-content="onGetContextItemContent"
649
657
  />
650
658
  <template v-if="!hasMessages && !isLoading">