@gitlab/duo-ui 8.9.1 → 8.10.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
+ # [8.10.0](https://gitlab.com/gitlab-org/duo-ui/compare/v8.9.1...v8.10.0) (2025-04-02)
2
+
3
+
4
+ ### Features
5
+
6
+ * Emit event on Duo message "Copy" ([7c1b460](https://gitlab.com/gitlab-org/duo-ui/commit/7c1b46079ed97593a5342f7896bb5359b5482fad))
7
+
1
8
  ## [8.9.1](https://gitlab.com/gitlab-org/duo-ui/compare/v8.9.0...v8.9.1) (2025-03-31)
2
9
 
3
10
 
@@ -58,6 +58,9 @@ var script = {
58
58
  onCopyCodeSnippet(e) {
59
59
  this.$emit('copy-code-snippet', e);
60
60
  },
61
+ onCopyMessage(e) {
62
+ this.$emit('copy-message', e);
63
+ },
61
64
  onGetContextItemContent(e) {
62
65
  this.$emit('get-context-item-content', e);
63
66
  }
@@ -69,7 +72,7 @@ var script = {
69
72
  const __vue_script__ = script;
70
73
 
71
74
  /* template */
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)};
75
+ 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,"copy-message":_vm.onCopyMessage,"get-context-item-content":_vm.onGetContextItemContent}})})],2)};
73
76
  var __vue_staticRenderFns__ = [];
74
77
 
75
78
  /* style */
@@ -1,16 +1,7 @@
1
1
  import { createButton } from './buttons_utils';
2
2
  import { createTooltip } from './tooltips_utils';
