@gitlab/duo-ui 8.22.0 → 8.23.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,17 @@
1
+ # [8.23.0](https://gitlab.com/gitlab-org/duo-ui/compare/v8.22.1...v8.23.0) (2025-06-26)
2
+
3
+
4
+ ### Features
5
+
6
+ * add message feedback to agentic messages ([04ca354](https://gitlab.com/gitlab-org/duo-ui/commit/04ca354f2a7e1e54074222f2672dd3967737452e))
7
+
8
+ ## [8.22.1](https://gitlab.com/gitlab-org/duo-ui/compare/v8.22.0...v8.22.1) (2025-06-20)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **chat:** Fix chat history group dates ([548c507](https://gitlab.com/gitlab-org/duo-ui/commit/548c5078090d0a648824727a59a0fe6f727d31f2))
14
+
1
15
  # [8.22.0](https://gitlab.com/gitlab-org/duo-ui/compare/v8.21.0...v8.22.0) (2025-06-18)
2
16
 
3
17
 
@@ -2,10 +2,10 @@ import { GlFormGroup, GlFormTextarea, GlIcon, GlAnimatedLoaderIcon, GlButton, Gl
2
2
  import { translate, translatePlural, sprintf } from '@gitlab/ui/dist/utils/i18n';
3
3
  import throttle from 'lodash/throttle';
4
4
  import DuoChatContextItemSelections from '../duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections';
5
- import GlDuoUserFeedback from '../../../user_feedback/user_feedback';
6
5
  import { SELECTED_CONTEXT_ITEMS_DEFAULT_COLLAPSED, MESSAGE_MODEL_ROLES } from '../../constants';
7
6
  import DocumentationSources from '../duo_chat_message_sources/duo_chat_message_sources';
8
7
  import { renderDuoChatMarkdownPreview } from '../../markdown_renderer';
8
+ import MessageFeedback from './message_feedback';
9
9
  import { CopyCodeElement } from './copy_code_element';
10
10
  import { InsertCodeSnippetElement } from './insert_code_snippet_element';
11
11
  import { concatUntilEmpty, checkClipboardPermissions } from './utils';
@@ -19,18 +19,10 @@ import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
19
19
  const COMPONENT_FOR_MESSAGE_TYPE = {
20
20
  tool: () => ToolMessage,
21
21
  request: () => InputRequestedMessage,
22
- workflow_end: () => WorkflowEndMessage
22
+ workflow_end: () => WorkflowEndMessage,
23
+ agent: () => AgentMessage
23
24
  };
24
25
  const i18n = {
25
- MODAL: {
26
- TITLE: translate('DuoChatMessage.modalTitle', 'Give feedback on GitLab Duo Chat'),
27
- ALERT_TEXT: translate('DuoChatMessage.modalAlertText', 'GitLab team members cannot view your conversation. Please be as descriptive as possible.'),
28
- DID_WHAT: translate('DuoChatMessage.modalDidWhat', 'What were you doing?'),
29
- INTERACTION: translate('DuoChatMessage.modalInteraction', 'The situation in which you interacted with GitLab Duo Chat.'),
30
- IMPROVE_WHAT: translate('DuoChatMessage.modalImproveWhat', 'How could the response be improved?'),
31
- BETTER_RESPONSE: translate('DuoChatMessage.modalBetterResponse', 'How the response might better meet your needs.'),
32
- MESSAGE_ERROR: translate('DuoChatMessage.modalMessageError', 'Error sending the message')
33
- },
34
26
  CHAT_MESSAGE_COPIED: translate('DuoChatMessage.chatMessageCopied', 'Copied'),
35
27
  CHAT_MESSAGE_COPY: translate('DuoChatMessage.chatMessageCopyToClipboard', 'Copy to clipboard'),
36
28
  FROM_ME: translate('DuoChatMessage.fromMe', 'Me:'),
@@ -45,7 +37,7 @@ var script = {
45
37
  components: {
46
38
  DocumentationSources,
47
39
  DuoChatContextItemSelections,
48
- GlDuoUserFeedback,
40
+ MessageFeedback,
49
41
  GlFormGroup,
50
42
  GlFormTextarea,
51
43
  GlIcon,
@@ -56,12 +48,6 @@ var script = {
56
48
  SafeHtml: GlSafeHtmlDirective,
57
49
  GlTooltip: GlTooltipDirective
58
50
  },
59
- provide() {
60
- return {
61
- modalTitle: i18n.MODAL.TITLE,
62
- modalAlertText: i18n.MODAL.ALERT_TEXT
63
- };
64
- },
65
51
  inject: {
66
52
  // Note, we likely might move away from Provide/Inject for this
67
53
  // and only ship the versions that are currently in the default
@@ -109,8 +95,6 @@ var script = {
109
95
  },
110
96
  data() {
111
97
  return {
112
- didWhat: '',
113
- improveWhat: '',
114
98
  messageWatcher: null,
115
99
  // imperatively set up watcher on message
116
100
  messageChunks: [],
@@ -141,7 +125,7 @@ var script = {
141
125
  },
142
126
  hasFeedback() {
143
127
  var _this$message$extras2;
144
- return (_this$message$extras2 = this.message.extras) === null || _this$message$extras2 === void 0 ? void 0 : _this$message$extras2.hasFeedback;
128
+ return Boolean((_this$message$extras2 = this.message.extras) === null || _this$message$extras2 === void 0 ? void 0 : _this$message$extras2.hasFeedback);
145
129
  },
146
130
  defaultContent() {
147
131
  if (this.message.contentHtml) {
@@ -246,8 +230,6 @@ var script = {
246
230
  logEvent(e) {
247
231
  this.$emit('track-feedback', {
248
232
  ...e,
249
- didWhat: this.didWhat,
250
- improveWhat: this.improveWhat,
251
233
  message: this.message
252
234
  });
253
235
  },
@@ -323,7 +305,7 @@ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=
323
305
  'gl-bg-subtle': _vm.isAssistantMessage && !_vm.error,
324
306
  'duo-chat-message-with-error gl-bg-feedback-danger': _vm.error,
325
307
  '!gl-rounded-br-none': _vm.shouldShowCopyAction,
326
- },on:{"insert-code-snippet":_vm.onInsertCodeSnippet,"copy-code-snippet":_vm.onCopyCodeSnippet}},[(_vm.showA11yFromText)?_c('div',{staticClass:"gl-sr-only"},[_vm._v("\n "+_vm._s(_vm.isUserMessage ? _vm.$options.i18n.FROM_ME : _vm.$options.i18n.FROM_DUO)+"\n ")]):_vm._e(),_vm._v(" "),(_vm.error)?_c('gl-icon',{staticClass:"error-icon gl-mr-3 gl-shrink-0 gl-text-danger",attrs:{"aria-label":_vm.$options.i18n.MESSAGE_ERROR,"name":"error","size":16,"data-testid":"error"}}):_vm._e(),_vm._v(" "),_c('div',{ref:"content-wrapper",class:{ 'has-error': _vm.error }},[(_vm.displaySelectedContextItems && _vm.isAssistantMessage)?_c('duo-chat-context-item-selections',{attrs:{"selections":_vm.selectedContextItems,"title":_vm.selectedContextItemsTitle,"default-collapsed":_vm.selectedContextItemsDefaultCollapsed,"variant":"assistant"},on:{"get-content":_vm.onGetContextItemContent}}):_vm._e(),_vm._v(" "),(_vm.error)?_c('div',{directives:[{name:"safe-html",rawName:"v-safe-html:[$options.safeHtmlConfigExtension]",value:(_vm.renderedError),expression:"renderedError",arg:_vm.$options.safeHtmlConfigExtension}],ref:"error-message"}):_c('div',[_c('div',{directives:[{name:"safe-html",rawName:"v-safe-html:[$options.safeHtmlConfigExtension]",value:(_vm.messageContent),expression:"messageContent",arg:_vm.$options.safeHtmlConfigExtension}],ref:"content"}),_vm._v(" "),(_vm.isAssistantMessage)?[(_vm.sources)?_c('documentation-sources',{attrs:{"sources":_vm.sources}}):_vm._e(),_vm._v(" "),_c('div',{staticClass:"duo-chat-message-feedback gl-mt-4 gl-flex gl-items-end"},[(_vm.isChunkAndNotCancelled)?_c('gl-animated-loader-icon',{attrs:{"is-on":true}}):_vm._e(),_vm._v(" "),(_vm.shouldShowFeedbackLink)?_c('gl-duo-user-feedback',{attrs:{"feedback-received":_vm.hasFeedback,"modal-title":_vm.$options.i18n.MODAL.TITLE,"modal-alert":_vm.$options.i18n.MODAL.ALERT_TEXT},on:{"feedback":_vm.logEvent},scopedSlots:_vm._u([{key:"feedback-extra-fields",fn:function(){return [_c('gl-form-group',{attrs:{"label":_vm.$options.i18n.MODAL.DID_WHAT,"optional":""}},[_c('gl-form-textarea',{attrs:{"placeholder":_vm.$options.i18n.MODAL.INTERACTION},model:{value:(_vm.didWhat),callback:function ($$v) {_vm.didWhat=$$v;},expression:"didWhat"}})],1),_vm._v(" "),_c('gl-form-group',{attrs:{"label":_vm.$options.i18n.MODAL.IMPROVE_WHAT,"optional":""}},[_c('gl-form-textarea',{attrs:{"placeholder":_vm.$options.i18n.MODAL.BETTER_RESPONSE},model:{value:(_vm.improveWhat),callback:function ($$v) {_vm.improveWhat=$$v;},expression:"improveWhat"}})],1)]},proxy:true}],null,false,419229417)}):_vm._e()],1)]:_vm._e()],2),_vm._v(" "),(_vm.displaySelectedContextItems && _vm.isUserMessage)?_c('duo-chat-context-item-selections',{attrs:{"selections":_vm.selectedContextItems,"title":_vm.selectedContextItemsTitle,"default-collapsed":_vm.selectedContextItemsDefaultCollapsed,"variant":"user"},on:{"get-content":_vm.onGetContextItemContent}}):_vm._e()],1)],1),_vm._v(" "),_c('transition',{attrs:{"name":"duo-chat-message-actions"}},[(_vm.shouldShowCopyAction)?_c('div',{staticClass:"gl-bg-subtle duo-chat-message-actions gl-rounded-tr-lg gl-rounded-br-lg"},[_c('gl-button',{directives:[{name:"gl-tooltip",rawName:"v-gl-tooltip.hover",modifiers:{"hover":true}}],class:{ '!gl-text-success': _vm.copied },attrs:{"title":_vm.copied ? _vm.$options.i18n.CHAT_MESSAGE_COPIED : _vm.$options.i18n.CHAT_MESSAGE_COPY,"icon":_vm.copied ? 'check-circle-filled' : 'copy-to-clipboard',"category":"tertiary"},on:{"click":_vm.copyMessage}})],1):_vm._e()])]:_c(_vm.componentForMessageType(_vm.message),{tag:"component",attrs:{"message":_vm.message,"data-testid":"workflow-message"},on:{"open-file-path":_vm.onOpenFilePath}})],2)};
308
+ },on:{"insert-code-snippet":_vm.onInsertCodeSnippet,"copy-code-snippet":_vm.onCopyCodeSnippet}},[(_vm.showA11yFromText)?_c('div',{staticClass:"gl-sr-only"},[_vm._v("\n "+_vm._s(_vm.isUserMessage ? _vm.$options.i18n.FROM_ME : _vm.$options.i18n.FROM_DUO)+"\n ")]):_vm._e(),_vm._v(" "),(_vm.error)?_c('gl-icon',{staticClass:"error-icon gl-mr-3 gl-shrink-0 gl-text-danger",attrs:{"aria-label":_vm.$options.i18n.MESSAGE_ERROR,"name":"error","size":16,"data-testid":"error"}}):_vm._e(),_vm._v(" "),_c('div',{ref:"content-wrapper",class:{ 'has-error': _vm.error }},[(_vm.displaySelectedContextItems && _vm.isAssistantMessage)?_c('duo-chat-context-item-selections',{attrs:{"selections":_vm.selectedContextItems,"title":_vm.selectedContextItemsTitle,"default-collapsed":_vm.selectedContextItemsDefaultCollapsed,"variant":"assistant"},on:{"get-content":_vm.onGetContextItemContent}}):_vm._e(),_vm._v(" "),(_vm.error)?_c('div',{directives:[{name:"safe-html",rawName:"v-safe-html:[$options.safeHtmlConfigExtension]",value:(_vm.renderedError),expression:"renderedError",arg:_vm.$options.safeHtmlConfigExtension}],ref:"error-message"}):_c('div',[_c('div',{directives:[{name:"safe-html",rawName:"v-safe-html:[$options.safeHtmlConfigExtension]",value:(_vm.messageContent),expression:"messageContent",arg:_vm.$options.safeHtmlConfigExtension}],ref:"content"}),_vm._v(" "),(_vm.isAssistantMessage)?[(_vm.sources)?_c('documentation-sources',{attrs:{"sources":_vm.sources}}):_vm._e(),_vm._v(" "),_c('div',{staticClass:"duo-chat-message-feedback gl-mt-4 gl-flex gl-items-end"},[(_vm.isChunkAndNotCancelled)?_c('gl-animated-loader-icon',{attrs:{"is-on":true}}):_vm._e(),_vm._v(" "),(_vm.shouldShowFeedbackLink)?_c('message-feedback',{attrs:{"has-feedback":_vm.hasFeedback},on:{"feedback":_vm.logEvent}}):_vm._e()],1)]:_vm._e()],2),_vm._v(" "),(_vm.displaySelectedContextItems && _vm.isUserMessage)?_c('duo-chat-context-item-selections',{attrs:{"selections":_vm.selectedContextItems,"title":_vm.selectedContextItemsTitle,"default-collapsed":_vm.selectedContextItemsDefaultCollapsed,"variant":"user"},on:{"get-content":_vm.onGetContextItemContent}}):_vm._e()],1)],1),_vm._v(" "),_c('transition',{attrs:{"name":"duo-chat-message-actions"}},[(_vm.shouldShowCopyAction)?_c('div',{staticClass:"gl-bg-subtle duo-chat-message-actions gl-rounded-tr-lg gl-rounded-br-lg"},[_c('gl-button',{directives:[{name:"gl-tooltip",rawName:"v-gl-tooltip.hover",modifiers:{"hover":true}}],class:{ '!gl-text-success': _vm.copied },attrs:{"title":_vm.copied ? _vm.$options.i18n.CHAT_MESSAGE_COPIED : _vm.$options.i18n.CHAT_MESSAGE_COPY,"icon":_vm.copied ? 'check-circle-filled' : 'copy-to-clipboard',"category":"tertiary"},on:{"click":_vm.copyMessage}})],1):_vm._e()])]:_c(_vm.componentForMessageType(_vm.message),{tag:"component",attrs:{"message":_vm.message,"with-feedback":_vm.withFeedback,"data-testid":"workflow-message"},on:{"open-file-path":_vm.onOpenFilePath,"feedback":_vm.logEvent}})],2)};
327
309
  var __vue_staticRenderFns__ = [];
