@gitlab/duo-ui 10.5.0 → 10.6.1

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
+ ## [10.6.1](https://gitlab.com/gitlab-org/duo-ui/compare/v10.6.0...v10.6.1) (2025-07-14)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * enhance markdown renderer security ([b73cc42](https://gitlab.com/gitlab-org/duo-ui/commit/b73cc42ecbec353cbe3646ceb0dacfd506b08975))
7
+ * enhance markdown renderer security ([c422395](https://gitlab.com/gitlab-org/duo-ui/commit/c422395af70992d91d1bc0350d87d1bbba9cfa9b))
8
+
9
+ # [10.6.0](https://gitlab.com/gitlab-org/duo-ui/compare/v10.5.0...v10.6.0) (2025-07-09)
10
+
11
+
12
+ ### Features
13
+
14
+ * Export and use messageMap in agentic chat ([884a306](https://gitlab.com/gitlab-org/duo-ui/commit/884a306b275ddd020c5f6e79c3cee51d35fc4e14))
15
+
1
16
  # [10.5.0](https://gitlab.com/gitlab-org/duo-ui/compare/v10.4.0...v10.5.0) (2025-07-08)
2
17
 
3
18
 
@@ -1,4 +1,4 @@
1
- import { GlFormGroup, GlFormTextarea, GlIcon, GlAnimatedLoaderIcon, GlButton, GlSafeHtmlDirective, GlTooltipDirective } from '@gitlab/ui';
1
+ import { GlIcon, GlAnimatedLoaderIcon, GlButton, GlSafeHtmlDirective, GlTooltipDirective } from '@gitlab/ui';
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';
@@ -10,18 +10,9 @@ import { CopyCodeElement } from './copy_code_element';
10
10
  import { InsertCodeSnippetElement } from './insert_code_snippet_element';
11
11
  import { concatUntilEmpty, checkClipboardPermissions } from './utils';
12
12
  import { DUO_CODE_SCRIM_BOTTOM_CLASS, DUO_CODE_SCRIM_OFFSET, DUO_CODE_SCRIM_TOP_CLASS } from './constants';
13
- import AgentMessage from './message_types/message_agent';
14
- import InputRequestedMessage from './message_types/message_input_requested';
15
- import ToolMessage from './message_types/message_tool';
16
- import WorkflowEndMessage from './message_types/message_workflow_end';
13
+ import MessageMap from './message_types/message_map';
17
14
  import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
18
15
 
19
- const COMPONENT_FOR_MESSAGE_TYPE = {
20
- tool: () => ToolMessage,
21
- request: () => InputRequestedMessage,
22
- workflow_end: () => WorkflowEndMessage,
23
- agent: () => AgentMessage
24
- };
25
16
  const i18n = {
26
17
  CHAT_MESSAGE_COPIED: translate('DuoChatMessage.chatMessageCopied', 'Copied'),
27
18
  CHAT_MESSAGE_COPY: translate('DuoChatMessage.chatMessageCopyToClipboard', 'Copy to clipboard'),
@@ -38,8 +29,7 @@ var script = {
38
29
  DocumentationSources,
39
30
  DuoChatContextItemSelections,
40
31
  MessageFeedback,
41
- GlFormGroup,
42
- GlFormTextarea,
32
+ MessageMap,
43
33
  GlIcon,
44
34
  GlAnimatedLoaderIcon,
45
35
  GlButton
@@ -200,10 +190,6 @@ var script = {
200
190
  var _this$message2, _this$message2$role, _this$message3, _this$message3$messag;
201
191
  return ((_this$message2 = this.message) === null || _this$message2 === void 0 ? void 0 : (_this$message2$role = _this$message2.role) === null || _this$message2$role === void 0 ? void 0 : _this$message2$role.toLowerCase()) === type || ((_this$message3 = this.message) === null || _this$message3 === void 0 ? void 0 : (_this$message3$messag = _this$message3.message_type) === null || _this$message3$messag === void 0 ? void 0 : _this$message3$messag.toLowerCase()) === type;
202
192
  },
203
- componentForMessageType(message) {
204
- var _COMPONENT_FOR_MESSAG, _COMPONENT_FOR_MESSAG2;
205
- return (_COMPONENT_FOR_MESSAG = (_COMPONENT_FOR_MESSAG2 = COMPONENT_FOR_MESSAGE_TYPE[message.message_type]) === null || _COMPONENT_FOR_MESSAG2 === void 0 ? void 0 : _COMPONENT_FOR_MESSAG2.call(COMPONENT_FOR_MESSAGE_TYPE, message, this.messages)) !== null && _COMPONENT_FOR_MESSAG !== void 0 ? _COMPONENT_FOR_MESSAG : AgentMessage;
206
- },
207
193
  setChunks() {
208
194
  if (this.isChunk) {
209
195
  const {
@@ -305,7 +291,7 @@ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=
305
291
  'gl-bg-subtle': _vm.isAssistantMessage && !_vm.error,
306
292
  'duo-chat-message-with-error gl-bg-feedback-danger': _vm.error,
307
293
  '!gl-rounded-br-none': _vm.shouldShowCopyAction,
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,"insert-code-snippet":_vm.onInsertCodeSnippet,"copy-code-snippet":_vm.onCopyCodeSnippet}})],2)};
294
+ },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('message-map',{attrs:{"message":_vm.message,"with-feedback":_vm.withFeedback,"data-testid":"workflow-message"},on:{"open-file-path":_vm.onOpenFilePath,"feedback":_vm.logEvent,"insert-code-snippet":_vm.onInsertCodeSnippet,"copy-code-snippet":_vm.onCopyCodeSnippet}})],2)};
309
295
  var __vue_staticRenderFns__ = [];
