@gitlab/ui 86.5.1 → 86.7.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 +14 -0
- package/dist/components/experimental/duo/chat/components/duo_chat_conversation/duo_chat_conversation.js +13 -1
- package/dist/components/experimental/duo/chat/components/duo_chat_message/buttons_utils.js +25 -0
- package/dist/components/experimental/duo/chat/components/duo_chat_message/copy_code_element.js +2 -21
- package/dist/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.js +9 -2
- package/dist/components/experimental/duo/chat/components/duo_chat_message/insert_code_snippet_element.js +20 -0
- package/dist/components/experimental/duo/chat/duo_chat.js +12 -1
- package/dist/components/experimental/duo/chat/mock_data.js +3 -1
- package/dist/directives/outside/outside.js +89 -39
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/package.json +1 -1
- package/src/components/experimental/duo/chat/components/duo_chat_conversation/duo_chat_conversation.vue +17 -1
- package/src/components/experimental/duo/chat/components/duo_chat_message/buttons_utils.js +30 -0
- package/src/components/experimental/duo/chat/components/duo_chat_message/copy_code_element.js +2 -31
- package/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.scss +19 -2
- package/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.vue +9 -1
- package/src/components/experimental/duo/chat/components/duo_chat_message/insert_code_snippet_element.js +17 -0
- package/src/components/experimental/duo/chat/duo_chat.vue +13 -0
- package/src/components/experimental/duo/chat/mock_data.js +3 -1
- package/src/directives/outside/outside.js +81 -41
- package/src/directives/outside/outside.md +92 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [86.7.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v86.6.0...v86.7.0) (2024-07-11)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **DuoChat:** add insert code snippet button ([6b99777](https://gitlab.com/gitlab-org/gitlab-ui/commit/6b997772587c93d68e6f3e95e15150a9ad59e1b2))
|
|
7
|
+
|
|
8
|
+
# [86.6.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v86.5.1...v86.6.0) (2024-07-10)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **OutsideDirective:** Add Event Listener for focusin ([aee99c5](https://gitlab.com/gitlab-org/gitlab-ui/commit/aee99c56b926d9468030774c4bd33eac01f4f601))
|
|
14
|
+
|
|
1
15
|
## [86.5.1](https://gitlab.com/gitlab-org/gitlab-ui/compare/v86.5.0...v86.5.1) (2024-07-10)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -27,6 +27,13 @@ var script = {
|
|
|
27
27
|
type: Array,
|
|
28
28
|
required: true
|
|
29
29
|
},
|
|
30
|
+
/**
|
|
31
|
+
* Whether the insertCode feature should be available.
|
|
32
|
+
*/
|
|
33
|
+
enableCodeInsertion: {
|
|
34
|
+
type: Boolean,
|
|
35
|
+
required: true
|
|
36
|
+
},
|
|
30
37
|
/**
|
|
31
38
|
* Whether to show the delimiter before this conversation
|
|
32
39
|
*/
|
|
@@ -43,6 +50,9 @@ var script = {
|
|
|
43
50
|
* @param {*} event An event, containing the feedback choices and the extended feedback text.
|
|
44
51
|
*/
|
|
45
52
|
this.$emit('track-feedback', event);
|
|
53
|
+
},
|
|
54
|
+
onInsertCodeSnippet(e) {
|
|
55
|
+
this.$emit('insert-code-snippet', e);
|
|
46
56
|
}
|
|
47
57
|
},
|
|
48
58
|
i18n
|
|
@@ -52,7 +62,9 @@ var script = {
|
|
|
52
62
|
const __vue_script__ = script;
|
|
53
63
|
|
|
54
64
|
/* template */
|
|
55
|
-
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{
|
|
65
|
+
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{class:[
|
|
66
|
+
'gl-display-flex gl-flex-direction-column gl-justify-content-end',
|
|
67
|
+
{ 'insert-code-hidden': !_vm.enableCodeInsertion } ]},[(_vm.showDelimiter)?_c('div',{staticClass:"gl-my-5 gl-display-flex gl-align-items-center gl-gap-4 gl-text-gray-500",attrs:{"data-testid":"conversation-delimiter"}},[_c('hr',{staticClass:"gl-flex-grow-1"}),_vm._v(" "),_c('span',[_vm._v(_vm._s(_vm.$options.i18n.CONVERSATION_NEW_CHAT))]),_vm._v(" "),_c('hr',{staticClass:"gl-flex-grow-1"})]):_vm._e(),_vm._v(" "),_vm._l((_vm.messages),function(msg,index){return _c('gl-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}})})],2)};
|
|
56
68
|
var __vue_staticRenderFns__ = [];
|
|
57
69
|
|
|
58
70
|
/* style */
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import iconsPath from '@gitlab/svgs/dist/icons.svg';
|
|
2
|
+
|
|
3
|
+
const createButton = function () {
|
|
4
|
+
let title = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'Insert the code snippet';
|
|
5
|
+
let iconId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'insert';
|
|
6
|
+
const button = document.createElement('button');
|
|
7
|
+
button.type = 'button';
|
|
8
|
+
button.classList.add('btn', 'btn-default', 'btn-md', 'gl-button', 'btn-default-secondary', 'btn-icon');
|
|
9
|
+
button.dataset.title = title;
|
|
10
|
+
|
|
11
|
+
// Create an SVG element with the correct namespace
|
|
12
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
13
|
+
svg.setAttribute('role', 'img');
|
|
14
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
15
|
+
svg.classList.add('gl-button-icon', 'gl-icon', 's16');
|
|
16
|
+
|
|
17
|
+
// Create a 'use' element with the correct namespace
|
|
18
|
+
const use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
|
|
19
|
+
use.setAttribute('href', `${iconsPath}#${iconId}`);
|
|
20
|
+
svg.appendChild(use);
|
|
21
|
+
button.appendChild(svg);
|
|
22
|
+
return button;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export { createButton };
|
package/dist/components/experimental/duo/chat/components/duo_chat_message/copy_code_element.js
CHANGED
|
@@ -1,28 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { createButton } from './buttons_utils';
|
|
2
2
|
|
|
3
|
-
const createButton = () => {
|
|
4
|
-
const button = document.createElement('button');
|
|
5
|
-
button.type = 'button';
|
|
6
|
-
button.classList.add('btn', 'btn-default', 'btn-md', 'gl-button', 'btn-default-secondary', 'btn-icon');
|
|
7
|
-
button.dataset.title = 'Copy to clipboard';
|
|
8
|
-
|
|
9
|
-
// Create an SVG element with the correct namespace
|
|
10
|
-
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
11
|
-
svg.setAttribute('role', 'img');
|
|
12
|
-
svg.setAttribute('aria-hidden', 'true');
|
|
13
|
-
svg.classList.add('gl-button-icon', 'gl-icon', 's16');
|
|
14
|
-
|
|
15
|
-
// Create a 'use' element with the correct namespace
|
|
16
|
-
const use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
|
|
17
|
-
use.setAttribute('href', `${iconsPath}#copy-to-clipboard`);
|
|
18
|
-
svg.appendChild(use);
|
|
19
|
-
button.appendChild(svg);
|
|
20
|
-
return button;
|
|
21
|
-
};
|
|
22
3
|
class CopyCodeElement extends HTMLElement {
|
|
23
4
|
constructor() {
|
|
24
5
|
super();
|
|
25
|
-
const btn = createButton();
|
|
6
|
+
const btn = createButton('Copy to clipboard', 'copy-to-clipboard');
|
|
26
7
|
const wrapper = this.parentNode;
|
|
27
8
|
this.appendChild(btn);
|
|
28
9
|
btn.addEventListener('click', async () => {
|
package/dist/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.js
CHANGED
|
@@ -9,6 +9,7 @@ import { MESSAGE_MODEL_ROLES } from '../../constants';
|
|
|
9
9
|
import DocumentationSources from '../duo_chat_message_sources/duo_chat_message_sources';
|
|
10
10
|
import { renderDuoChatMarkdownPreview } from '../../markdown_renderer';
|
|
11
11
|
import { CopyCodeElement } from './copy_code_element';
|
|
12
|
+
import { InsertCodeSnippetElement } from './insert_code_snippet_element';
|
|
12
13
|
import { concatUntilEmpty } from './utils';
|
|
13
14
|
import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
|
|
14
15
|
|
|
@@ -27,7 +28,7 @@ var script = {
|
|
|
27
28
|
name: 'GlDuoChatMessage',
|
|
28
29
|
i18n,
|
|
29
30
|
safeHtmlConfigExtension: {
|
|
30
|
-
ADD_TAGS: ['copy-code']
|
|
31
|
+
ADD_TAGS: ['copy-code', 'insert-code-snippet']
|
|
31
32
|
},
|
|
32
33
|
components: {
|
|
33
34
|
DocumentationSources,
|
|
@@ -130,6 +131,9 @@ var script = {
|
|
|
130
131
|
if (!customElements.get('copy-code')) {
|
|
131
132
|
customElements.define('copy-code', CopyCodeElement);
|
|
132
133
|
}
|
|
134
|
+
if (!customElements.get('insert-code-snippet')) {
|
|
135
|
+
customElements.define('insert-code-snippet', InsertCodeSnippetElement);
|
|
136
|
+
}
|
|
133
137
|
},
|
|
134
138
|
mounted() {
|
|
135
139
|
if (this.isAssistantMessage) {
|
|
@@ -180,6 +184,9 @@ var script = {
|
|
|
180
184
|
if (!this.isChunk) {
|
|
181
185
|
this.stopWatchingMessage();
|
|
182
186
|
}
|
|
187
|
+
},
|
|
188
|
+
onInsertCodeSnippet(e) {
|
|
189
|
+
this.$emit('insert-code-snippet', e);
|
|
183
190
|
}
|
|
184
191
|
}
|
|
185
192
|
};
|
|
@@ -194,7 +201,7 @@ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=
|
|
|
194
201
|
_vm.isAssistantMessage,
|
|
195
202
|
'gl-bg-white': _vm.isAssistantMessage && !_vm.error,
|
|
196
203
|
'gl-bg-red-50 gl-border-none!': _vm.error,
|
|
197
|
-
}},[(_vm.error)?_c('gl-icon',{staticClass:"gl-border-red-500 gl-rounded-full gl-mr-3 gl-flex-shrink-0 error-icon gl-border 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.error)?_c('div',{ref:"error-message"},[_vm._v(_vm._s(_vm.error))]):_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:"gl-display-flex gl-align-items-flex-end gl-mt-4 duo-chat-message-feedback"},[(_vm.isChunkAndNotCancelled)?_c('gl-loading-icon',{staticClass:"gl-pt-4",attrs:{"variant":"dots","inline":""}}):_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)])],1)};
|
|
204
|
+
},on:{"insert-code-snippet":_vm.onInsertCodeSnippet}},[(_vm.error)?_c('gl-icon',{staticClass:"gl-border-red-500 gl-rounded-full gl-mr-3 gl-flex-shrink-0 error-icon gl-border 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.error)?_c('div',{ref:"error-message"},[_vm._v(_vm._s(_vm.error))]):_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:"gl-display-flex gl-align-items-flex-end gl-mt-4 duo-chat-message-feedback"},[(_vm.isChunkAndNotCancelled)?_c('gl-loading-icon',{staticClass:"gl-pt-4",attrs:{"variant":"dots","inline":""}}):_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)])],1)};
|
|
198
205
|
var __vue_staticRenderFns__ = [];
|
|
199
206
|
|
|
200
207
|
/* style */
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createButton } from './buttons_utils';
|
|
2
|
+
|
|
3
|
+
class InsertCodeSnippetElement extends HTMLElement {
|
|
4
|
+
constructor(codeBlock) {
|
|
5
|
+
super();
|
|
6
|
+
const btn = createButton();
|
|
7
|
+
const wrapper = codeBlock;
|
|
8
|
+
this.appendChild(btn);
|
|
9
|
+
btn.addEventListener('click', () => {
|
|
10
|
+
if (wrapper) {
|
|
11
|
+
wrapper.dispatchEvent(new CustomEvent('insert-code-snippet', {
|
|
12
|
+
bubbles: true,
|
|
13
|
+
cancelable: true
|
|
14
|
+
}));
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { InsertCodeSnippetElement };
|
|
@@ -108,6 +108,14 @@ var script = {
|
|
|
108
108
|
required: false,
|
|
109
109
|
default: true
|
|
110
110
|
},
|
|
111
|
+
/**
|
|
112
|
+
* Whether the insertCode feature should be available.
|
|
113
|
+
*/
|
|
114
|
+
enableCodeInsertion: {
|
|
115
|
+
type: Boolean,
|
|
116
|
+
required: false,
|
|
117
|
+
default: false
|
|
118
|
+
},
|
|
111
119
|
/**
|
|
112
120
|
* Array of predefined prompts to display in the chat to start a conversation.
|
|
113
121
|
*/
|
|
@@ -391,6 +399,9 @@ var script = {
|
|
|
391
399
|
} else {
|
|
392
400
|
this.setPromptAndFocus(`${command.name} `);
|
|
393
401
|
}
|
|
402
|
+
},
|
|
403
|
+
onInsertCodeSnippet(e) {
|
|
404
|
+
this.$emit('insert-code-snippet', e);
|
|
394
405
|
}
|
|
395
406
|
},
|
|
396
407
|
i18n,
|
|
@@ -405,7 +416,7 @@ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=
|
|
|
405
416
|
{
|
|
406
417
|
'gl-h-full': !_vm.hasMessages,
|
|
407
418
|
'force-scroll-bar': _vm.hasMessages,
|
|
408
|
-
} ],attrs:{"tag":"section","name":"message"}},[_vm._l((_vm.conversations),function(conversation,index){return _c('gl-duo-chat-conversation',{key:("conversation-" + index),attrs:{"messages":conversation,"canceled-request-ids":_vm.canceledRequestIds,"show-delimiter":index > 0},on:{"track-feedback":_vm.onTrackFeedback}})}),_vm._v(" "),(!_vm.hasMessages && !_vm.isLoading)?[_c('gl-empty-state',{key:"empty-state",staticClass:"gl-flex-grow gl-justify-content-center",attrs:{"svg-path":_vm.$options.emptySvg,"svg-height":145,"title":_vm.emptyStateTitle,"description":_vm.emptyStateDescription}}),_vm._v(" "),_c('gl-duo-chat-predefined-prompts',{key:"predefined-prompts",attrs:{"prompts":_vm.predefinedPrompts},on:{"click":_vm.sendPredefinedPrompt}})]:_vm._e(),_vm._v(" "),(_vm.isLoading)?_c('gl-duo-chat-loader',{key:"loader",attrs:{"tool-name":_vm.toolName}}):_vm._e(),_vm._v(" "),_c('div',{key:"anchor",ref:"anchor",staticClass:"scroll-anchor"})],2)],1),_vm._v(" "),(_vm.isChatAvailable)?_c('footer',{staticClass:"duo-chat-drawer-footer duo-chat-drawer-footer-sticky gl-p-5 gl-bg-gray-10 gl-border-t",class:{ 'duo-chat-drawer-body-scrim-on-footer': !_vm.scrolledToBottom },attrs:{"data-testid":"chat-footer"}},[_c('gl-form',{attrs:{"data-testid":"chat-prompt-form"},on:{"submit":function($event){$event.stopPropagation();$event.preventDefault();return _vm.sendChatPrompt.apply(null, arguments)}}},[_c('gl-form-input-group',{scopedSlots:_vm._u([{key:"append",fn:function(){return [(_vm.displaySubmitButton)?_c('gl-button',{staticClass:"gl-rounded-base! !gl-absolute gl-bottom-2 gl-right-2",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-rounded-base! !gl-absolute gl-bottom-2 gl-right-2",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,1231938732)},[_c('div',{staticClass:"duo-chat-input gl-flex-grow-1 gl-vertical-align-top gl-max-w-full gl-min-h-8 gl-inset-border-1-gray-400 gl-rounded-base gl-bg-white",attrs:{"data-value":_vm.prompt}},[(_vm.shouldShowSlashCommands)?_c('gl-card',{ref:"commands",staticClass:"slash-commands gl-translate-y-n100 gl-list-style-none gl-pl-0 gl-w-full gl-shadow-md !gl-absolute",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-display-flex gl-justify-content-space-between"},[_c('span',{staticClass:"gl-display-block"},[_vm._v(_vm._s(command.name))]),_vm._v(" "),_c('small',{staticClass:"gl-font-style-italic gl-text-right gl-pl-3 gl-text-gray-500"},[_vm._v(_vm._s(command.description))])])])}),1):_vm._e(),_vm._v(" "),_c('gl-form-textarea',{ref:"prompt",staticClass:"gl-h-full! gl-py-4! gl-bg-transparent! gl-rounded-top-right-none gl-rounded-bottom-right-none gl-shadow-none! gl-absolute",class:{ 'gl-text-truncate': !_vm.prompt },attrs:{"data-testid":"chat-prompt-input","placeholder":_vm.inputPlaceholder,"autofocus":""},on:{"compositionend":_vm.compositionEnd},nativeOn:{"keydown":function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"enter",13,$event.key,"Enter")){ return null; }if($event.ctrlKey||$event.shiftKey||$event.altKey||$event.metaKey){ return null; }$event.preventDefault();},"keyup":function($event){return _vm.onInputKeyup.apply(null, arguments)}},model:{value:(_vm.prompt),callback:function ($$v) {_vm.prompt=$$v;},expression:"prompt"}})],1)]),_vm._v(" "),_c('gl-form-text',{staticClass:"gl-line-height-20 gl-mt-3 gl-text-gray-400",attrs:{"data-testid":"chat-legal-disclaimer"}},[_vm._v(_vm._s(_vm.$options.i18n.CHAT_LEGAL_DISCLAIMER))])],1)],1):_vm._e()]):_vm._e()};
|
|
419
|
+
} ],attrs:{"tag":"section","name":"message"}},[_vm._l((_vm.conversations),function(conversation,index){return _c('gl-duo-chat-conversation',{key:("conversation-" + index),attrs:{"enable-code-insertion":_vm.enableCodeInsertion,"messages":conversation,"canceled-request-ids":_vm.canceledRequestIds,"show-delimiter":index > 0},on:{"track-feedback":_vm.onTrackFeedback,"insert-code-snippet":_vm.onInsertCodeSnippet}})}),_vm._v(" "),(!_vm.hasMessages && !_vm.isLoading)?[_c('gl-empty-state',{key:"empty-state",staticClass:"gl-flex-grow gl-justify-content-center",attrs:{"svg-path":_vm.$options.emptySvg,"svg-height":145,"title":_vm.emptyStateTitle,"description":_vm.emptyStateDescription}}),_vm._v(" "),_c('gl-duo-chat-predefined-prompts',{key:"predefined-prompts",attrs:{"prompts":_vm.predefinedPrompts},on:{"click":_vm.sendPredefinedPrompt}})]:_vm._e(),_vm._v(" "),(_vm.isLoading)?_c('gl-duo-chat-loader',{key:"loader",attrs:{"tool-name":_vm.toolName}}):_vm._e(),_vm._v(" "),_c('div',{key:"anchor",ref:"anchor",staticClass:"scroll-anchor"})],2)],1),_vm._v(" "),(_vm.isChatAvailable)?_c('footer',{staticClass:"duo-chat-drawer-footer duo-chat-drawer-footer-sticky gl-p-5 gl-bg-gray-10 gl-border-t",class:{ 'duo-chat-drawer-body-scrim-on-footer': !_vm.scrolledToBottom },attrs:{"data-testid":"chat-footer"}},[_c('gl-form',{attrs:{"data-testid":"chat-prompt-form"},on:{"submit":function($event){$event.stopPropagation();$event.preventDefault();return _vm.sendChatPrompt.apply(null, arguments)}}},[_c('gl-form-input-group',{scopedSlots:_vm._u([{key:"append",fn:function(){return [(_vm.displaySubmitButton)?_c('gl-button',{staticClass:"gl-rounded-base! !gl-absolute gl-bottom-2 gl-right-2",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-rounded-base! !gl-absolute gl-bottom-2 gl-right-2",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,1231938732)},[_c('div',{staticClass:"duo-chat-input gl-flex-grow-1 gl-vertical-align-top gl-max-w-full gl-min-h-8 gl-inset-border-1-gray-400 gl-rounded-base gl-bg-white",attrs:{"data-value":_vm.prompt}},[(_vm.shouldShowSlashCommands)?_c('gl-card',{ref:"commands",staticClass:"slash-commands gl-translate-y-n100 gl-list-style-none gl-pl-0 gl-w-full gl-shadow-md !gl-absolute",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-display-flex gl-justify-content-space-between"},[_c('span',{staticClass:"gl-display-block"},[_vm._v(_vm._s(command.name))]),_vm._v(" "),_c('small',{staticClass:"gl-font-style-italic gl-text-right gl-pl-3 gl-text-gray-500"},[_vm._v(_vm._s(command.description))])])])}),1):_vm._e(),_vm._v(" "),_c('gl-form-textarea',{ref:"prompt",staticClass:"gl-h-full! gl-py-4! gl-bg-transparent! gl-rounded-top-right-none gl-rounded-bottom-right-none gl-shadow-none! gl-absolute",class:{ 'gl-text-truncate': !_vm.prompt },attrs:{"data-testid":"chat-prompt-input","placeholder":_vm.inputPlaceholder,"autofocus":""},on:{"compositionend":_vm.compositionEnd},nativeOn:{"keydown":function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"enter",13,$event.key,"Enter")){ return null; }if($event.ctrlKey||$event.shiftKey||$event.altKey||$event.metaKey){ return null; }$event.preventDefault();},"keyup":function($event){return _vm.onInputKeyup.apply(null, arguments)}},model:{value:(_vm.prompt),callback:function ($$v) {_vm.prompt=$$v;},expression:"prompt"}})],1)]),_vm._v(" "),_c('gl-form-text',{staticClass:"gl-line-height-20 gl-mt-3 gl-text-gray-400",attrs:{"data-testid":"chat-legal-disclaimer"}},[_vm._v(_vm._s(_vm.$options.i18n.CHAT_LEGAL_DISCLAIMER))])],1)],1):_vm._e()]):_vm._e()};
|
|
409
420
|
var __vue_staticRenderFns__ = [];
|
|
410
421
|
|
|
411
422
|
/* style */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { setStoryTimeout } from '../../../../utils/test_utils';
|
|
2
|
+
import { InsertCodeSnippetElement } from './components/duo_chat_message/insert_code_snippet_element';
|
|
2
3
|
import { DOCUMENTATION_SOURCE_TYPES, MESSAGE_MODEL_ROLES, CHAT_RESET_MESSAGE, CHAT_CLEAN_MESSAGE } from './constants';
|
|
3
4
|
|
|
4
5
|
const MOCK_SOURCES = [{
|
|
@@ -19,7 +20,7 @@ const MOCK_SOURCES = [{
|
|
|
19
20
|
const MOCK_RESPONSE_MESSAGE = {
|
|
20
21
|
id: '123',
|
|
21
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',
|
|
22
|
-
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</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>',
|
|
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>',
|
|
23
24
|
role: MESSAGE_MODEL_ROLES.assistant,
|
|
24
25
|
extras: {
|
|
25
26
|
sources: MOCK_SOURCES
|
|
@@ -128,6 +129,7 @@ const renderGFM = el => {
|
|
|
128
129
|
const codeBlock = el.querySelectorAll('.markdown-code-block');
|
|
129
130
|
codeBlock.forEach(block => {
|
|
130
131
|
block === null || block === void 0 ? void 0 : block.classList.add('gl-markdown', 'gl-compact-markdown');
|
|
132
|
+
block === null || block === void 0 ? void 0 : block.appendChild(new InsertCodeSnippetElement(block));
|
|
131
133
|
});
|
|
132
134
|
};
|
|
133
135
|
const SLASH_COMMANDS = [{
|
|
@@ -1,19 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Map<HTMLElement, Function>
|
|
2
|
+
* Map<HTMLElement, { callback: Function, eventTypes: Array<string> }>
|
|
3
3
|
*/
|
|
4
4
|
const callbacks = new Map();
|
|
5
|
+
const click = 'click';
|
|
6
|
+
const focusin = 'focusin';
|
|
7
|
+
const supportedEventTypes = [click, focusin];
|
|
8
|
+
const defaultEventType = click;
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
|
-
*
|
|
11
|
+
* A Set to keep track of currently active event types.
|
|
12
|
+
* This ensures that event listeners are only added for the event types that are in use.
|
|
13
|
+
*
|
|
14
|
+
* @type {Set<string>}
|
|
8
15
|
*/
|
|
9
|
-
|
|
16
|
+
const activeEventTypes = new Set();
|
|
10
17
|
let lastMousedown = null;
|
|
11
18
|
const globalListener = event => {
|
|
12
|
-
callbacks.forEach((
|
|
13
|
-
|
|
19
|
+
callbacks.forEach((_ref, element) => {
|
|
20
|
+
let {
|
|
21
|
+
callback,
|
|
22
|
+
eventTypes
|
|
23
|
+
} = _ref;
|
|
24
|
+
const originalEvent = event.type === click ? lastMousedown || event : event;
|
|
14
25
|
if (
|
|
15
26
|
// Ignore events that aren't targeted outside the element
|
|
16
|
-
element.contains(originalEvent.target)
|
|
27
|
+
element.contains(originalEvent.target) ||
|
|
28
|
+
// Ignore events that aren't the specified types for this element
|
|
29
|
+
!eventTypes.includes(event.type)) {
|
|
17
30
|
return;
|
|
18
31
|
}
|
|
19
32
|
try {
|
|
@@ -25,7 +38,9 @@ const globalListener = event => {
|
|
|
25
38
|
}
|
|
26
39
|
}
|
|
27
40
|
});
|
|
28
|
-
|
|
41
|
+
if (event.type === click) {
|
|
42
|
+
lastMousedown = null;
|
|
43
|
+
}
|
|
29
44
|
};
|
|
30
45
|
|
|
31
46
|
// We need to listen for mouse events because text selection fires click event only when selection ends.
|
|
@@ -34,39 +49,72 @@ const globalListener = event => {
|
|
|
34
49
|
const onMousedown = event => {
|
|
35
50
|
lastMousedown = event;
|
|
36
51
|
};
|
|
37
|
-
const startListening =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
52
|
+
const startListening = eventTypes => {
|
|
53
|
+
eventTypes.forEach(eventType => {
|
|
54
|
+
if (!activeEventTypes.has(eventType)) {
|
|
55
|
+
// Listening to mousedown events, ensures that a text selection doesn't trigger the
|
|
56
|
+
// GlOutsideDirective 'click' callback if the selection started within the target element.
|
|
57
|
+
if (eventType === click) {
|
|
58
|
+
document.addEventListener('mousedown', onMousedown);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Added { capture: true } to all event types to prevent the behavior discussed in https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/1686#note_412545027
|
|
62
|
+
// Ensures the event listener handles the event in the capturing phase, avoiding issues encountered previously.
|
|
63
|
+
// Cannot be tested with Jest or Cypress, but can be tested with Playwright in the future: https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/4272#note_1947425384
|
|
64
|
+
document.addEventListener(eventType, globalListener, {
|
|
65
|
+
capture: true
|
|
66
|
+
});
|
|
67
|
+
activeEventTypes.add(eventType);
|
|
68
|
+
}
|
|
47
69
|
});
|
|
48
|
-
listening = true;
|
|
49
70
|
lastMousedown = null;
|
|
50
71
|
};
|
|
51
|
-
const stopListening =
|
|
52
|
-
|
|
53
|
-
|
|
72
|
+
const stopListening = eventTypesToUnbind => {
|
|
73
|
+
eventTypesToUnbind.forEach(eventType => {
|
|
74
|
+
if (activeEventTypes.has(eventType)) {
|
|
75
|
+
if ([...callbacks.values()].every(_ref2 => {
|
|
76
|
+
let {
|
|
77
|
+
eventTypes
|
|
78
|
+
} = _ref2;
|
|
79
|
+
return !eventTypes.includes(eventType);
|
|
80
|
+
})) {
|
|
81
|
+
document.removeEventListener(eventType, globalListener);
|
|
82
|
+
activeEventTypes.delete(eventType);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
if (eventTypesToUnbind.includes(click) && !activeEventTypes.has(click)) {
|
|
87
|
+
document.removeEventListener('mousedown', onMousedown);
|
|
54
88
|
}
|
|
55
|
-
document.removeEventListener('mousedown', onMousedown);
|
|
56
|
-
document.removeEventListener('click', globalListener);
|
|
57
|
-
listening = false;
|
|
58
89
|
};
|
|
59
|
-
|
|
90
|
+
function parseBinding(_ref3) {
|
|
60
91
|
let {
|
|
92
|
+
arg,
|
|
61
93
|
value,
|
|
62
|
-
|
|
63
|
-
} =
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
94
|
+
modifiers
|
|
95
|
+
} = _ref3;
|
|
96
|
+
const modifiersList = Object.keys(modifiers);
|
|
97
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
98
|
+
if (typeof value !== 'function') {
|
|
99
|
+
throw new Error(`[GlOutsideDirective] Value must be a function; got ${typeof value}!`);
|
|
100
|
+
}
|
|
101
|
+
if (typeof arg !== 'undefined') {
|
|
102
|
+
throw new Error(`[GlOutsideDirective] Arguments are not supported. Consider using modifiers instead.`);
|
|
103
|
+
}
|
|
104
|
+
if (modifiersList.some(modifier => !supportedEventTypes.includes(modifier))) {
|
|
105
|
+
throw new Error(`[GlOutsideDirective] Cannot bind ${modifiersList} events; supported event types are: ${supportedEventTypes.join(', ')}`);
|
|
106
|
+
}
|
|
69
107
|
}
|
|
108
|
+
return {
|
|
109
|
+
callback: value,
|
|
110
|
+
eventTypes: modifiersList.length > 0 ? modifiersList : [defaultEventType]
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const bind = (el, bindings) => {
|
|
114
|
+
const {
|
|
115
|
+
callback,
|
|
116
|
+
eventTypes
|
|
117
|
+
} = parseBinding(bindings);
|
|
70
118
|
if (callbacks.has(el)) {
|
|
71
119
|
// This element is already bound. This is possible if two components, which
|
|
72
120
|
// share the same root node, (i.e., one is a higher-order component
|
|
@@ -79,15 +127,17 @@ const bind = (el, _ref) => {
|
|
|
79
127
|
// element.
|
|
80
128
|
return;
|
|
81
129
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
130
|
+
callbacks.set(el, {
|
|
131
|
+
callback,
|
|
132
|
+
eventTypes
|
|
133
|
+
});
|
|
134
|
+
startListening(eventTypes);
|
|
86
135
|
};
|
|
87
136
|
const unbind = el => {
|
|
88
|
-
callbacks.
|
|
89
|
-
if (
|
|
90
|
-
|
|
137
|
+
const entry = callbacks.get(el);
|
|
138
|
+
if (entry) {
|
|
139
|
+
callbacks.delete(el);
|
|
140
|
+
stopListening(entry.eventTypes);
|
|
91
141
|
}
|
|
92
142
|
};
|
|
93
143
|
const OutsideDirective = {
|