328
310
 
329
311
  /* style */
@@ -0,0 +1,90 @@
1
+ import { GlFormGroup, GlFormTextarea } from '@gitlab/ui';
2
+ import { translate } from '@gitlab/ui/dist/utils/i18n';
3
+ import GlDuoUserFeedback from '../../../user_feedback/user_feedback';
4
+ import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
5
+
6
+ const i18n = {
7
+ TITLE: translate('DuoChatMessage.modalTitle', 'Give feedback on GitLab Duo Chat'),
8
+ ALERT_TEXT: translate('DuoChatMessage.modalAlertText', 'GitLab team members cannot view your conversation. Please be as descriptive as possible.'),
9
+ DID_WHAT: translate('DuoChatMessage.modalDidWhat', 'What were you doing?'),
10
+ INTERACTION: translate('DuoChatMessage.modalInteraction', 'The situation in which you interacted with GitLab Duo Chat.'),
11
+ IMPROVE_WHAT: translate('DuoChatMessage.modalImproveWhat', 'How could the response be improved?'),
12
+ BETTER_RESPONSE: translate('DuoChatMessage.modalBetterResponse', 'How the response might better meet your needs.'),
13
+ MESSAGE_ERROR: translate('DuoChatMessage.modalMessageError', 'Error sending the message')
14
+ };
15
+ var script = {
16
+ i18n,
17
+ name: 'MessageFeedback',
18
+ components: {
19
+ GlDuoUserFeedback,
20
+ GlFormGroup,
21
+ GlFormTextarea
22
+ },
23
+ provide() {
24
+ return {
25
+ modalTitle: i18n.TITLE,
26
+ modalAlertText: i18n.ALERT_TEXT
27
+ };
28
+ },
29
+ props: {
30
+ hasFeedback: {
31
+ required: false,
32
+ default: false,
33
+ type: Boolean
34
+ }
35
+ },
36
+ data() {
37
+ return {
38
+ didWhat: '',
39
+ improveWhat: ''
40
+ };
41
+ },
42
+ methods: {
43
+ logEvent(e) {
44
+ this.$emit('feedback', {
45
+ ...e,
46
+ didWhat: this.didWhat,
47
+ improveWhat: this.improveWhat
48
+ });
49
+ }
50
+ }
51
+ };
52
+
53
+ /* script */
54
+ const __vue_script__ = script;
55
+
56
+ /* template */
57
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('gl-duo-user-feedback',{attrs:{"feedback-received":_vm.hasFeedback,"modal-title":_vm.$options.i18n.TITLE,"modal-alert":_vm.$options.i18n.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.DID_WHAT,"optional":"","data-testid":"did-what-form-group"}},[_c('gl-form-textarea',{attrs:{"placeholder":_vm.$options.i18n.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.IMPROVE_WHAT,"optional":"","data-testid":"improve-what-form-group"}},[_c('gl-form-textarea',{attrs:{"placeholder":_vm.$options.i18n.BETTER_RESPONSE},model:{value:(_vm.improveWhat),callback:function ($$v) {_vm.improveWhat=$$v;},expression:"improveWhat"}})],1)]},proxy:true}])})};
58
+ var __vue_staticRenderFns__ = [];
59
+
60
+ /* style */
61
+ const __vue_inject_styles__ = undefined;
62
+ /* scoped */
63
+ const __vue_scope_id__ = undefined;
64
+ /* module identifier */
65
+ const __vue_module_identifier__ = undefined;
66
+ /* functional template */
67
+ const __vue_is_functional_template__ = false;
68
+ /* style inject */
69
+
70
+ /* style inject SSR */
71
+
72
+ /* style inject shadow dom */
73
+
74
+
75
+
76
+ const __vue_component__ = __vue_normalize__(
77
+ { render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ },
78
+ __vue_inject_styles__,
79
+ __vue_script__,
80
+ __vue_scope_id__,
81
+ __vue_is_functional_template__,
82
+ __vue_module_identifier__,
83
+ false,
84
+ undefined,
85
+ undefined,
86
+ undefined
87
+ );
88
+
89
+ export default __vue_component__;
90
+ export { i18n };
@@ -1,15 +1,22 @@
1
+ import MessageFeedback from '../message_feedback';
1
2
  import BaseMessage from './message_base';