3
+ import { checkClipboardPermissions } from './utils';
3
4
 
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
- };
14
5
  class CopyCodeElement extends HTMLElement {
15
6
  constructor() {
16
7
  super();
@@ -8,7 +8,7 @@ import DocumentationSources from '../duo_chat_message_sources/duo_chat_message_s
8
8
  import { renderDuoChatMarkdownPreview } from '../../markdown_renderer';
9
9
  import { CopyCodeElement } from './copy_code_element';
10
10
  import { InsertCodeSnippetElement } from './insert_code_snippet_element';
11
- import { concatUntilEmpty } from './utils';
11
+ import { concatUntilEmpty, checkClipboardPermissions } from './utils';
12
12
  import { DUO_CODE_SCRIM_BOTTOM_CLASS, DUO_CODE_SCRIM_OFFSET, DUO_CODE_SCRIM_TOP_CLASS } from './constants';
13
13
  import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
14
14
 
@@ -255,8 +255,17 @@ var script = {
255
255
  }
256
256
  },
257
257
  async copyMessage() {
258
- if (navigator.clipboard && !this.copied) {
259
- await navigator.clipboard.writeText(this.message.content);
258
+ const hasPermissions = await checkClipboardPermissions();
259
+ if (!this.copied) {
260
+ if (hasPermissions) {
261
+ await navigator.clipboard.writeText(this.message.content);
262
+ } else {
263
+ this.$emit('copy-message', {
264
+ detail: {
265
+ message: this.message.content
266
+ }
267
+ });
268
+ }
260
269
  this.copied = true;
261
270
  }
262
271
  }
@@ -4,5 +4,18 @@ const concatUntilEmpty = arr => {
4
4
  if (end < 0) end = arr.length;
5
5
  return arr.slice(0, end).join('');
6
6
  };
7
+ const checkClipboardPermissions = async () => {
8
+ try {
9
+ if (!navigator.clipboard || !navigator.permissions) {
10
+ return false;
11
+ }
12
+ const permission = await navigator.permissions.query({
13
+ name: 'clipboard-write'
14
+ });
15
+ return permission.state === 'granted';
16
+ } catch (error) {
17
+ return false;
18
+ }
19
+ };
7
20
 
8
- export { concatUntilEmpty };
21
+ export { checkClipboardPermissions, concatUntilEmpty };
@@ -75,7 +75,7 @@ var script = {
75
75
  const __vue_script__ = script;
76
76
 
77
77
  /* template */
78
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"gl-p-5"},[_c('div',{staticClass:"gl-bg-gray-50 gl-text-gray-500 gl-p-4 gl-mb-5 gl-rounded-base",attrs:{"data-testid":"chat-threads-info-banner"}},[_c('p',{staticClass:"gl-m-0 gl-flex"},[_c('gl-icon',{staticClass:"gl-mr-4",attrs:{"name":"bulb"}}),_vm._v(_vm._s(_vm.$options.i18n.CHAT_HISTORY_INFO)+"\n ")],1)]),_vm._v(" "),(_vm.hasThreads)?_vm._l((_vm.groupedThreads),function(threadsForDate,date){return _c('div',{key:date},[_c('div',{staticClass:"gl-font-bold gl-neutral-900 gl-my-4",attrs:{"data-testid":"chat-threads-date-header"}},[_vm._v("\n "+_vm._s(_vm.formattedLocalDate(date))+"\n ")]),_vm._v(" "),_c('div',_vm._l((threadsForDate),function(thread){return _c('div',{key:thread.id,staticClass:"gl-flex gl-align-center"},[_c('gl-button',{staticClass:"hover:gl-bg-gray-50 gl-text-ellipsis !gl-text-default justify-content-start gl-overflow-hidden gl-rounded-base gl-rounded-base gl-p-4 gl-w-full gl-whitespace-nowrap",attrs:{"data-testid":"chat-threads-thread-box","category":"tertiary","aria-label":_vm.sprintf(_vm.$options.i18n.OPEN_CHAT_LABEL, { title: thread.title || 'Untitled Chat' })},on:{"click":function($event){return _vm.onSelectThread(thread)}}},[_vm._v("\n "+_vm._s(thread.title || 'Untitled Chat')+"\n ")]),_vm._v(" "),(_vm.threads.length > 1)?_c('gl-button',{staticClass:"gl-neutral-900 !gl-p-4",attrs:{"data-testid":"chat-threads-delete-thread-button","icon":"remove","category":"tertiary","size":"small","aria-label":_vm.$options.i18n.THREAD_DELETE_LABEL},on:{"click":function($event){return _vm.$emit('delete-thread', thread.id)}}}):_vm._e()],1)}),0)])}):_c('duo-chat-threads-empty')],2)};
78
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"gl-p-5"},[_c('div',{staticClass:"gl-bg-gray-50 gl-text-gray-500 gl-p-4 gl-mb-5 gl-rounded-base",attrs:{"data-testid":"chat-threads-info-banner"}},[_c('p',{staticClass:"gl-m-0 gl-flex"},[_c('gl-icon',{staticClass:"gl-mr-4",attrs:{"name":"bulb"}}),_vm._v(_vm._s(_vm.$options.i18n.CHAT_HISTORY_INFO)+"\n ")],1)]),_vm._v(" "),(_vm.hasThreads)?_vm._l((_vm.groupedThreads),function(threadsForDate,date){return _c('div',{key:date},[_c('div',{staticClass:"gl-font-bold gl-neutral-900 gl-my-4",attrs:{"data-testid":"chat-threads-date-header"}},[_vm._v("\n "+_vm._s(_vm.formattedLocalDate(date))+"\n ")]),_vm._v(" "),_c('div',_vm._l((threadsForDate),function(thread){return _c('div',{key:thread.id,staticClass:"gl-flex gl-align-center"},[_c('gl-button',{staticClass:"hover:gl-bg-gray-50 gl-text-ellipsis !gl-text-default justify-content-start gl-overflow-hidden gl-rounded-base gl-rounded-base gl-p-4 gl-w-full gl-whitespace-nowrap",attrs:{"data-testid":"chat-threads-thread-box","category":"tertiary","aria-label":_vm.sprintf(_vm.$options.i18n.OPEN_CHAT_LABEL, { title: thread.title || 'Untitled Chat' })},on:{"click":function($event){return _vm.onSelectThread(thread)}}},[_vm._v("\n "+_vm._s(thread.title || 'Untitled Chat')+"\n ")]),_vm._v(" "),_c('gl-button',{staticClass:"gl-neutral-900 !gl-p-4",attrs:{"data-testid":"chat-threads-delete-thread-button","icon":"remove","category":"tertiary","size":"small","aria-label":_vm.$options.i18n.THREAD_DELETE_LABEL},on:{"click":function($event){return _vm.$emit('delete-thread', thread.id)}}})],1)}),0)])}):_c('duo-chat-threads-empty')],2)};
79
79
  var __vue_staticRenderFns__ = [];
80
80
 
81
81
  /* style */
@@ -562,6 +562,13 @@ var script = {
562
562
  */
563
563
  this.$emit('copy-code-snippet', e);
564
564
  },
