@gitlab/duo-ui 8.16.0 → 8.16.2

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,18 @@
1
+ ## [8.16.2](https://gitlab.com/gitlab-org/duo-ui/compare/v8.16.1...v8.16.2) (2025-05-28)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * correct config option for DOMPurify ([5085b3c](https://gitlab.com/gitlab-org/duo-ui/commit/5085b3cc3e6c9e690aef7d2d35644076a9b7ea50))
7
+ * fixed nested code blocks ([1a1953a](https://gitlab.com/gitlab-org/duo-ui/commit/1a1953aab48ecaf72160be24f29f0be48ff79a7f))
8
+
9
+ ## [8.16.1](https://gitlab.com/gitlab-org/duo-ui/compare/v8.16.0...v8.16.1) (2025-05-21)
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * **chat:** Update Agentic Chat disclaimer ([8277327](https://gitlab.com/gitlab-org/duo-ui/commit/827732738f2dc7bf1f7e67a9ddb48593e291706f))
15
+
1
16
  # [8.16.0](https://gitlab.com/gitlab-org/duo-ui/compare/v8.15.0...v8.16.0) (2025-05-14)
2
17
 
3
18
 
@@ -14,7 +14,7 @@ import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
14
14
  const i18n = {
15
15
  CHAT_DEFAULT_TITLE: translate('AgenticDuoChat.chatDefaultTitle', 'GitLab Duo Chat'),
16
16
  CHAT_HISTORY_TITLE: translate('AgenticDuoChat.chatHistoryTitle', 'Chat history'),
17
- CHAT_DISCLAMER: translate('AgenticDuoChat.chatDisclamer', 'Responses may be inaccurate. Verify before use.'),
17
+ CHAT_DISCLAMER: translate('AgenticDuoChat.chatDisclamer', 'Chat can autonomously change code. Responses and changes can be inaccurate. Review carefully.'),
18
18
  CHAT_EMPTY_STATE_TITLE: translate('AgenticDuoChat.chatEmptyStateTitle', '👋 I am GitLab Duo Chat, your personal AI-powered assistant. How can I help you today?'),
19
19
  CHAT_PROMPT_PLACEHOLDER_DEFAULT: translate('AgenticDuoChat.chatPromptPlaceholderDefault', 'GitLab Duo Chat'),
20
20
  CHAT_PROMPT_PLACEHOLDER_WITH_COMMANDS: translate('AgenticDuoChat.chatPromptPlaceholderWithCommands', 'Type /help to learn more'),
@@ -9,6 +9,7 @@ const i18n = {
9
9
  CHAT_NEW_LABEL: translate('DuoChat.chatNewLabel', 'New Chat'),
10
10
  CHAT_NEW_TOOLTIP: translate('DuoChat.chatNewToolTip', 'New Chat'),
11
11
  CHAT_HISTORY_TOOLTIP: translate('DuoChat.chatHistoryToolTip', 'Chat History'),
12
+ CHAT_BACK_TO_CHAT_TOOLTIP: translate('DuoChat.chatBackToChatToolTip', 'Back to Chat'),
12
13
  CHAT_TITLE: translate('DuoChat.chatTitle', 'GitLab Duo Chat')
13
14
  };
14
15
  var script = {
@@ -75,7 +76,7 @@ var script = {
75
76
  const __vue_script__ = script;
76
77
 
77
78
  /* template */
78
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('header',{staticClass:"gl-border-b gl-bg-default gl-p-0 gl-shrink-0",attrs:{"data-testid":"chat-header"}},[_c('div',{staticClass:"drawer-title gl-flex gl-items-center gl-justify-start gl-p-5"},[(_vm.isMultithreaded && _vm.currentView === _vm.VIEW_TYPES.CHAT)?_c('gl-button',{directives:[{name:"gl-tooltip",rawName:"v-gl-tooltip"}],staticClass:"gl-mr-3",attrs:{"title":_vm.$options.i18n.CHAT_HISTORY_TOOLTIP,"data-testid":"chat-back-button","category":"primary","size":"medium","icon":"history","aria-label":_vm.$options.i18n.CHAT_BACK_LABEL},on:{"click":function($event){return _vm.$emit('go-back')}}}):_vm._e(),_vm._v(" "),(_vm.isMultithreaded && (_vm.activeThreadId || _vm.currentView === _vm.VIEW_TYPES.LIST))?_c('gl-button',{directives:[{name:"gl-tooltip",rawName:"v-gl-tooltip"}],staticClass:"gl-mr-3",attrs:{"title":_vm.$options.i18n.CHAT_NEW_TOOLTIP,"data-testid":"chat-new-button","category":"primary","size":"medium","icon":"duo-chat-new","aria-label":_vm.$options.i18n.CHAT_NEW_LABEL},on:{"click":function($event){return _vm.$emit('new-chat')}}}):_vm._e(),_vm._v(" "),_c('div',{staticClass:"gl-flex-1 gl-overflow-hidden"},[_c('div',{staticClass:"gl-flex gl-items-center"},[_c('h3',{staticClass:"gl-my-0 gl-text-size-h2"},[_vm._v(_vm._s(_vm.title))]),_vm._v(" "),(_vm.badgeType)?_c('gl-experiment-badge',{attrs:{"type":_vm.badgeType,"container-id":"chat-component"}}):_vm._e()],1),_vm._v(" "),(_vm.subtitle)?_c('h4',{staticClass:"gl-text-base gl-whitespace-nowrap gl-overflow-hidden gl-text-ellipsis gl-font-normal text-muted gl-mb-0",attrs:{"data-testid":"chat-subtitle"}},[_vm._v("\n "+_vm._s(_vm.subtitle)+"\n ")]):_vm._e()]),_vm._v(" "),_c('gl-button',{staticClass:"gl-ml-auto",attrs:{"category":"tertiary","variant":"default","icon":"close","size":"small","data-testid":"chat-close-button","aria-label":_vm.$options.i18n.CHAT_CLOSE_LABEL},on:{"click":function($event){return _vm.$emit('close')}}})],1),_vm._v(" "),_vm._t("subheader"),_vm._v(" "),(_vm.error)?_c('gl-alert',{key:"error",staticClass:"!gl-pl-9",attrs:{"dismissible":false,"variant":"danger","role":"alert","data-testid":"chat-error"}},[_c('span',{directives:[{name:"safe-html",rawName:"v-safe-html",value:(_vm.error),expression:"error"}]})]):_vm._e()],2)};
79
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('header',{staticClass:"gl-border-b gl-bg-default gl-p-0 gl-shrink-0",attrs:{"data-testid":"chat-header"}},[_c('div',{staticClass:"drawer-title gl-flex gl-items-center gl-justify-start gl-p-5"},[(_vm.isMultithreaded && _vm.activeThreadId && _vm.currentView === _vm.VIEW_TYPES.LIST)?_c('gl-button',{directives:[{name:"gl-tooltip",rawName:"v-gl-tooltip"}],staticClass:"gl-mr-3",attrs:{"title":_vm.$options.i18n.CHAT_BACK_TO_CHAT_TOOLTIP,"data-testid":"go-back-to-chat-button","category":"primary","size":"medium","icon":"go-back","aria-label":_vm.$options.i18n.CHAT_BACK_TO_CHAT_TOOLTIP},on:{"click":function($event){return _vm.$emit('go-back-to-chat')}}}):_vm._e(),_vm._v(" "),(_vm.isMultithreaded && _vm.currentView === _vm.VIEW_TYPES.CHAT)?_c('gl-button',{directives:[{name:"gl-tooltip",rawName:"v-gl-tooltip"}],staticClass:"gl-mr-3",attrs:{"title":_vm.$options.i18n.CHAT_HISTORY_TOOLTIP,"data-testid":"go-back-to-list-button","category":"primary","size":"medium","icon":"history","aria-label":_vm.$options.i18n.CHAT_BACK_LABEL},on:{"click":function($event){return _vm.$emit('go-back')}}}):_vm._e(),_vm._v(" "),(_vm.isMultithreaded && (_vm.activeThreadId || _vm.currentView === _vm.VIEW_TYPES.LIST))?_c('gl-button',{directives:[{name:"gl-tooltip",rawName:"v-gl-tooltip"}],staticClass:"gl-mr-3",attrs:{"title":_vm.$options.i18n.CHAT_NEW_TOOLTIP,"data-testid":"chat-new-button","category":"primary","size":"medium","icon":"duo-chat-new","aria-label":_vm.$options.i18n.CHAT_NEW_LABEL},on:{"click":function($event){return _vm.$emit('new-chat')}}}):_vm._e(),_vm._v(" "),_c('div',{staticClass:"gl-flex-1 gl-overflow-hidden"},[_c('div',{staticClass:"gl-flex gl-items-center"},[_c('h3',{staticClass:"gl-my-0 gl-text-size-h2"},[_vm._v(_vm._s(_vm.title))]),_vm._v(" "),(_vm.badgeType)?_c('gl-experiment-badge',{attrs:{"type":_vm.badgeType,"container-id":"chat-component"}}):_vm._e()],1),_vm._v(" "),(_vm.subtitle)?_c('h4',{staticClass:"gl-text-base gl-whitespace-nowrap gl-overflow-hidden gl-text-ellipsis gl-font-normal text-muted gl-mb-0",attrs:{"data-testid":"chat-subtitle"}},[_vm._v("\n "+_vm._s(_vm.subtitle)+"\n ")]):_vm._e()]),_vm._v(" "),_c('gl-button',{staticClass:"gl-ml-auto",attrs:{"category":"tertiary","variant":"default","icon":"close","size":"small","data-testid":"chat-close-button","aria-label":_vm.$options.i18n.CHAT_CLOSE_LABEL},on:{"click":function($event){return _vm.$emit('close')}}})],1),_vm._v(" "),_vm._t("subheader"),_vm._v(" "),(_vm.error)?_c('gl-alert',{key:"error",staticClass:"!gl-pl-9",attrs:{"dismissible":false,"variant":"danger","role":"alert","data-testid":"chat-error"}},[_c('span',{directives:[{name:"safe-html",rawName:"v-safe-html",value:(_vm.error),expression:"error"}]})]):_vm._e()],2)};
79
80
  var __vue_staticRenderFns__ = [];
80
81
 
81
82
  /* style */
@@ -392,6 +392,9 @@ var script = {
392
392
  onGoBack() {
393
393
  this.$emit('back-to-list');
394
394
  },
395
+ onGoBackToChat() {
396
+ this.$emit('back-to-chat');
397
+ },
395
398
  onNewChat() {
396
399
  this.$emit('new-chat');
397
400
  this.$nextTick(() => {
@@ -627,7 +630,7 @@ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=
627
630
  },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:{
628
631
  'resizable-content': _vm.shouldRenderResizable,
629
632
  'duo-chat-drawer': !_vm.shouldRenderResizable,
630
- },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,"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,"trusted-urls":_vm.trustedUrls},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,"open-file-path":_vm.onOpenFilePath}})}),_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()])};
633
+ },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,"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,"go-back-to-chat":_vm.onGoBackToChat,"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,"trusted-urls":_vm.trustedUrls},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,"open-file-path":_vm.onOpenFilePath}})}),_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()])};
631
634
  var __vue_staticRenderFns__ = [];