2
3
  import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
3
4
 
4
5
  var script = {
5
6
  name: 'DuoAgentMessage',
6
7
  components: {
7
- BaseMessage
8
+ BaseMessage,
9
+ MessageFeedback
8
10
  },
9
11
  props: {
10
12
  message: {
11
13
  required: true,
12
14
  type: Object
15
+ },
16
+ withFeedback: {
17
+ required: false,
18
+ default: false,
19
+ type: Boolean
13
20
  }
14
21
  },
15
22
  computed: {
@@ -26,7 +33,7 @@ const __vue_script__ = script;
26
33
  /* template */
27
34
  var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('base-message',{staticClass:"gl-rounded-bl-none gl-border-1 gl-border-solid gl-text-gray-900 gl-p-4 gl-border-gray-50",class:{
28
35
  'gl-border-red-100': _vm.hasError,
29
- },attrs:{"message":_vm.message},scopedSlots:_vm._u([{key:"message",fn:function(slotProps){return [_vm._t("message",null,null,slotProps)]}}],null,true)})};
36
+ },attrs:{"message":_vm.message},scopedSlots:_vm._u([{key:"message",fn:function(slotProps){return [_vm._t("message",function(){return [_vm._v(_vm._s(slotProps.content))]},null,slotProps),_vm._v(" "),(_vm.withFeedback)?_c('message-feedback',{on:{"feedback":function($event){return _vm.$emit('feedback', $event)}}}):_vm._e()]}}],null,true)})};
30
37
  var __vue_staticRenderFns__ = [];