565
+ onCopyMessage(e) {
566
+ /**
567
+ * Emit copy-message event that clients can use to copy chat message content.
568
+ * @param {*} event An event containing code string in the "detail.message" field.
569
+ */
570
+ this.$emit('copy-message', e);
571
+ },
565
572
  onGetContextItemContent(event) {
566
573
  /**
567
574
  * Emit get-context-item-content event that tells clients to load the full file content for a selected context item.
@@ -605,7 +612,7 @@ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=
605
612
  },attrs:{"width":_vm.shouldRenderResizable ? _vm.dimensions.width : null,"height":_vm.shouldRenderResizable ? _vm.dimensions.height : null,"max-width":_vm.shouldRenderResizable ? _vm.dimensions.maxWidth : null,"max-height":_vm.shouldRenderResizable ? _vm.dimensions.maxHeight : null,"min-width":_vm.shouldRenderResizable ? _vm.dimensions.minWidth : null,"left":_vm.shouldRenderResizable ? _vm.dimensions.left : null,"top":_vm.shouldRenderResizable ? _vm.dimensions.top : null,"fit-parent":true,"min-height":_vm.shouldRenderResizable ? _vm.dimensions.minHeight : null,"active":_vm.shouldRenderResizable ? ['l', 't', 'lt'] : null},on:{"resize:end":_vm.updateSize}},[(!_vm.isHidden)?_c('aside',{staticClass:"markdown-code-block duo-chat gl-bottom-0 gl-max-h-full gl-flex gl-flex-col",class:{
606
613
  'resizable-content': _vm.shouldRenderResizable,
607
614
  'duo-chat-drawer': !_vm.shouldRenderResizable,
608
- },attrs:{"id":"chat-component","role":"complementary","data-testid":"chat-component"}},[(_vm.showHeader)?_c('duo-chat-header',{ref:"header",attrs:{"active-thread-id":_vm.activeThreadId,"title":_vm.isMultithreaded && _vm.currentView === 'list' ? _vm.$options.i18n.CHAT_HISTORY_TITLE : _vm.title,"subtitle":_vm.activeThreadTitleForView,"error":_vm.error,"is-multithreaded":_vm.isMultithreaded,"current-view":_vm.currentView,"should-render-resizable":_vm.shouldRenderResizable,"badge-type":_vm.isMultithreaded ? null : _vm.badgeType},on:{"go-back":_vm.onGoBack,"new-chat":_vm.onNewChat,"close":_vm.hideChat},scopedSlots:_vm._u([{key:"subheader",fn:function(){return [_vm._t("subheader")]},proxy:true}],null,true)}):_vm._e(),_vm._v(" "),_c('div',{staticClass:"gl-overflow-y-auto gl-flex gl-flex-col gl-flex-1 gl-flex-grow gl-bg-inherit gl-overscroll-contain",attrs:{"data-testid":"chat-history"},on:{"scroll":_vm.handleScrollingThrottled}},[(_vm.shouldShowThreadList)?_c('duo-chat-threads',{attrs:{"threads":_vm.threadList,"preferred-locale":_vm.preferredLocale},on:{"new-chat":_vm.onNewChat,"select-thread":_vm.onSelectThread,"delete-thread":_vm.onDeleteThread,"close":_vm.hideChat}}):_c('transition-group',{staticClass:"duo-chat-history gl-p-5 gl-mt-auto",attrs:{"mode":"out-in","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 && !_vm.shouldShowThreadList)?_c('footer',{staticClass:"duo-chat-drawer-footer gl-border-0 gl-bg-default gl-pb-3 gl-shrink-0 gl-relative gl-z-2",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()],1):_vm._e()])};
615
+ },attrs:{"id":"chat-component","role":"complementary","data-testid":"chat-component"}},[(_vm.showHeader)?_c('duo-chat-header',{ref:"header",attrs:{"active-thread-id":_vm.activeThreadId,"title":_vm.isMultithreaded && _vm.currentView === 'list' ? _vm.$options.i18n.CHAT_HISTORY_TITLE : _vm.title,"subtitle":_vm.activeThreadTitleForView,"error":_vm.error,"is-multithreaded":_vm.isMultithreaded,"current-view":_vm.currentView,"should-render-resizable":_vm.shouldRenderResizable,"badge-type":_vm.isMultithreaded ? null : _vm.badgeType},on:{"go-back":_vm.onGoBack,"new-chat":_vm.onNewChat,"close":_vm.hideChat},scopedSlots:_vm._u([{key:"subheader",fn:function(){return [_vm._t("subheader")]},proxy:true}],null,true)}):_vm._e(),_vm._v(" "),_c('div',{staticClass:"gl-overflow-y-auto gl-flex gl-flex-col gl-flex-1 gl-flex-grow gl-bg-inherit gl-overscroll-contain",attrs:{"data-testid":"chat-history"},on:{"scroll":_vm.handleScrollingThrottled}},[(_vm.shouldShowThreadList)?_c('duo-chat-threads',{attrs:{"threads":_vm.threadList,"preferred-locale":_vm.preferredLocale},on:{"new-chat":_vm.onNewChat,"select-thread":_vm.onSelectThread,"delete-thread":_vm.onDeleteThread,"close":_vm.hideChat}}):_c('transition-group',{staticClass:"duo-chat-history gl-p-5 gl-mt-auto",attrs:{"mode":"out-in","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,"copy-message":_vm.onCopyMessage,"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 && !_vm.shouldShowThreadList)?_c('footer',{staticClass:"duo-chat-drawer-footer gl-border-0 gl-bg-default gl-pb-3 gl-shrink-0 gl-relative gl-z-2",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()],1):_vm._e()])};
609
616
  var __vue_staticRenderFns__ = [];
610
617
 
611
618
  /* style */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/duo-ui",
3
- "version": "8.9.1",
3
+ "version": "8.10.0",
4
4
  "description": "Duo UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -60,6 +60,9 @@ export default {
60
60
  onCopyCodeSnippet(e) {
61
61
  this.$emit('copy-code-snippet', e);
62
62
  },
63
+ onCopyMessage(e) {
64
+ this.$emit('copy-message', e);
65
+ },
63
66
  onGetContextItemContent(e) {
64
67
  this.$emit('get-context-item-content', e);
65
68
  },
@@ -88,6 +91,7 @@ export default {
88
91
  @track-feedback="onTrackFeedback"
89
92
  @insert-code-snippet="onInsertCodeSnippet"
90
93
  @copy-code-snippet="onCopyCodeSnippet"
94
+ @copy-message="onCopyMessage"
91
95
  @get-context-item-content="onGetContextItemContent"
92
96
  />
93
97
  </div>
@@ -1,14 +1,6 @@
1
1
  import { createButton } from './buttons_utils';
2
2
  import { createTooltip } from './tooltips_utils';
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
- };
3
+ import { checkClipboardPermissions } from './utils';
12
4
 
13
5
  export class CopyCodeElement extends HTMLElement {
14
6
  constructor() {
@@ -20,7 +20,7 @@ import DocumentationSources from '../duo_chat_message_sources/duo_chat_message_s
20
20
  import { renderDuoChatMarkdownPreview } from '../../markdown_renderer';
21
21
  import { CopyCodeElement } from './copy_code_element';
22
22
  import { InsertCodeSnippetElement } from './insert_code_snippet_element';
23
- import { concatUntilEmpty } from './utils';
23
+ import { checkClipboardPermissions, concatUntilEmpty } from './utils';
24
24
  import {
25
25
  DUO_CODE_SCRIM_BOTTOM_CLASS,
26
26
  DUO_CODE_SCRIM_OFFSET,
@@ -295,8 +295,18 @@ export default {
295
295
  }
296
296
  },
297
297
  async copyMessage() {
298
- if (navigator.clipboard && !this.copied) {
299
- await navigator.clipboard.writeText(this.message.content);
298
+ const hasPermissions = await checkClipboardPermissions();
299
+
300
+ if (!this.copied) {
301
+ if (hasPermissions) {
302
+ await navigator.clipboard.writeText(this.message.content);
303
+ } else {
304
+ this.$emit('copy-message', {
305
+ detail: {
306
+ message: this.message.content,
307
+ },
308
+ });
309
+ }
300
310
  this.copied = true;
301
311
  }
302
312
  },
@@ -7,3 +7,16 @@ export const concatUntilEmpty = (arr) => {
7
7
 
8
8
  return arr.slice(0, end).join('');
9
9
  };
10
+
11
+ export const checkClipboardPermissions = async () => {
12
+ try {
13
+ if (!navigator.clipboard || !navigator.permissions) {
14
+ return false;
15
+ }
16
+
17
+ const permission = await navigator.permissions.query({ name: 'clipboard-write' });
18
+ return permission.state === 'granted';
19
+ } catch (error) {
20
+ return false;
21
+ }
22
+ };
@@ -124,7 +124,6 @@ export default {
124
124
  </gl-button>
125
125
 
126
126
  <gl-button
127
- v-if="threads.length > 1"
128
127
  data-testid="chat-threads-delete-thread-button"
129
128
  icon="remove"
130
129
  category="tertiary"
@@ -634,6 +634,13 @@ export default {
634
634
  */
635
635
  this.$emit('copy-code-snippet', e);
636
636
  },
637
+ onCopyMessage(e) {
638
+ /**
639
+ * Emit copy-message event that clients can use to copy chat message content.
640
+ * @param {*} event An event containing code string in the "detail.message" field.
641
+ */
642
+ this.$emit('copy-message', e);
643
+ },
637
644
  onGetContextItemContent(event) {
638
645
  /**
639
646
  * Emit get-context-item-content event that tells clients to load the full file content for a selected context item.
@@ -750,6 +757,7 @@ export default {
750
757
  @track-feedback="onTrackFeedback"
751
758
  @insert-code-snippet="onInsertCodeSnippet"
752
759
  @copy-code-snippet="onCopyCodeSnippet"
760
+ @copy-message="onCopyMessage"
753
761
  @get-context-item-content="onGetContextItemContent"
754
762
  />
755
763
  <template v-if="!hasMessages && !isLoading">