632
635
 
633
636
  /* style */
@@ -9,7 +9,7 @@ const duoMarked = new Marked([{
9
9
  gfm: false
10
10
  }, markedBidi()]);
11
11
  const config = {
12
- ALLOW_TAGS: ['p', '#text', 'div', 'code', 'insert-code-snippet', 'gl-markdown', 'pre', 'span', 'gl-compact-markdown', 'copy-code'],
12
+ ADD_TAGS: ['insert-code-snippet', 'copy-code', 'gl-markdown', '#text', 'gl-compact-markdown'],
13
13
  ADD_ATTR: ['data-canonical-lang', 'data-sourcepos', 'lang', 'data-src', 'img'],
14
14
  FORBID_TAGS: ['script', 'style', 'iframe', 'form', 'button'],
15
15
  FORBID_ATTR: ['onerror', 'onload', 'onclick']
@@ -96,6 +96,22 @@ const sanitizeLinksHook = function () {
96
96
  node.removeAttribute('href');
97
97
  };
98
98
  };
99
+ function isHtml(markup) {
100
+ const src = markup.toString().trim();
101
+ if (src.length === 0 || !src.startsWith('<') || !src.includes('>')) {
102
+ return false; // fast-fail trivial cases
103
+ }
104
+ const doc = new DOMParser().parseFromString(src, 'text/html');
105
+
106
+ // DOMParser drops invalid tags but inserts a <parsererror> element on failure
107
+ // https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#error_handling
108
+ if (doc.querySelector('parsererror') !== null) {
109
+ return false;
110
+ }
111
+
112
+ // Do we have at least one real element node inside <body>?
113
+ return Array.from(doc.body.childNodes).some(n => n.nodeType === Node.ELEMENT_NODE);
114
+ }
99
115
  function renderDuoChatMarkdownPreview(md) {
100
116
  let {
101
117
  trustedUrls = []
@@ -103,7 +119,7 @@ function renderDuoChatMarkdownPreview(md) {
103
119
  if (!md) return '';
104
120
  DOMPurify.addHook('beforeSanitizeElements', handleImageElements);
105
121
  DOMPurify.addHook('afterSanitizeAttributes', sanitizeLinksHook(trustedUrls));
106
- const parsedMarkdown = duoMarked.parse(md.toString());
122
+ const parsedMarkdown = isHtml(md) ? md : duoMarked.parse(md.toString());
107
123
  const sanitized = DOMPurify.sanitize(parsedMarkdown, config);
108
124
  DOMPurify.removeHook('beforeSanitizeElements');
109
125
  DOMPurify.removeHook('afterSanitizeAttributes');
@@ -19,8 +19,40 @@ const MOCK_SOURCES = [{
19
19
  }];
20
20
  const MOCK_RESPONSE_MESSAGE = {
21
21
  id: '123',
22
- content: 'Here is a simple JavaScript function to sum two numbers:\n\n ```js\n function sum(a, b) {\n return a + b;\n }\n ```\n \n To use it:\n \n ```js\n const result = sum(5, 3); // result = 8\n ```\n \n This function takes two number parameters, a and b. It returns the sum of adding them together.\n',
23
- contentHtml: '<p data-sourcepos="1:1-1:56" dir="auto">Here is a simple JavaScript function to sum two numbers:</p>\n<div class="gl-relative markdown-code-block js-markdown-code">\n<pre data-sourcepos="3:1-7:3" data-canonical-lang="js" class="code highlight js-syntax-highlight language-javascript" lang="javascript" v-pre="true"><code><span id="LC1" class="line" lang="javascript"><span class="kd">function</span> <span class="nf">sum</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="p">{</span></span>\n<span id="LC2" class="line" lang="javascript"> <span class="k">return</span> <span class="nx">a</span> <span class="o">+</span> <span class="nx">b</span><span class="p">;</span></span>\n<span id="LC3" class="line" lang="javascript"><span class="p">}</span></span></code></pre>\n<copy-code></copy-code>\n<insert-code-snippet></insert-code-snippet>\n</div>\n<p data-sourcepos="9:1-9:10" dir="auto">To use it:</p>\n<div class="gl-relative markdown-code-block js-markdown-code">\n<pre data-sourcepos="11:1-13:3" data-canonical-lang="js" class="code highlight js-syntax-highlight language-javascript" lang="javascript" v-pre="true"><code><span id="LC1" class="line" lang="javascript"><span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="nf">sum</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span> <span class="c1">// result = 8</span></span></code></pre>\n<copy-code></copy-code>\n</div>\n<p data-sourcepos="15:1-15:95" dir="auto">This function takes two number parameters, a and b. It returns the sum of adding them together.</p>',
22
+ content: 'I\'ll write a simple Python function with comments for you. Here\'s an example:\\\\n\\\\n```python\\\\ndef calculate_factorial(n):\\\\n \\\\\\"\\\\\\"\\\\\\"\\\\n Calculate the factorial of a non-negative integer.\\\\n \\\\n Args:\\\\n n (int): A non-negative integer\\\\n \\\\n Returns:\\\\n int: The factorial of n (n!)\\\\n \\\\n Examples:\\\\n >>> calculate_factorial(5)\\\\n 120\\\\n >>> calculate_factorial(0)\\\\n 1\\\\n \\\\\\"\\\\\\"\\\\\\"\\\\n # Handle base cases\\\\n if n == 0 or n == 1:\\\\n return 1\\\\n \\\\n # Use recursion to calculate factorial\\\\n return n * calculate_factorial(n - 1)\\\\n```\\\\n\\\\nThis function calculates the factorial of a number using recursion. It includes a docstring explaining what the function does, its parameters, return value, and usage examples, plus inline comments explaining the logic.',
23
+ contentHtml: `<p dir="auto">I&#39;d be happy to write a simple Python function with comments for you. </p>
24
+ <p dir="auto">Here&#39;s a basic Python function that calculates the factorial of a number:</p>
25
+ <div class="gl-relative markdown-code-block js-markdown-code"><pre><code class="language-python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">factorial</span>(<span class="hljs-params">n</span>):
26
+ <span class="hljs-string">&quot;&quot;&quot;
27
+ Calculate the factorial of a non-negative integer.
28
+
29
+ Args:
30
+ n (int): A non-negative integer
31
+
32
+ Returns:
33
+ int: The factorial of n (n!)
34
+
35
+ Examples:
36
+ &gt;&gt;&gt; factorial(5)
37
+ 120
38
+ &gt;&gt;&gt; factorial(0)
39
+ 1
40
+ &quot;&quot;&quot;</span>
41
+ <span class="hljs-comment"># Base case: factorial of 0 or 1 is 1</span>
42
+ <span class="hljs-keyword">if</span> n &lt;= <span class="hljs-number">1</span>:
43
+ <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>
44
+
45
+ <span class="hljs-comment"># Recursive case: n! = n * (n-1)!</span>
46
+ <span class="hljs-keyword">return</span> n * factorial(n - <span class="hljs-number">1</span>)
47
+ </code></pre>
48
+ <copy-code></copy-code><insert-code-snippet></insert-code-snippet></div><p dir="auto">This function includes:</p>
49
+ <ul dir="auto">
50
+ <li>A docstring explaining what the function does</li>
51
+ <li>Parameter and return type documentation</li>
52
+ <li>Usage examples</li>
53
+ <li>Inline comments explaining the logic</li>
54
+ </ul>
55
+ <p dir="auto">Is there a specific type of function you&#39;d like me to create instead?</p>`,
24
56
  role: MESSAGE_MODEL_ROLES.assistant,
25
57
  extras: {
26
58
  sources: MOCK_SOURCES,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/duo-ui",
3
- "version": "8.16.0",
3
+ "version": "8.16.2",
4
4
  "description": "Duo UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -100,7 +100,7 @@
100
100
  "@gitlab/eslint-plugin": "20.7.1",
101
101
  "@gitlab/fonts": "^1.3.0",
102
102
  "@gitlab/stylelint-config": "6.2.2",
103
- "@gitlab/svgs": "^3.129.0",
103
+ "@gitlab/svgs": "^3.134.0",
104
104
  "@gitlab/ui": "latest",
105
105
  "@jest/test-sequencer": "^29.7.0",
106
106
  "@rollup/plugin-commonjs": "^11.1.0",
@@ -37,7 +37,7 @@ export const i18n = {
37
37
  CHAT_HISTORY_TITLE: translate('AgenticDuoChat.chatHistoryTitle', 'Chat history'),
38
38
  CHAT_DISCLAMER: translate(
39
39
  'AgenticDuoChat.chatDisclamer',
40
- 'Responses may be inaccurate. Verify before use.'
40
+ 'Chat can autonomously change code. Responses and changes can be inaccurate. Review carefully.'
41
41
  ),
42
42
  CHAT_EMPTY_STATE_TITLE: translate(
43
43
  'AgenticDuoChat.chatEmptyStateTitle',
@@ -15,6 +15,7 @@ export const i18n = {
15
15
  CHAT_NEW_LABEL: translate('DuoChat.chatNewLabel', 'New Chat'),
16
16
  CHAT_NEW_TOOLTIP: translate('DuoChat.chatNewToolTip', 'New Chat'),
17
17
  CHAT_HISTORY_TOOLTIP: translate('DuoChat.chatHistoryToolTip', 'Chat History'),
18
+ CHAT_BACK_TO_CHAT_TOOLTIP: translate('DuoChat.chatBackToChatToolTip', 'Back to Chat'),
18
19
  CHAT_TITLE: translate('DuoChat.chatTitle', 'GitLab Duo Chat'),
19
20
  };
20
21
 
@@ -83,11 +84,23 @@ export default {
83
84
  <template>
84
85
  <header data-testid="chat-header" class="gl-border-b gl-bg-default gl-p-0 gl-shrink-0">
85
86
  <div class="drawer-title gl-flex gl-items-center gl-justify-start gl-p-5">
87
+ <gl-button
88
+ v-if="isMultithreaded && activeThreadId && currentView === VIEW_TYPES.LIST"
89
+ v-gl-tooltip
90
+ :title="$options.i18n.CHAT_BACK_TO_CHAT_TOOLTIP"
91
+ data-testid="go-back-to-chat-button"
92
+ category="primary"
93
+ size="medium"
94
+ icon="go-back"
95
+ class="gl-mr-3"
96
+ :aria-label="$options.i18n.CHAT_BACK_TO_CHAT_TOOLTIP"
97
+ @click="$emit('go-back-to-chat')"
98
+ />
86
99
  <gl-button
87
100
  v-if="isMultithreaded && currentView === VIEW_TYPES.CHAT"
88
101
  v-gl-tooltip
89
102
  :title="$options.i18n.CHAT_HISTORY_TOOLTIP"
90
- data-testid="chat-back-button"
103
+ data-testid="go-back-to-list-button"
91
104
  category="primary"
92
105
  size="medium"
93
106
  icon="history"
@@ -462,6 +462,9 @@ export default {
462
462
  onGoBack() {
463
463
  this.$emit('back-to-list');
464
464
  },
465
+ onGoBackToChat() {
466
+ this.$emit('back-to-chat');
467
+ },
465
468
  onNewChat() {
466
469
  this.$emit('new-chat');
467
470
 
@@ -732,6 +735,7 @@ export default {
732
735
  :should-render-resizable="shouldRenderResizable"
733
736
  :badge-type="isMultithreaded ? null : badgeType"
734
737
  @go-back="onGoBack"
738
+ @go-back-to-chat="onGoBackToChat"
735
739
  @new-chat="onNewChat"
736
740
  @close="hideChat"
737
741
  >
@@ -13,18 +13,7 @@ const duoMarked = new Marked([
13
13
  ]);
14
14
 
15
15
  const config = {
16
- ALLOW_TAGS: [
17
- 'p',
18
- '#text',
19
- 'div',
20
- 'code',
21
- 'insert-code-snippet',
22
- 'gl-markdown',
23
- 'pre',
24
- 'span',
25
- 'gl-compact-markdown',
26
- 'copy-code',
27
- ],
16
+ ADD_TAGS: ['insert-code-snippet', 'copy-code', 'gl-markdown', '#text', 'gl-compact-markdown'],
28
17
  ADD_ATTR: ['data-canonical-lang', 'data-sourcepos', 'lang', 'data-src', 'img'],
29
18
  FORBID_TAGS: ['script', 'style', 'iframe', 'form', 'button'],
30
19
  FORBID_ATTR: ['onerror', 'onload', 'onclick'],
@@ -116,13 +105,32 @@ const sanitizeLinksHook =
116
105
  node.removeAttribute('href');
117
106
  };
118
107
 
108
+ function isHtml(markup) {
109
+ const src = markup.toString().trim();
110
+ if (src.length === 0 || !src.startsWith('<') || !src.includes('>')) {
111
+ return false; // fast-fail trivial cases
112
+ }
113
+
114
+ const doc = new DOMParser().parseFromString(src, 'text/html');
115
+
116
+ // DOMParser drops invalid tags but inserts a <parsererror> element on failure
117
+ // https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#error_handling
118
+ if (doc.querySelector('parsererror') !== null) {
119
+ return false;
120
+ }
121
+
122
+ // Do we have at least one real element node inside <body>?
123
+ return Array.from(doc.body.childNodes).some((n) => n.nodeType === Node.ELEMENT_NODE);
124
+ }
125
+
119
126
  export function renderDuoChatMarkdownPreview(md, { trustedUrls = [] } = {}) {
120
127
  if (!md) return '';
121
128
 
122
129
  DOMPurify.addHook('beforeSanitizeElements', handleImageElements);
123
130
  DOMPurify.addHook('afterSanitizeAttributes', sanitizeLinksHook(trustedUrls));
124
131
 
125
- const parsedMarkdown = duoMarked.parse(md.toString());
132
+ const parsedMarkdown = isHtml(md) ? md : duoMarked.parse(md.toString());
133
+
126
134
  const sanitized = DOMPurify.sanitize(parsedMarkdown, config);
127
135
 
128
136
  DOMPurify.removeHook('beforeSanitizeElements');
@@ -31,9 +31,40 @@ const MOCK_SOURCES = [
31
31
  export const MOCK_RESPONSE_MESSAGE = {
32
32
  id: '123',
33
33
  content:
34
- 'Here is a simple JavaScript function to sum two numbers:\n\n ```js\n function sum(a, b) {\n return a + b;\n }\n ```\n \n To use it:\n \n ```js\n const result = sum(5, 3); // result = 8\n ```\n \n This function takes two number parameters, a and b. It returns the sum of adding them together.\n',
35
- contentHtml:
36
- '<p data-sourcepos="1:1-1:56" dir="auto">Here is a simple JavaScript function to sum two numbers:</p>\n<div class="gl-relative markdown-code-block js-markdown-code">\n<pre data-sourcepos="3:1-7:3" data-canonical-lang="js" class="code highlight js-syntax-highlight language-javascript" lang="javascript" v-pre="true"><code><span id="LC1" class="line" lang="javascript"><span class="kd">function</span> <span class="nf">sum</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="p">{</span></span>\n<span id="LC2" class="line" lang="javascript"> <span class="k">return</span> <span class="nx">a</span> <span class="o">+</span> <span class="nx">b</span><span class="p">;</span></span>\n<span id="LC3" class="line" lang="javascript"><span class="p">}</span></span></code></pre>\n<copy-code></copy-code>\n<insert-code-snippet></insert-code-snippet>\n</div>\n<p data-sourcepos="9:1-9:10" dir="auto">To use it:</p>\n<div class="gl-relative markdown-code-block js-markdown-code">\n<pre data-sourcepos="11:1-13:3" data-canonical-lang="js" class="code highlight js-syntax-highlight language-javascript" lang="javascript" v-pre="true"><code><span id="LC1" class="line" lang="javascript"><span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="nf">sum</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span> <span class="c1">// result = 8</span></span></code></pre>\n<copy-code></copy-code>\n</div>\n<p data-sourcepos="15:1-15:95" dir="auto">This function takes two number parameters, a and b. It returns the sum of adding them together.</p>',
34
+ 'I\'ll write a simple Python function with comments for you. Here\'s an example:\\\\n\\\\n```python\\\\ndef calculate_factorial(n):\\\\n \\\\\\"\\\\\\"\\\\\\"\\\\n Calculate the factorial of a non-negative integer.\\\\n \\\\n Args:\\\\n n (int): A non-negative integer\\\\n \\\\n Returns:\\\\n int: The factorial of n (n!)\\\\n \\\\n Examples:\\\\n >>> calculate_factorial(5)\\\\n 120\\\\n >>> calculate_factorial(0)\\\\n 1\\\\n \\\\\\"\\\\\\"\\\\\\"\\\\n # Handle base cases\\\\n if n == 0 or n == 1:\\\\n return 1\\\\n \\\\n # Use recursion to calculate factorial\\\\n return n * calculate_factorial(n - 1)\\\\n```\\\\n\\\\nThis function calculates the factorial of a number using recursion. It includes a docstring explaining what the function does, its parameters, return value, and usage examples, plus inline comments explaining the logic.',
35
+ contentHtml: `<p dir="auto">I&#39;d be happy to write a simple Python function with comments for you. </p>
36
+ <p dir="auto">Here&#39;s a basic Python function that calculates the factorial of a number:</p>
37
+ <div class="gl-relative markdown-code-block js-markdown-code"><pre><code class="language-python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">factorial</span>(<span class="hljs-params">n</span>):
38
+ <span class="hljs-string">&quot;&quot;&quot;
39
+ Calculate the factorial of a non-negative integer.
40
+
41
+ Args:
42
+ n (int): A non-negative integer
43
+
44
+ Returns:
45
+ int: The factorial of n (n!)
46
+
47
+ Examples:
48
+ &gt;&gt;&gt; factorial(5)
49
+ 120
50
+ &gt;&gt;&gt; factorial(0)
51
+ 1
52
+ &quot;&quot;&quot;</span>
53
+ <span class="hljs-comment"># Base case: factorial of 0 or 1 is 1</span>
54
+ <span class="hljs-keyword">if</span> n &lt;= <span class="hljs-number">1</span>:
55
+ <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>
56
+
57
+ <span class="hljs-comment"># Recursive case: n! = n * (n-1)!</span>
58
+ <span class="hljs-keyword">return</span> n * factorial(n - <span class="hljs-number">1</span>)
59
+ </code></pre>
60
+ <copy-code></copy-code><insert-code-snippet></insert-code-snippet></div><p dir="auto">This function includes:</p>
61
+ <ul dir="auto">
62
+ <li>A docstring explaining what the function does</li>
63
+ <li>Parameter and return type documentation</li>
64
+ <li>Usage examples</li>
65
+ <li>Inline comments explaining the logic</li>
66
+ </ul>
67
+ <p dir="auto">Is there a specific type of function you&#39;d like me to create instead?</p>`,
37
68
  role: MESSAGE_MODEL_ROLES.assistant,
38
69
  extras: {
39
70
  sources: MOCK_SOURCES,
package/translations.js CHANGED
@@ -7,7 +7,8 @@ export default {
7
7
  'AgenticDuoChat.chatDefaultPredefinedPromptsCreateTemplate': 'How do I create a template?',
8
8
  'AgenticDuoChat.chatDefaultPredefinedPromptsForkProject': 'How do I fork a project?',
9
9
  'AgenticDuoChat.chatDefaultTitle': 'GitLab Duo Chat',
10
- 'AgenticDuoChat.chatDisclamer': 'Responses may be inaccurate. Verify before use.',
10
+ 'AgenticDuoChat.chatDisclamer':
11
+ 'Chat can autonomously change code. Responses and changes can be inaccurate. Review carefully.',
11
12
  'AgenticDuoChat.chatEmptyStateTitle':
12
13
  '👋 I am GitLab Duo Chat, your personal AI-powered assistant. How can I help you today?',
13
14
  'AgenticDuoChat.chatHistoryTitle': 'Chat history',
@@ -15,6 +16,7 @@ export default {
15
16
  'AgenticDuoChat.chatPromptPlaceholderWithCommands': 'Type /help to learn more',
16
17
  'AgenticDuoChat.chatSubmitLabel': 'Send chat message.',
17
18
  'DuoChat.chatBackLabel': 'Back to History',
19
+ 'DuoChat.chatBackToChatToolTip': 'Back to Chat',
18
20
  'DuoChat.chatCancelLabel': 'Cancel',
19
21
  'DuoChat.chatDefaultPredefinedPromptsChangePassword': 'How do I change my password in GitLab?',
20
22
  'DuoChat.chatDefaultPredefinedPromptsCloneRepository': 'How do I clone a repository?',