31
38
 
32
39
  /* style */
@@ -26,9 +26,6 @@ var script = {
26
26
  }
27
27
  },
28
28
  computed: {
29
- formattedLocalDate() {
30
- return date => formatLocalizedDate(date, this.preferredLocale);
31
- },
32
29
  groupedThreads() {
33
30
  if (!this.hasThreads) {
34
31
  return {};
@@ -46,6 +43,9 @@ var script = {
46
43
  }
47
44
  },
48
45
  methods: {
46
+ formattedLocalDate(date) {
47
+ return formatLocalizedDate(date, this.preferredLocale);
48
+ },
49
49
  onNewChat() {
50
50
  this.$emit('new-chat');
51
51
  },
@@ -63,7 +63,8 @@ var script = {
63
63
  return new Date(dateA) - new Date(dateB);
64
64
  },
65
65
  getDateKey(date) {
66
- return new Date(date).toISOString().split('T')[0];
66
+ // add 00:00:00 to the date string to make it at midnight local time
67
+ return `${new Date(date).toISOString().split('T')[0]}T00:00:00`;
67
68
  },
68
69
  sprintf
69
70
  },
@@ -18,6 +18,7 @@ const MESSAGE_MODEL_ROLES = {
18
18
  user: 'user',
19
19
  system: 'system',
20
20
  assistant: 'assistant',
21
+ agent: 'agent',
21
22
  tool: 'tool',
22
23
  request: 'request',
23
24
  workflow_end: 'workflow_end'
@@ -74,6 +74,11 @@ const MOCK_TOOL_MESSAGE = {
74
74
  name: 'list_files'
75
75
  }
76
76
  };
77
+ const MOCK_AGENT_MESSAGE = {
78
+ id: '123',
79
+ content: "Search for 'duo.*chat.*message' in directory",
80
+ message_type: MESSAGE_MODEL_ROLES.agent
81
+ };
77
82
  const MOCK_TOOL_MESSAGE_WITH_LINK = {
78
83
  id: '123',
79
84
  content: "Search for 'duo.*chat.*message' in directory",
@@ -295,4 +300,4 @@ const THREADLIST = [{
295
300
  title: 'Before the Configuration Committee'
296
301
  }];
297
302
 
298
- export { INCLUDE_SLASH_COMMAND, MOCK_REQUEST_MESSAGE, MOCK_RESPONSE_MESSAGE, MOCK_RESPONSE_MESSAGE_FOR_STREAMING, MOCK_TOOL_MESSAGE, MOCK_TOOL_MESSAGE_WITH_LINK, MOCK_USER_PROMPT_MESSAGE, MOCK_WORKFLOW_END_MESSAGE, SLASH_COMMANDS, THREADLIST, generateMockResponseChunks, generateSeparateChunks, renderGFM, renderMarkdown };
303
+ export { INCLUDE_SLASH_COMMAND, MOCK_AGENT_MESSAGE, MOCK_REQUEST_MESSAGE, MOCK_RESPONSE_MESSAGE, MOCK_RESPONSE_MESSAGE_FOR_STREAMING, MOCK_TOOL_MESSAGE, MOCK_TOOL_MESSAGE_WITH_LINK, MOCK_USER_PROMPT_MESSAGE, MOCK_WORKFLOW_END_MESSAGE, SLASH_COMMANDS, THREADLIST, generateMockResponseChunks, generateSeparateChunks, renderGFM, renderMarkdown };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/duo-ui",
3
- "version": "8.22.0",
3
+ "version": "8.23.0",
4
4
  "description": "Duo UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -97,10 +97,10 @@
97
97
  "@babel/plugin-proposal-optional-chaining": "^7.21.0",
98
98
  "@babel/preset-env": "^7.27.2",
99
99
  "@babel/preset-react": "^7.27.1",
100
- "@gitlab/eslint-plugin": "21.0.0",
100
+ "@gitlab/eslint-plugin": "21.1.0",
101
101
  "@gitlab/fonts": "^1.3.0",
102
102
  "@gitlab/stylelint-config": "6.2.2",
103
- "@gitlab/svgs": "^3.135.0",
103
+ "@gitlab/svgs": "^3.136.0",
104
104
  "@gitlab/ui": "latest",
105
105
  "@jest/test-sequencer": "^29.7.0",
106
106
  "@rollup/plugin-commonjs": "^11.1.0",
@@ -155,8 +155,8 @@
155
155
  "module-alias": "^2.2.2",
156
156
  "npm-run-all": "^4.1.5",
157
157
  "pikaday": "^1.8.0",
158
- "playwright": "^1.53.0",
159
- "playwright-core": "^1.53.0",
158
+ "playwright": "^1.53.1",
159
+ "playwright-core": "^1.53.1",
160
160
  "plop": "^2.5.4",
161
161
  "postcss": "8.4.28",
162
162
  "postcss-loader": "^7.0.2",
@@ -12,12 +12,12 @@ import {
12
12
  import { sprintf, translate, translatePlural } from '@gitlab/ui/dist/utils/i18n';
13
13
  import throttle from 'lodash/throttle';
14
14
  import DuoChatContextItemSelections from '../duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections.vue';
15
- import GlDuoUserFeedback from '../../../user_feedback/user_feedback.vue';
16
15
  import { MESSAGE_MODEL_ROLES, SELECTED_CONTEXT_ITEMS_DEFAULT_COLLAPSED } from '../../constants';
17
16
 
18
17
  import DocumentationSources from '../duo_chat_message_sources/duo_chat_message_sources.vue';
19
18
  // eslint-disable-next-line no-restricted-imports
20
19
  import { renderDuoChatMarkdownPreview } from '../../markdown_renderer';
20
+ import MessageFeedback from './message_feedback.vue';
21
21
  import { CopyCodeElement } from './copy_code_element';
22
22
  import { InsertCodeSnippetElement } from './insert_code_snippet_element';
23
23
  import { checkClipboardPermissions, concatUntilEmpty } from './utils';
@@ -35,30 +35,10 @@ const COMPONENT_FOR_MESSAGE_TYPE = {
35
35
  tool: () => ToolMessage,
36
36
  request: () => InputRequestedMessage,
37
37
  workflow_end: () => WorkflowEndMessage,
38
+ agent: () => AgentMessage,
38
39
  };
39
40
 
40
41
  export const i18n = {
41
- MODAL: {
42
- TITLE: translate('DuoChatMessage.modalTitle', 'Give feedback on GitLab Duo Chat'),
43
- ALERT_TEXT: translate(
44
- 'DuoChatMessage.modalAlertText',
45
- 'GitLab team members cannot view your conversation. Please be as descriptive as possible.'
46
- ),
47
- DID_WHAT: translate('DuoChatMessage.modalDidWhat', 'What were you doing?'),
48
- INTERACTION: translate(
49
- 'DuoChatMessage.modalInteraction',
50
- 'The situation in which you interacted with GitLab Duo Chat.'
51
- ),
52
- IMPROVE_WHAT: translate(
53
- 'DuoChatMessage.modalImproveWhat',
54
- 'How could the response be improved?'
55
- ),
56
- BETTER_RESPONSE: translate(
57
- 'DuoChatMessage.modalBetterResponse',
58
- 'How the response might better meet your needs.'
59
- ),
60
- MESSAGE_ERROR: translate('DuoChatMessage.modalMessageError', 'Error sending the message'),
61
- },
62
42
  CHAT_MESSAGE_COPIED: translate('DuoChatMessage.chatMessageCopied', 'Copied'),
63
43
  CHAT_MESSAGE_COPY: translate('DuoChatMessage.chatMessageCopyToClipboard', 'Copy to clipboard'),
64
44
  FROM_ME: translate('DuoChatMessage.fromMe', 'Me:'),
@@ -74,7 +54,7 @@ export default {
74
54
  components: {
75
55
  DocumentationSources,
76
56
  DuoChatContextItemSelections,
77
- GlDuoUserFeedback,
57
+ MessageFeedback,
78
58
  GlFormGroup,
79
59
  GlFormTextarea,
80
60
  GlIcon,
@@ -85,12 +65,6 @@ export default {
85
65
  SafeHtml,
86
66
  GlTooltip: GlTooltipDirective,
87
67
  },
88
- provide() {
89
- return {
90
- modalTitle: i18n.MODAL.TITLE,
91
- modalAlertText: i18n.MODAL.ALERT_TEXT,
92
- };
93
- },
94
68
  inject: {
95
69
  // Note, we likely might move away from Provide/Inject for this
96
70
  // and only ship the versions that are currently in the default
@@ -138,8 +112,6 @@ export default {
138
112
  },
139
113
  data() {
140
114
  return {
141
- didWhat: '',
142
- improveWhat: '',
143
115
  messageWatcher: null, // imperatively set up watcher on message
144
116
  messageChunks: [],
145
117
  selectedContextItemsDefaultCollapsed: SELECTED_CONTEXT_ITEMS_DEFAULT_COLLAPSED,
@@ -167,7 +139,7 @@ export default {
167
139
  return this.message.extras?.sources;
168
140
  },
169
141
  hasFeedback() {
170
- return this.message.extras?.hasFeedback;
142
+ return Boolean(this.message.extras?.hasFeedback);
171
143
  },
172
144
  defaultContent() {
173
145
  if (this.message.contentHtml) {
@@ -290,8 +262,6 @@ export default {
290
262
  logEvent(e) {
291
263
  this.$emit('track-feedback', {
292
264
  ...e,
293
- didWhat: this.didWhat,
294
- improveWhat: this.improveWhat,
295
265
  message: this.message,
296
266
  });
297
267
  },
@@ -418,28 +388,11 @@ export default {
418
388
 
419
389
  <div class="duo-chat-message-feedback gl-mt-4 gl-flex gl-items-end">
420
390
  <gl-animated-loader-icon v-if="isChunkAndNotCancelled" :is-on="true" />
421
- <gl-duo-user-feedback
391
+ <message-feedback
422
392
  v-if="shouldShowFeedbackLink"
423
- :feedback-received="hasFeedback"
424
- :modal-title="$options.i18n.MODAL.TITLE"
425
- :modal-alert="$options.i18n.MODAL.ALERT_TEXT"
393
+ :has-feedback="hasFeedback"
426
394
  @feedback="logEvent"
427
- >
428
- <template #feedback-extra-fields>
429
- <gl-form-group :label="$options.i18n.MODAL.DID_WHAT" optional>
430
- <gl-form-textarea
431
- v-model="didWhat"
432
- :placeholder="$options.i18n.MODAL.INTERACTION"
433
- />
434
- </gl-form-group>
435
- <gl-form-group :label="$options.i18n.MODAL.IMPROVE_WHAT" optional>
436
- <gl-form-textarea
437
- v-model="improveWhat"
438
- :placeholder="$options.i18n.MODAL.BETTER_RESPONSE"
439
- />
440
- </gl-form-group>
441
- </template>
442
- </gl-duo-user-feedback>
395
+ />
443
396
  </div>
444
397
  </template>
445
398
  </div>
@@ -473,8 +426,10 @@ export default {
473
426
  :is="componentForMessageType(message)"
474
427
  v-else
475
428
  :message="message"
429
+ :with-feedback="withFeedback"
476
430
  data-testid="workflow-message"
477
431
  @open-file-path="onOpenFilePath"
432
+ @feedback="logEvent"
478
433
  />
479
434
  </div>
480
435
  </template>
@@ -0,0 +1,83 @@
1
+ <script>
2
+ import { GlFormGroup, GlFormTextarea } from '@gitlab/ui';
3
+ import { translate } from '@gitlab/ui/dist/utils/i18n';
4
+ import GlDuoUserFeedback from '../../../user_feedback/user_feedback.vue';
5
+
6
+ export const i18n = {
7
+ TITLE: translate('DuoChatMessage.modalTitle', 'Give feedback on GitLab Duo Chat'),
8
+ ALERT_TEXT: translate(
9
+ 'DuoChatMessage.modalAlertText',
10
+ 'GitLab team members cannot view your conversation. Please be as descriptive as possible.'
11
+ ),
12
+ DID_WHAT: translate('DuoChatMessage.modalDidWhat', 'What were you doing?'),
13
+ INTERACTION: translate(
14
+ 'DuoChatMessage.modalInteraction',
15
+ 'The situation in which you interacted with GitLab Duo Chat.'
16
+ ),
17
+ IMPROVE_WHAT: translate('DuoChatMessage.modalImproveWhat', 'How could the response be improved?'),
18
+ BETTER_RESPONSE: translate(
19
+ 'DuoChatMessage.modalBetterResponse',
20
+ 'How the response might better meet your needs.'
21
+ ),
22
+ MESSAGE_ERROR: translate('DuoChatMessage.modalMessageError', 'Error sending the message'),
23
+ };
24
+
25
+ export default {
26
+ i18n,
27
+ name: 'MessageFeedback',
28
+ components: {
29
+ GlDuoUserFeedback,
30
+ GlFormGroup,
31
+ GlFormTextarea,
32
+ },
33
+ provide() {
34
+ return {
35
+ modalTitle: i18n.TITLE,
36
+ modalAlertText: i18n.ALERT_TEXT,
37
+ };
38
+ },
39
+ props: {
40
+ hasFeedback: {
41
+ required: false,
42
+ default: false,
43
+ type: Boolean,
44
+ },
45
+ },
46
+ data() {
47
+ return {
48
+ didWhat: '',
49
+ improveWhat: '',
50
+ };
51
+ },
52
+ methods: {
53
+ logEvent(e) {
54
+ this.$emit('feedback', {
55
+ ...e,
56
+ didWhat: this.didWhat,
57
+ improveWhat: this.improveWhat,
58
+ });
59
+ },
60
+ },
61
+ };
62
+ </script>
63
+ <template>
64
+ <gl-duo-user-feedback
65
+ :feedback-received="hasFeedback"
66
+ :modal-title="$options.i18n.TITLE"
67
+ :modal-alert="$options.i18n.ALERT_TEXT"
68
+ @feedback="logEvent"
69
+ >
70
+ <template #feedback-extra-fields>
71
+ <gl-form-group :label="$options.i18n.DID_WHAT" optional data-testid="did-what-form-group">
72
+ <gl-form-textarea v-model="didWhat" :placeholder="$options.i18n.INTERACTION" />
73
+ </gl-form-group>
74
+ <gl-form-group
75
+ :label="$options.i18n.IMPROVE_WHAT"
76
+ optional
77
+ data-testid="improve-what-form-group"
78
+ >
79
+ <gl-form-textarea v-model="improveWhat" :placeholder="$options.i18n.BETTER_RESPONSE" />
80
+ </gl-form-group>
81
+ </template>
82
+ </gl-duo-user-feedback>
83
+ </template>
@@ -1,16 +1,23 @@
1
1
  <script>
2
+ import MessageFeedback from '../message_feedback.vue';
2
3
  import BaseMessage from './message_base.vue';
3
4
 
4
5
  export default {
5
6
  name: 'DuoAgentMessage',
6
7
  components: {
7
8
  BaseMessage,
9
+ MessageFeedback,
8
10
  },
9
11
  props: {
10
12
  message: {
11
13
  required: true,
12
14
  type: Object,
13
15
  },
16
+ withFeedback: {
17
+ required: false,
18
+ default: false,
19
+ type: Boolean,
20
+ },
14
21
  },
15
22
  computed: {
16
23
  hasError() {
@@ -28,7 +35,9 @@ export default {
28
35
  }"
29
36
  >
30
37
  <template #message="slotProps">
31
- <slot name="message" v-bind="slotProps"> </slot>
38
+ <slot name="message" v-bind="slotProps">{{ slotProps.content }}</slot>
39
+
40
+ <message-feedback v-if="withFeedback" @feedback="$emit('feedback', $event)" />
32
41
  </template>
33
42
  </base-message>
34
43
  </template>
@@ -33,10 +33,6 @@ export default {
33
33
  },
34
34
 
35
35
  computed: {
36
- formattedLocalDate() {
37
- return (date) => formatLocalizedDate(date, this.preferredLocale);
38
- },
39
-
40
36
  groupedThreads() {
41
37
  if (!this.hasThreads) {
42
38
  return {};
@@ -53,38 +49,35 @@ export default {
53
49
  };
54
50
  }, {});
55
51
  },
56
-
57
52
  hasThreads() {
58
53
  return this.threads.length > 0;
59
54
  },
60
55
  },
61
56
 
62
57
  methods: {
58
+ formattedLocalDate(date) {
59
+ return formatLocalizedDate(date, this.preferredLocale);
60
+ },
63
61
  onNewChat() {
64
62
  this.$emit('new-chat');
65
63
  },
66
-
67
64
  onSelectThread(thread) {
68
65
  this.$emit('select-thread', thread);
69
66
  },
70
-
71
67
  onDeleteThread(threadId, event) {
72
68
  event.stopPropagation();
73
69
  this.$emit('delete-thread', threadId);
74
70
  },
75
-
76
71
  onClose() {
77
72
  this.$emit('close');
78
73
  },
79
-
80
74
  compareThreadDates(dateA, dateB) {
81
75
  return new Date(dateA) - new Date(dateB);
82
76
  },
83
-
84
77
  getDateKey(date) {
85
- return new Date(date).toISOString().split('T')[0];
78
+ // add 00:00:00 to the date string to make it at midnight local time
79
+ return `${new Date(date).toISOString().split('T')[0]}T00:00:00`;
86
80
  },
87
-
88
81
  sprintf,
89
82
  },
90
83
  i18n,
@@ -21,6 +21,7 @@ export const MESSAGE_MODEL_ROLES = {
21
21
  user: 'user',
22
22
  system: 'system',
23
23
  assistant: 'assistant',
24
+ agent: 'agent',
24
25
  tool: 'tool',
25
26
  request: 'request',
26
27
  workflow_end: 'workflow_end',
@@ -89,6 +89,12 @@ export const MOCK_TOOL_MESSAGE = {
89
89
  },
90
90
  };
91
91
 
92
+ export const MOCK_AGENT_MESSAGE = {
93
+ id: '123',
94
+ content: "Search for 'duo.*chat.*message' in directory",
95
+ message_type: MESSAGE_MODEL_ROLES.agent,
96
+ };
97
+
92
98
  export const MOCK_TOOL_MESSAGE_WITH_LINK = {
93
99
  id: '123',
94
100
  content: "Search for 'duo.*chat.*message' in directory",