310
296
 
311
297
  /* style */
@@ -11,7 +11,7 @@ const duoMarked = new Marked([{
11
11
  const config = {
12
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
- FORBID_TAGS: ['script', 'style', 'iframe', 'form', 'button'],
14
+ FORBID_TAGS: ['script', 'style', 'iframe', 'form', 'button', 'svg', 'video', 'audio', 'embed', 'object'],
15
15
  FORBID_ATTR: ['onerror', 'onload', 'onclick']
16
16
  };
17
17
  const handleImageElements = node => {
@@ -29,8 +29,8 @@ const handleImageElements = node => {
29
29
  * @returns {boolean} - True if the URL is a safe relative URL
30
30
  */
31
31
  function isRelativeUrlWithoutEmbeddedUrls(url) {
32
- // Check if the string starts with a slash
33
- if (!url.startsWith('/')) {
32
+ // Check if the string starts with a slash but not with double slash (protocol-relative URLs)
33
+ if (!url.startsWith('/') || url.startsWith('//')) {
34
34
  return false;
35
35
  }
36
36
 
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ export { default as DuoChatLoader } from './components/chat/components/duo_chat_
5
5
  export { default as DuoChatMessage } from './components/chat/components/duo_chat_message/duo_chat_message';
6
6
  export { default as DuoChatMessageSources } from './components/chat/components/duo_chat_message_sources/duo_chat_message_sources';
7
7
  export { default as DuoChatPredefinedPrompts } from './components/chat/components/duo_chat_predefined_prompts/duo_chat_predefined_prompts';
8
+ export { default as MessageMap } from './components/chat/components/duo_chat_message/message_types/message_map';
8
9
  export { default as AgentMessage } from './components/chat/components/duo_chat_message/message_types/message_agent';
9
10
  export { default as InputRequestedMessage } from './components/chat/components/duo_chat_message/message_types/message_input_requested';
10
11
  export { default as SystemMessage } from './components/chat/components/duo_chat_message/message_types/message_tool';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/duo-ui",
3
- "version": "10.5.0",
3
+ "version": "10.6.1",
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.28.0",
99
99
  "@babel/preset-react": "^7.27.1",
100
- "@gitlab/eslint-plugin": "21.1.0",
100
+ "@gitlab/eslint-plugin": "21.2.0",
101
101
  "@gitlab/fonts": "^1.3.0",
102
102
  "@gitlab/stylelint-config": "6.2.2",
103
- "@gitlab/svgs": "^3.137.0",
103
+ "@gitlab/svgs": "^3.139.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.2",
159
- "playwright-core": "^1.53.2",
158
+ "playwright": "^1.54.0",
159
+ "playwright-core": "^1.54.0",
160
160
  "plop": "^2.5.4",
161
161
  "postcss": "8.4.28",
162
162
  "postcss-loader": "^7.0.2",
@@ -3,8 +3,6 @@ import {
3
3
  GlButton,
4
4
  GlIcon,
5
5
  GlAnimatedLoaderIcon,
6
- GlFormGroup,
7
- GlFormTextarea,
8
6
  GlTooltipDirective,
9
7
  GlSafeHtmlDirective as SafeHtml,
10
8
  } from '@gitlab/ui';
@@ -26,17 +24,7 @@ import {
26
24
  DUO_CODE_SCRIM_OFFSET,
27
25
  DUO_CODE_SCRIM_TOP_CLASS,
28
26
  } from './constants';
29
- import AgentMessage from './message_types/message_agent.vue';
30
- import InputRequestedMessage from './message_types/message_input_requested.vue';
31
- import ToolMessage from './message_types/message_tool.vue';
32
- import WorkflowEndMessage from './message_types/message_workflow_end.vue';
33
-
34
- const COMPONENT_FOR_MESSAGE_TYPE = {
35
- tool: () => ToolMessage,
36
- request: () => InputRequestedMessage,
37
- workflow_end: () => WorkflowEndMessage,
38
- agent: () => AgentMessage,
39
- };
27
+ import MessageMap from './message_types/message_map.vue';
40
28
 
41
29
  export const i18n = {
42
30
  CHAT_MESSAGE_COPIED: translate('DuoChatMessage.chatMessageCopied', 'Copied'),
@@ -55,8 +43,7 @@ export default {
55
43
  DocumentationSources,
56
44
  DuoChatContextItemSelections,
57
45
  MessageFeedback,
58
- GlFormGroup,
59
- GlFormTextarea,
46
+ MessageMap,
60
47
  GlIcon,
61
48
  GlAnimatedLoaderIcon,
62
49
  GlButton,
@@ -234,11 +221,6 @@ export default {
234
221
  this.message?.message_type?.toLowerCase() === type
235
222
  );
236
223
  },
237
- componentForMessageType(message) {
238
- return (
239
- COMPONENT_FOR_MESSAGE_TYPE[message.message_type]?.(message, this.messages) ?? AgentMessage
240
- );
241
- },
242
224
  setChunks() {
243
225
  if (this.isChunk) {
244
226
  const { chunkId, content } = this.message;
@@ -422,8 +404,7 @@ export default {
422
404
  </div>
423
405
  </transition>
424
406
  </template>
425
- <component
426
- :is="componentForMessageType(message)"
407
+ <message-map
427
408
  v-else
428
409
  :message="message"
429
410
  :with-feedback="withFeedback"
@@ -15,7 +15,18 @@ const duoMarked = new Marked([
15
15
  const config = {
16
16
  ADD_TAGS: ['insert-code-snippet', 'copy-code', 'gl-markdown', '#text', 'gl-compact-markdown'],
17
17
  ADD_ATTR: ['data-canonical-lang', 'data-sourcepos', 'lang', 'data-src', 'img'],
18
- FORBID_TAGS: ['script', 'style', 'iframe', 'form', 'button'],
18
+ FORBID_TAGS: [
19
+ 'script',
20
+ 'style',
21
+ 'iframe',
22
+ 'form',
23
+ 'button',
24
+ 'svg',
25
+ 'video',
26
+ 'audio',
27
+ 'embed',
28
+ 'object',
29
+ ],
19
30
  FORBID_ATTR: ['onerror', 'onload', 'onclick'],
20
31
  };
21
32
 
@@ -34,8 +45,8 @@ const handleImageElements = (node) => {
34
45
  * @returns {boolean} - True if the URL is a safe relative URL
35
46
  */
36
47
  function isRelativeUrlWithoutEmbeddedUrls(url) {
37
- // Check if the string starts with a slash
38
- if (!url.startsWith('/')) {
48
+ // Check if the string starts with a slash but not with double slash (protocol-relative URLs)
49
+ if (!url.startsWith('/') || url.startsWith('//')) {
39
50
  return false;
40
51
  }
41
52
 
package/src/index.js CHANGED
@@ -9,6 +9,7 @@ export { default as DuoChatMessageSources } from './components/chat/components/d
9
9
  export { default as DuoChatPredefinedPrompts } from './components/chat/components/duo_chat_predefined_prompts/duo_chat_predefined_prompts.vue';
10
10
 
11
11
  // Duo message type components
12
+ export { default as MessageMap } from './components/chat/components/duo_chat_message/message_types/message_map.vue';
12
13
  export { default as AgentMessage } from './components/chat/components/duo_chat_message/message_types/message_agent.vue';
13
14
  export { default as InputRequestedMessage } from './components/chat/components/duo_chat_message/message_types/message_input_requested.vue';
14
15
  export { default as SystemMessage } from './components/chat/components/duo_chat_message/message_types/message_tool.vue';