@gitlab/duo-ui 15.13.0 → 15.14.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.
Files changed (23) hide show
  1. package/dist/components/agentic_chat/web_agentic_duo_chat.js +14 -1
  2. package/dist/components/chat/components/duo_chat_conversation/duo_chat_conversation.js +14 -1
  3. package/dist/components/chat/components/duo_chat_message/duo_chat_message.js +14 -2
  4. package/dist/components/chat/components/duo_chat_message/message_types/index.js +6 -7
  5. package/dist/components/chat/components/duo_chat_message/message_types/message_map.js +48 -28
  6. package/dist/components/chat/components/duo_chat_message/message_types/message_tool.js +1 -7
  7. package/dist/components/chat/components/duo_chat_message/message_types/message_tool_approved.js +63 -0
  8. package/dist/components/chat/components/utils.js +3 -1
  9. package/dist/components/chat/constants.js +2 -1
  10. package/dist/index.js +11 -1
  11. package/package.json +7 -6
  12. package/src/components/agentic_chat/web_agentic_duo_chat.vue +14 -0
  13. package/src/components/chat/components/duo_chat_conversation/duo_chat_conversation.vue +14 -0
  14. package/src/components/chat/components/duo_chat_message/duo_chat_message.vue +14 -1
  15. package/src/components/chat/components/duo_chat_message/message_types/index.js +2 -13
  16. package/src/components/chat/components/duo_chat_message/message_types/message_map.vue +51 -30
  17. package/src/components/chat/components/duo_chat_message/message_types/message_tool.vue +1 -13
  18. package/src/components/chat/components/duo_chat_message/message_types/message_tool_approved.vue +32 -0
  19. package/src/components/chat/components/utils.js +9 -0
  20. package/src/components/chat/constants.js +10 -0
  21. package/src/index.js +10 -8
  22. package/dist/components/chat/components/duo_chat_message/message_types/constants.js +0 -11
  23. package/src/components/chat/components/duo_chat_message/message_types/constants.js +0 -15
@@ -6,6 +6,7 @@ import { VIEW_TYPES } from '../chat/components/duo_chat_header/constants';
6
6
  import DuoChatLoader from '../chat/components/duo_chat_loader/duo_chat_loader';
7
7
  import DuoChatPredefinedPrompts from '../chat/components/duo_chat_predefined_prompts/duo_chat_predefined_prompts';
8
8
  import DuoChatConversation from '../chat/components/duo_chat_conversation/duo_chat_conversation';
9
+ import { messageRenderersValidator } from '../chat/components/utils';
9
10
  import WebDuoChatHeader from '../chat/components/duo_chat_header/web_duo_chat_header';
10
11
  import DuoChatThreads from '../chat/components/duo_chat_threads/duo_chat_threads';
11
12
  import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
@@ -341,6 +342,18 @@ var script = {
341
342
  type: Boolean,
342
343
  required: false,
343
344
  default: false
345
+ },
346
+ /**
347
+ * Optional array of custom message renderers passed through to MessageMap.
348
+ * Each entry is an object with `component` (Vue component) and `matchMessage` (Function).
349
+ */
350
+ messageRenderers: {
351
+ type: Array,
352
+ required: false,
353
+ validator: messageRenderersValidator,
354
+ default() {
355
+ return [];
356
+ }
344
357
  }
345
358
  },
346
359
  data() {
@@ -772,7 +785,7 @@ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=
772
785
  'duo-chat-history gl-px-4',
773
786
  _vm.$scopedSlots['custom-empty-state'] && !_vm.hasMessages && !_vm.isLoading
774
787
  ? 'gl-m-auto'
775
- : 'gl-mt-auto gl-pb-4 gl-pt-6' ],attrs:{"mode":"out-in","tag":"section","name":_vm.$scopedSlots['custom-empty-state'] ? '' : 'message',"data-testid":"chat-messages"}},[_vm._l((_vm.conversations),function(conversation,index){return _c('duo-chat-conversation',{key:("conversation-" + index),attrs:{"enable-code-insertion":_vm.enableCodeInsertion,"messages":conversation,"show-delimiter":index > 0,"with-feedback":_vm.withFeedback,"is-tool-approval-processing":_vm.isToolApprovalProcessing,"working-directory":_vm.workingDirectory,"trusted-urls":_vm.trustedUrls,"is-binary-feedback-enabled":_vm.isBinaryFeedbackEnabled},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,"approve-tool":_vm.onApproveToolCall,"deny-tool":_vm.onDenyToolCall,"open-file-path":_vm.onOpenFilePath}})}),_vm._v(" "),(!_vm.hasMessages && !_vm.isLoading)?[_c('div',{key:"empty-state-container"},[_vm._t("custom-empty-state",function(){return [_c('div',{key:"empty-state-message",staticClass:"duo-chat-message gl-rounded-bl-none gl-leading-20 gl-text-default gl-break-anywhere",attrs:{"data-testid":"gl-duo-chat-empty-state"}},[_c('div',{staticClass:"gl-mb-[3.75rem] gl-flex gl-flex-col gl-items-center gl-justify-center gl-gap-3 gl-text-center"},[_c('h1',{staticClass:"gl-my-0 gl-text-[3.5rem]",attrs:{"data-testid":"gl-duo-chat-empty-state-emoji"}},[_vm._v("\n "+_vm._s(_vm.$options.i18n.CHAT_EMPTY_STATE_EMOJI)+"\n ")]),_vm._v(" "),(_vm.agentName)?_c('h2',{staticClass:"gl-heading-2 gl-my-0",attrs:{"data-testid":"gl-duo-chat-empty-state-greeting"}},[_vm._v("\n "+_vm._s(_vm.emptyStateGreeting)+"\n ")]):_vm._e(),_vm._v(" "),_c('h2',{staticClass:"gl-my-0 gl-text-size-h2",attrs:{"data-testid":"gl-duo-chat-empty-state-title"}},[_vm._v("\n "+_vm._s(_vm.emptyStateMainText)+"\n ")]),_vm._v(" "),_c('p',{staticClass:"gl-text-base gl-text-subtle",attrs:{"data-testid":"gl-duo-chat-empty-state-subtitle"}},[_vm._v("\n "+_vm._s(_vm.emptyStateSubText)+"\n ")])]),_vm._v(" "),_c('duo-chat-predefined-prompts',{key:"predefined-prompts",attrs:{"prompts":_vm.predefinedPrompts},on:{"click":_vm.sendPredefinedPrompt}})],1)]})],2)]:_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.shouldShowThreadList)?_c('footer',{staticClass:"duo-chat-drawer-footer gl-relative gl-z-2 gl-shrink-0",attrs:{"data-testid":"chat-footer"}},[_c('p',{staticClass:"gl-mb-3 gl-text-sm gl-text-subtle",class:{ 'gl-invisible': !_vm.hasAssistantMessages },attrs:{"data-testid":"chat-disclaimer"}},[_vm._v("\n "+_vm._s(_vm.$options.i18n.CHAT_DISCLAIMER)+"\n ")]),_vm._v(" "),_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('div',{staticClass:"duo-chat-input gl-relative gl-min-h-8 gl-max-w-full gl-grow gl-flex-col gl-rounded-bl-[12px] gl-rounded-br-[18px] gl-rounded-tl-[12px] gl-rounded-tr-[12px] gl-align-top forced-colors:gl-border"},[(_vm.$scopedSlots['agentic-model'] || _vm.$scopedSlots['agentic-switch'])?_c('div',{staticClass:"gl-flex gl-items-center gl-justify-between gl-gap-5 gl-border-0 gl-border-b-1 gl-border-solid gl-border-strong gl-px-4 gl-py-4 forced-colors:gl-border-none"},[_c('div',{staticClass:"duo-model-switcher gl-min-w-0 gl-max-w-full"},[_vm._t("agentic-model")],2),_vm._v(" "),_c('div',{staticClass:"duo-agent-mode-switcher gl-min-w-0 gl-max-w-full gl-shrink-0"},[_vm._t("agentic-switch")],2)]):_vm._e(),_vm._v(" "),_c('div',{staticClass:"duo-chat-input-wrap gl-relative gl-flex gl-grow gl-flex-col",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-w-full",attrs:{"disabled":!_vm.canSubmit || !_vm.isChatAvailable || !_vm.chatState.isEnabled,"data-testid":"chat-prompt-input","placeholder":_vm.inputPlaceholder,"character-count-limit":_vm.maxPromptLength,"textarea-classes":[
788
+ : 'gl-mt-auto gl-pb-4 gl-pt-6' ],attrs:{"mode":"out-in","tag":"section","name":_vm.$scopedSlots['custom-empty-state'] ? '' : 'message',"data-testid":"chat-messages"}},[_vm._l((_vm.conversations),function(conversation,index){return _c('duo-chat-conversation',{key:("conversation-" + index),attrs:{"enable-code-insertion":_vm.enableCodeInsertion,"messages":conversation,"show-delimiter":index > 0,"with-feedback":_vm.withFeedback,"is-tool-approval-processing":_vm.isToolApprovalProcessing,"working-directory":_vm.workingDirectory,"trusted-urls":_vm.trustedUrls,"is-binary-feedback-enabled":_vm.isBinaryFeedbackEnabled,"message-renderers":_vm.messageRenderers},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,"approve-tool":_vm.onApproveToolCall,"deny-tool":_vm.onDenyToolCall,"open-file-path":_vm.onOpenFilePath}})}),_vm._v(" "),(!_vm.hasMessages && !_vm.isLoading)?[_c('div',{key:"empty-state-container"},[_vm._t("custom-empty-state",function(){return [_c('div',{key:"empty-state-message",staticClass:"duo-chat-message gl-rounded-bl-none gl-leading-20 gl-text-default gl-break-anywhere",attrs:{"data-testid":"gl-duo-chat-empty-state"}},[_c('div',{staticClass:"gl-mb-[3.75rem] gl-flex gl-flex-col gl-items-center gl-justify-center gl-gap-3 gl-text-center"},[_c('h1',{staticClass:"gl-my-0 gl-text-[3.5rem]",attrs:{"data-testid":"gl-duo-chat-empty-state-emoji"}},[_vm._v("\n "+_vm._s(_vm.$options.i18n.CHAT_EMPTY_STATE_EMOJI)+"\n ")]),_vm._v(" "),(_vm.agentName)?_c('h2',{staticClass:"gl-heading-2 gl-my-0",attrs:{"data-testid":"gl-duo-chat-empty-state-greeting"}},[_vm._v("\n "+_vm._s(_vm.emptyStateGreeting)+"\n ")]):_vm._e(),_vm._v(" "),_c('h2',{staticClass:"gl-my-0 gl-text-size-h2",attrs:{"data-testid":"gl-duo-chat-empty-state-title"}},[_vm._v("\n "+_vm._s(_vm.emptyStateMainText)+"\n ")]),_vm._v(" "),_c('p',{staticClass:"gl-text-base gl-text-subtle",attrs:{"data-testid":"gl-duo-chat-empty-state-subtitle"}},[_vm._v("\n "+_vm._s(_vm.emptyStateSubText)+"\n ")])]),_vm._v(" "),_c('duo-chat-predefined-prompts',{key:"predefined-prompts",attrs:{"prompts":_vm.predefinedPrompts},on:{"click":_vm.sendPredefinedPrompt}})],1)]})],2)]:_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.shouldShowThreadList)?_c('footer',{staticClass:"duo-chat-drawer-footer gl-relative gl-z-2 gl-shrink-0",attrs:{"data-testid":"chat-footer"}},[_c('p',{staticClass:"gl-mb-3 gl-text-sm gl-text-subtle",class:{ 'gl-invisible': !_vm.hasAssistantMessages },attrs:{"data-testid":"chat-disclaimer"}},[_vm._v("\n "+_vm._s(_vm.$options.i18n.CHAT_DISCLAIMER)+"\n ")]),_vm._v(" "),_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('div',{staticClass:"duo-chat-input gl-relative gl-min-h-8 gl-max-w-full gl-grow gl-flex-col gl-rounded-bl-[12px] gl-rounded-br-[18px] gl-rounded-tl-[12px] gl-rounded-tr-[12px] gl-align-top forced-colors:gl-border"},[(_vm.$scopedSlots['agentic-model'] || _vm.$scopedSlots['agentic-switch'])?_c('div',{staticClass:"gl-flex gl-items-center gl-justify-between gl-gap-5 gl-border-0 gl-border-b-1 gl-border-solid gl-border-strong gl-px-4 gl-py-4 forced-colors:gl-border-none"},[_c('div',{staticClass:"duo-model-switcher gl-min-w-0 gl-max-w-full"},[_vm._t("agentic-model")],2),_vm._v(" "),_c('div',{staticClass:"duo-agent-mode-switcher gl-min-w-0 gl-max-w-full gl-shrink-0"},[_vm._t("agentic-switch")],2)]):_vm._e(),_vm._v(" "),_c('div',{staticClass:"duo-chat-input-wrap gl-relative gl-flex gl-grow gl-flex-col",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-w-full",attrs:{"disabled":!_vm.canSubmit || !_vm.isChatAvailable || !_vm.chatState.isEnabled,"data-testid":"chat-prompt-input","placeholder":_vm.inputPlaceholder,"character-count-limit":_vm.maxPromptLength,"textarea-classes":[
776
789
  '!gl-h-full',
777
790
  '!gl-bg-transparent',
778
791
  '!gl-py-4',
@@ -1,6 +1,7 @@
1
1
  import { translate } from '../../../../utils/i18n';
2
2
  import DuoChatMessage from '../duo_chat_message/duo_chat_message';
3
3
  import DuoChatMessageToolApproval from '../duo_chat_message_tool_approval/message_tool_approval';
4
+ import { messageRenderersValidator } from '../utils';
4
5
  import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
5
6
 
6
7
  const i18n = {
@@ -88,6 +89,18 @@ var script = {
88
89
  type: Boolean,
89
90
  required: false,
90
91
  default: false
92
+ },
93
+ /**
94
+ * Optional array of custom message renderers passed through to MessageMap.
95
+ * Each entry is an object with `component` (Vue component) and `matchMessage` (Function).
96
+ */
97
+ messageRenderers: {
98
+ type: Array,
99
+ required: false,
100
+ validator: messageRenderersValidator,
101
+ default() {
102
+ return [];
103
+ }
91
104
  }
92
105
  },
93
106
  computed: {
@@ -173,7 +186,7 @@ const __vue_script__ = script;
173
186
  /* template */
174
187
  var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{class:[
175
188
  'gl-flex gl-flex-col gl-justify-end gl-gap-6',
176
- { 'insert-code-hidden': !_vm.enableCodeInsertion } ]},[(_vm.showDelimiter)?_c('div',{staticClass:"gl-my-5 gl-flex gl-items-center gl-gap-4 gl-text-gray-500",attrs:{"data-testid":"conversation-delimiter"}},[_c('hr',{staticClass:"gl-grow"}),_vm._v(" "),_c('span',[_vm._v(_vm._s(_vm.$options.i18n.CONVERSATION_NEW_CHAT))]),_vm._v(" "),_c('hr',{staticClass:"gl-grow"})]):_vm._e(),_vm._v(" "),_vm._l((_vm.messages),function(msg,index){return _c('duo-chat-message',{key:((msg.role) + "-" + index),attrs:{"message":msg,"trusted-urls":_vm.trustedUrls,"is-cancelled":_vm.canceledRequestIds.includes(msg.requestId),"with-feedback":_vm.withFeedback,"working-directory":_vm.workingDirectory,"is-binary-feedback-enabled":_vm.isBinaryFeedbackEnabled,"show-binary-feedback":index === _vm.lastAssistantMessageIndex},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.hasPendingToolApprovals)?_c('duo-chat-message-tool-approval',{attrs:{"messages":_vm.pendingToolApprovals,"is-processing":_vm.isToolApprovalProcessing,"approval-options":_vm.toolApprovalOptions},on:{"approve-tool":_vm.onApproveToolCall,"deny-tool":_vm.onDenyToolCall}}):_vm._e()],2)};
189
+ { 'insert-code-hidden': !_vm.enableCodeInsertion } ]},[(_vm.showDelimiter)?_c('div',{staticClass:"gl-my-5 gl-flex gl-items-center gl-gap-4 gl-text-gray-500",attrs:{"data-testid":"conversation-delimiter"}},[_c('hr',{staticClass:"gl-grow"}),_vm._v(" "),_c('span',[_vm._v(_vm._s(_vm.$options.i18n.CONVERSATION_NEW_CHAT))]),_vm._v(" "),_c('hr',{staticClass:"gl-grow"})]):_vm._e(),_vm._v(" "),_vm._l((_vm.messages),function(msg,index){return _c('duo-chat-message',{key:((msg.role) + "-" + index),attrs:{"message":msg,"trusted-urls":_vm.trustedUrls,"is-cancelled":_vm.canceledRequestIds.includes(msg.requestId),"with-feedback":_vm.withFeedback,"working-directory":_vm.workingDirectory,"is-binary-feedback-enabled":_vm.isBinaryFeedbackEnabled,"show-binary-feedback":index === _vm.lastAssistantMessageIndex,"message-renderers":_vm.messageRenderers},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.hasPendingToolApprovals)?_c('duo-chat-message-tool-approval',{attrs:{"messages":_vm.pendingToolApprovals,"is-processing":_vm.isToolApprovalProcessing,"approval-options":_vm.toolApprovalOptions},on:{"approve-tool":_vm.onApproveToolCall,"deny-tool":_vm.onDenyToolCall}}):_vm._e()],2)};
177
190
  var __vue_staticRenderFns__ = [];
178
191
 
179
192
  /* style */
@@ -4,7 +4,7 @@ import { throttle } from 'lodash-es';
4
4
  import DuoChatContextItemSelections from '../duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections';
5
5
  import { SELECTED_CONTEXT_ITEMS_DEFAULT_COLLAPSED, MESSAGE_MODEL_ROLES } from '../../constants';
6
6
  import DocumentationSources from '../duo_chat_message_sources/duo_chat_message_sources';
7
- import { concatUntilEmpty, copyToClipboard } from '../utils';
7
+ import { messageRenderersValidator, concatUntilEmpty, copyToClipboard } from '../utils';
8
8
  import AgenticBinaryFeedback from '../../../agentic_chat/components/agentic_binary_feedback/agentic_binary_feedback';
9
9
  import AgenticFeedbackPanel from '../../../agentic_chat/components/agentic_feedback_panel/agentic_feedback_panel';
10
10
  import MessageActionBar from '../message_action_bar/message_action_bar';
@@ -111,6 +111,18 @@ var script = {
111
111
  type: Boolean,
112
112
  required: false,
113
113
  default: false
114
+ },
115
+ /**
116
+ * Optional array of custom message renderers to pass to MessageMap.
117
+ * Each entry is an object with `component` (Vue component) and `matchMessage` (Function).
118
+ */
119
+ messageRenderers: {
120
+ type: Array,
121
+ required: false,
122
+ validator: messageRenderersValidator,
123
+ default() {
124
+ return [];
125
+ }
114
126
  }
115
127
  },
116
128
  data() {
@@ -344,7 +356,7 @@ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=
344
356
  },attrs:{"is-html":_vm.hasContentHtml,"markdown":_vm.messageContent,"trusted-urls":_vm.trustedUrls}}),_vm._v(" "),(_vm.sources)?_c('documentation-sources',{attrs:{"sources":_vm.sources}}):_vm._e(),_vm._v(" "),(_vm.isAssistantMessage)?[_c('message-action-bar',[(_vm.isChunkAndNotCancelled)?_c('gl-animated-loader-icon',{attrs:{"is-on":true}}):_vm._e(),_vm._v(" "),(_vm.shouldShowFeedbackLink && _vm.isBinaryFeedbackEnabled)?[(_vm.showBinaryFeedback)?_c('agentic-binary-feedback',{attrs:{"feedback-choice":_vm.feedbackChoice,"submitted":_vm.feedbackSubmitted,"data-testid":"agentic-feedback-latest"},on:{"select-type":_vm.onFeedbackTypeSelected}}):_c('div',{staticClass:"agentic-feedback-hover-wrapper",class:{
345
357
  '-gl-mr-3 gl-w-0 gl-overflow-hidden gl-opacity-0':
346
358
  !_vm.feedbackChoice && !_vm.feedbackSubmitted,
347
- },attrs:{"data-testid":"agentic-feedback-hover-container"}},[_c('agentic-binary-feedback',{attrs:{"feedback-choice":_vm.feedbackChoice,"submitted":_vm.feedbackSubmitted},on:{"select-type":_vm.onFeedbackTypeSelected}})],1)]:(_vm.shouldShowFeedbackLink)?_c('message-feedback',{attrs:{"has-feedback":_vm.hasFeedback},on:{"feedback":_vm.logEvent}}):_vm._e(),_vm._v(" "),(_vm.shouldShowCopyAction)?_c('gl-button',{directives:[{name:"gl-tooltip",rawName:"v-gl-tooltip"}],class:{ '!gl-text-success': _vm.copied, '!gl-text-subtle': !_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","size":"small"},on:{"click":_vm.copyMessage,"focusout":function($event){_vm.copied = false;}}}):_vm._e()],2),_vm._v(" "),(_vm.feedbackChoice && !_vm.feedbackSubmitted)?_c('agentic-feedback-panel',{attrs:{"feedback-type":_vm.feedbackChoice},on:{"submit":_vm.onFeedbackPanelSubmit,"close":_vm.onFeedbackPanelClose}}):_vm._e()]:_vm._e(),_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()]:_c('message-map',{attrs:{"message":_vm.message,"with-feedback":_vm.withFeedback,"working-directory":_vm.workingDirectory,"data-testid":"workflow-message"},on:{"open-file-path":_vm.onOpenFilePath,"feedback":_vm.logEvent}})],2)};
359
+ },attrs:{"data-testid":"agentic-feedback-hover-container"}},[_c('agentic-binary-feedback',{attrs:{"feedback-choice":_vm.feedbackChoice,"submitted":_vm.feedbackSubmitted},on:{"select-type":_vm.onFeedbackTypeSelected}})],1)]:(_vm.shouldShowFeedbackLink)?_c('message-feedback',{attrs:{"has-feedback":_vm.hasFeedback},on:{"feedback":_vm.logEvent}}):_vm._e(),_vm._v(" "),(_vm.shouldShowCopyAction)?_c('gl-button',{directives:[{name:"gl-tooltip",rawName:"v-gl-tooltip"}],class:{ '!gl-text-success': _vm.copied, '!gl-text-subtle': !_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","size":"small"},on:{"click":_vm.copyMessage,"focusout":function($event){_vm.copied = false;}}}):_vm._e()],2),_vm._v(" "),(_vm.feedbackChoice && !_vm.feedbackSubmitted)?_c('agentic-feedback-panel',{attrs:{"feedback-type":_vm.feedbackChoice},on:{"submit":_vm.onFeedbackPanelSubmit,"close":_vm.onFeedbackPanelClose}}):_vm._e()]:_vm._e(),_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()]:_c('message-map',{attrs:{"message":_vm.message,"message-renderers":_vm.messageRenderers,"with-feedback":_vm.withFeedback,"working-directory":_vm.workingDirectory,"data-testid":"workflow-message"},on:{"open-file-path":_vm.onOpenFilePath,"feedback":_vm.logEvent}})],2)};
348
360
  var __vue_staticRenderFns__ = [];
349
361
 
350
362
  /* style */
@@ -1,11 +1,14 @@
1
+ import { MESSAGE_MODEL_ROLES, VALID_MESSAGE_TYPES } from '../../../constants';
1
2
  import AgentMessage from './message_agent';
2
3
  import InputRequestedMessage from './message_input_requested';
3
4
  import SystemMessage from './message_tool';
4
5
  import WorkflowEndMessage from './message_workflow_end';
5
6
  import MessageMap from './message_map';
6
- import { VALID_MESSAGE_TYPES, AGENT_MESSAGE_TYPE, USER_MESSAGE_TYPE, FLOW_END_TYPE, INPUT_REQUEST_TYPE, TOOL_MESSAGE_TYPE } from './constants';
7
7
 
8
8
  // Direct imports for backward compatibility
9
+
10
+ // Import constants and factory functions
11
+
9
12
  var index = {
10
13
  // Main wrapper component - recommended for new consumers
11
14
  MessageMap,
@@ -17,12 +20,8 @@ var index = {
17
20
  ToolMessage: SystemMessage,
18
21
  WorkflowEndMessage,
19
22
  // Constants and utility functions
20
- VALID_MESSAGE_TYPES,
21
- AGENT_MESSAGE_TYPE,
22
- USER_MESSAGE_TYPE,
23
- FLOW_END_TYPE,
24
- INPUT_REQUEST_TYPE,
25
- TOOL_MESSAGE_TYPE
23
+ MESSAGE_MODEL_ROLES,
24
+ VALID_MESSAGE_TYPES
26
25
  };
27
26
 
28
27
  export default index;
@@ -1,18 +1,35 @@
1
+ import { MESSAGE_MODEL_ROLES, APPROVAL_TOOL_NAMES } from '../../../constants';
2
+ import { messageRenderersValidator } from '../../utils';
1
3
  import AgentMessage from './message_agent';
2
4
  import UserMessage from './message_user';
3
5
  import InputRequestedMessage from './message_input_requested';
4
- import SystemMessage from './message_tool';
6
+ import MessageToolVisualization from './message_tool';
7
+ import MessageToolApproved from './message_tool_approved';
5
8
  import WorkflowEndMessage from './message_workflow_end';
6
- import { AGENT_MESSAGE_TYPE, FLOW_END_TYPE, INPUT_REQUEST_TYPE, TOOL_MESSAGE_TYPE, USER_MESSAGE_TYPE } from './constants';
7
9
  import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
8
10
 
9
- const DEFAULT_COMPONENT_MAP = {
10
- [AGENT_MESSAGE_TYPE]: AgentMessage,
11
- [FLOW_END_TYPE]: WorkflowEndMessage,
12
- [INPUT_REQUEST_TYPE]: InputRequestedMessage,
13
- [TOOL_MESSAGE_TYPE]: SystemMessage,
14
- [USER_MESSAGE_TYPE]: UserMessage
15
- };
11
+ const DEFAULT_COMPONENT_MAP = [{
12
+ component: AgentMessage,
13
+ matchMessage: message => message.message_type === MESSAGE_MODEL_ROLES.agent
14
+ }, {
15
+ component: WorkflowEndMessage,
16
+ matchMessage: message => message.message_type === MESSAGE_MODEL_ROLES.workflow_end
17
+ }, {
18
+ component: InputRequestedMessage,
19
+ matchMessage: message => message.message_type === MESSAGE_MODEL_ROLES.request
20
+ }, {
21
+ component: MessageToolApproved,
22
+ matchMessage: message => {
23
+ var _message$tool_info;
24
+ return message.message_type === MESSAGE_MODEL_ROLES.tool && Object.values(APPROVAL_TOOL_NAMES).includes((_message$tool_info = message.tool_info) === null || _message$tool_info === void 0 ? void 0 : _message$tool_info.name);
25
+ }
26
+ }, {
27
+ component: MessageToolVisualization,
28
+ matchMessage: message => message.message_type === MESSAGE_MODEL_ROLES.tool
29
+ }, {
30
+ component: UserMessage,
31
+ matchMessage: message => message.message_type === MESSAGE_MODEL_ROLES.user
32
+ }];
16
33
  var script = {
17
34
  name: 'MessageMap',
18
35
  props: {
@@ -27,13 +44,16 @@ var script = {
27
44
  }
28
45
  },
29
46
  /**
30
- * Optional custom component map to override or add default message components
47
+ * Optional array of custom message renderers to override or extend the default message components.
48
+ * Each entry is an object with `component` (Vue component) and `matchMessage` (Function).
49
+ * Custom entries are evaluated before defaults, allowing overrides.
31
50
  */
32
- componentMap: {
33
- type: Object,
51
+ messageRenderers: {
52
+ type: Array,
34
53
  required: false,
54
+ validator: messageRenderersValidator,
35
55
  default() {
36
- return {};
56
+ return [];
37
57
  }
38
58
  },
39
59
  /**
@@ -54,22 +74,22 @@ var script = {
54
74
  }
55
75
  },
56
76
  computed: {
57
- messageMap() {
58
- return {
59
- ...DEFAULT_COMPONENT_MAP,
60
- ...this.componentMap
61
- };
62
- },
63
77
  messageComponent() {
64
- var _this$message;
65
- return this.isValidMessageType ? this.messageMap[(_this$message = this.message) === null || _this$message === void 0 ? void 0 : _this$message.message_type] : AgentMessage;
66
- },
67
- validMessageTypes() {
68
- return [...Object.keys(DEFAULT_COMPONENT_MAP), ...Object.keys(this.componentMap)];
69
- },
70
- isValidMessageType() {
71
- var _this$message2;
72
- return this.validMessageTypes.includes((_this$message2 = this.message) === null || _this$message2 === void 0 ? void 0 : _this$message2.message_type);
78
+ const combined = [...this.messageRenderers, ...DEFAULT_COMPONENT_MAP];
79
+ try {
80
+ var _match$component;
81
+ const match = combined.find(_ref => {
82
+ let {
83
+ matchMessage
84
+ } = _ref;
85
+ return matchMessage(this.message);
86
+ });
87
+ return (_match$component = match === null || match === void 0 ? void 0 : match.component) !== null && _match$component !== void 0 ? _match$component : AgentMessage;
88
+ } catch (e) {
89
+ // eslint-disable-next-line no-console
90
+ console.warn('Invalid message renderer found. Falling back to the default agent message renderer', e);
91
+ return AgentMessage;
92
+ }
73
93
  }
74
94
  }
75
95
  };
@@ -1,7 +1,5 @@
1
1
  import { GlButton, GlCollapse, GlIcon, GlLink } from '@gitlab/ui';
2
2
  import { translate } from '../../../../../utils/i18n';
3
- import MessageToolApproval from '../../duo_chat_message_tool_approval/message_tool_approval';
4
- import { APPROVAL_TOOL_NAMES } from '../../../constants';
5
3
  import MessageToolDetails from './message_tool_details';
6
4
  import BaseMessage from './message_base';
7
5
  import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
@@ -20,7 +18,6 @@ var script = {
20
18
  name: 'DuoToolMessage',
21
19
  components: {
22
20
  BaseMessage,
23
- MessageToolApproval,
24
21
  MessageToolDetails,
25
22
  GlButton,
26
23
  GlCollapse,
@@ -156,9 +153,6 @@ var script = {
156
153
  collapseIconName() {
157
154
  return this.isDetailsOpen ? 'chevron-down' : 'chevron-right';
158
155
  },
159
- requiresApproval() {
160
- return Object.values(APPROVAL_TOOL_NAMES).includes(this.toolName);
161
- },
162
156
  shouldShowDetails() {
163
157
  var _this$message;
164
158
  return !hiddenSubTypes.includes((_this$message = this.message) === null || _this$message === void 0 ? void 0 : _this$message.message_sub_type) && (this.hasToolInfoArgs || this.hasToolInfoResponse);
@@ -189,7 +183,7 @@ var script = {
189
183
  const __vue_script__ = script;
190
184
 
191
185
  /* template */
192
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return (_vm.requiresApproval)?_c('message-tool-approval',{attrs:{"messages":[_vm.message],"working-directory":_vm.workingDirectory,"approval-status":"approved"}}):_c('base-message',{attrs:{"message":_vm.message},scopedSlots:_vm._u([{key:"message",fn:function(ref){
186
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('base-message',{attrs:{"message":_vm.message},scopedSlots:_vm._u([{key:"message",fn:function(ref){
193
187
  var content = ref.content;
194
188
  return [_c('div',{staticClass:"gl-border gl-flex-col gl-rounded-lg gl-border-default gl-p-4"},[_c('div',{staticClass:"gl-flex gl-flex-col gl-gap-2 gl-text-base gl-text-subtle"},[_c('div',{staticClass:"gl-flex gl-items-start gl-gap-x-3"},[_c('div',{staticClass:"gl-mt-1 gl-flex gl-flex-shrink-0"},[_c('gl-icon',{attrs:{"name":_vm.iconName}})],1),_vm._v(" "),_c('div',{staticClass:"gl-flex gl-min-w-0 gl-grow gl-flex-wrap gl-items-center gl-gap-x-2"},[_c('span',{attrs:{"data-testid":"tool-message-content"}},[_vm._v(_vm._s(content))]),_vm._v(" "),(_vm.shouldShowFilePath)?_c('gl-link',{staticClass:"file-path-link gl-min-w-0 gl-break-all gl-font-monospace",attrs:{"data-testid":"tool-message-file-path-link"},on:{"click":function($event){$event.preventDefault();return _vm.onOpenFilePath(_vm.messageFilePath)}}},[_c('code',{staticClass:"gl-rounded-base gl-px-2 gl-py-1 gl-text-default"},[_vm._v(_vm._s(_vm.messageFilePath))])]):_vm._e()],1),_vm._v(" "),(_vm.shouldShowDetails)?_c('gl-button',{staticClass:"-gl-mt-1",attrs:{"category":"tertiary","size":"small","icon":_vm.collapseIconName,"aria-label":_vm.isDetailsOpen ? _vm.$options.i18n.COLLAPSE_DETAILS : _vm.$options.i18n.EXPAND_DETAILS,"data-testid":"tool-message-toggle-button"},on:{"click":_vm.toggleDetails}}):_vm._e()],1),_vm._v(" "),(_vm.hasMetadataToShow)?_c('div',{staticClass:"gl-ml-5 gl-flex gl-flex-wrap gl-gap-2 gl-text-sm"},[(_vm.shouldShowProjectId)?_c('div',{staticClass:"gl-flex gl-min-w-0 gl-gap-x-2 gl-rounded-full gl-bg-strong gl-px-3",attrs:{"data-testid":"tool-message-project-info"}},[_c('span',{staticClass:"gl-whitespace-nowrap"},[_vm._v(_vm._s(_vm.$options.i18n.PROJECT_LABEL)+":")]),_vm._v(" "),_c('span',{staticClass:"gl-truncate",attrs:{"data-testid":"tool-message-project-id"}},[_vm._v(_vm._s(_vm.projectId))])]):_vm._e(),_vm._v(" "),(_vm.shouldShowBranch)?_c('div',{staticClass:"gl-flex gl-min-w-0 gl-gap-x-2 gl-rounded-full gl-bg-strong gl-px-3",attrs:{"data-testid":"tool-message-branch-info"}},[_c('span',{staticClass:"gl-whitespace-nowrap"},[_vm._v(_vm._s(_vm.$options.i18n.BRANCH_LABEL)+":")]),_vm._v(" "),_c('span',{staticClass:"gl-truncate",attrs:{"data-testid":"tool-message-branch-name"}},[_vm._v(_vm._s(_vm.branchName))])]):_vm._e(),_vm._v(" "),(_vm.shouldShowStartBranch)?_c('div',{staticClass:"gl-flex gl-min-w-0 gl-gap-x-2 gl-rounded-full gl-bg-strong gl-px-3",attrs:{"data-testid":"tool-message-start-branch-info"}},[_c('span',{staticClass:"gl-whitespace-nowrap"},[_vm._v(_vm._s(_vm.$options.i18n.START_BRANCH_LABEL)+":")]),_vm._v(" "),_c('span',{staticClass:"gl-truncate",attrs:{"data-testid":"tool-message-start-branch-name"}},[_vm._v(_vm._s(_vm.startBranch))])]):_vm._e()]):_vm._e()]),_vm._v(" "),(_vm.shouldShowDetails)?_c('gl-collapse',{staticClass:"gl-overflow-hidden",attrs:{"visible":_vm.isDetailsOpen}},[_c('message-tool-details',{attrs:{"message":_vm.message,"is-expanded":_vm.isDetailsOpen},on:{"copy-code-snippet":_vm.onCopyCodeSnippet}})],1):_vm._e()],1)]}}])})};
195
189
  var __vue_staticRenderFns__ = [];
@@ -0,0 +1,63 @@
1
+ import MessageToolApproval from '../../duo_chat_message_tool_approval/message_tool_approval';
2
+ import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
3
+
4
+ var script = {
5
+ name: 'MessageToolApproved',
6
+ components: {
7
+ MessageToolApproval
8
+ },
9
+ props: {
10
+ message: {
11
+ required: true,
12
+ type: Object
13
+ },
14
+ workingDirectory: {
15
+ required: false,
16
+ type: String,
17
+ default: ''
18
+ }
19
+ },
20
+ computed: {
21
+ messages() {
22
+ return [this.message];
23
+ }
24
+ }
25
+ };
26
+
27
+ /* script */
28
+ const __vue_script__ = script;
29
+
30
+ /* template */
31
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('message-tool-approval',_vm._g({attrs:{"messages":_vm.messages,"working-directory":_vm.workingDirectory,"approval-status":"approved"}},_vm.$listeners))};
32
+ var __vue_staticRenderFns__ = [];
33
+
34
+ /* style */
35
+ const __vue_inject_styles__ = undefined;
36
+ /* scoped */
37
+ const __vue_scope_id__ = undefined;
38
+ /* module identifier */
39
+ const __vue_module_identifier__ = undefined;
40
+ /* functional template */
41
+ const __vue_is_functional_template__ = false;
42
+ /* style inject */
43
+
44
+ /* style inject SSR */
45
+
46
+ /* style inject shadow dom */
47
+
48
+
49
+
50
+ const __vue_component__ = __vue_normalize__(
51
+ { render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ },
52
+ __vue_inject_styles__,
53
+ __vue_script__,
54
+ __vue_scope_id__,
55
+ __vue_is_functional_template__,
56
+ __vue_module_identifier__,
57
+ false,
58
+ undefined,
59
+ undefined,
60
+ undefined
61
+ );
62
+
63
+ export default __vue_component__;
@@ -1,3 +1,5 @@
1
+ const isValidMessageRenderer = renderer => renderer !== null && typeof renderer === 'object' && typeof renderer.component === 'object' && typeof renderer.matchMessage === 'function';
2
+ const messageRenderersValidator = renderers => Array.isArray(renderers) && renderers.every(renderer => isValidMessageRenderer(renderer));
1
3
  const concatUntilEmpty = arr => {
2
4
  if (!arr) return '';
3
5
  let end = arr.findIndex(el => !el);
@@ -53,4 +55,4 @@ const copyToClipboard = (textToCopy, el) => {
53
55
  }
54
56
  };
55
57
 
56
- export { concatUntilEmpty, copyToClipboard };
58
+ export { concatUntilEmpty, copyToClipboard, isValidMessageRenderer, messageRenderersValidator };
@@ -25,6 +25,7 @@ const MESSAGE_MODEL_ROLES = {
25
25
  request: 'request',
26
26
  workflow_end: 'workflow_end'
27
27
  };
28
+ const VALID_MESSAGE_TYPES = [MESSAGE_MODEL_ROLES.agent, MESSAGE_MODEL_ROLES.workflow_end, MESSAGE_MODEL_ROLES.request, MESSAGE_MODEL_ROLES.tool, MESSAGE_MODEL_ROLES.user, MESSAGE_MODEL_ROLES.system, MESSAGE_MODEL_ROLES.assistant];
28
29
  const SELECTED_CONTEXT_ITEMS_DEFAULT_COLLAPSED = true;
29
30
 
30
31
  // TODO this should be a shared util in GitLab UI
@@ -60,4 +61,4 @@ const APPROVAL_TOOL_NAMES = {
60
61
  runGitCommand: 'run_git_command'
61
62
  };
62
63
 
63
- export { APPROVAL_TOOL_NAMES, CHAT_BASE_COMMANDS, CHAT_CLEAR_MESSAGE, CHAT_INCLUDE_MESSAGE, CHAT_NEW_MESSAGE, CHAT_RESET_MESSAGE, DEFAULT_DEBOUNCE_AND_THROTTLE_MS, DOCUMENTATION_SOURCE_TYPES, LOADING_TRANSITION_DURATION, MAX_PROMPT_LENGTH, MESSAGE_MODEL_ROLES, PROMPT_LENGTH_WARNING, SELECTED_CONTEXT_ITEMS_DEFAULT_COLLAPSED, acceptedApproveToolPayloads, badgeTypeValidator, badgeTypes };
64
+ export { APPROVAL_TOOL_NAMES, CHAT_BASE_COMMANDS, CHAT_CLEAR_MESSAGE, CHAT_INCLUDE_MESSAGE, CHAT_NEW_MESSAGE, CHAT_RESET_MESSAGE, DEFAULT_DEBOUNCE_AND_THROTTLE_MS, DOCUMENTATION_SOURCE_TYPES, LOADING_TRANSITION_DURATION, MAX_PROMPT_LENGTH, MESSAGE_MODEL_ROLES, PROMPT_LENGTH_WARNING, SELECTED_CONTEXT_ITEMS_DEFAULT_COLLAPSED, VALID_MESSAGE_TYPES, acceptedApproveToolPayloads, badgeTypeValidator, badgeTypes };
package/dist/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { MESSAGE_MODEL_ROLES } from './components/chat/constants';
2
+ export { MESSAGE_MODEL_ROLES, VALID_MESSAGE_TYPES } from './components/chat/constants';
1
3
  export { default as DuoUserFeedback } from './components/user_feedback/user_feedback';
2
4
  export { default as DuoChat } from './components/chat/duo_chat';
3
5
  export { default as DuoChatContextConversation } from './components/chat/components/duo_chat_conversation/duo_chat_conversation';
@@ -5,7 +7,6 @@ export { default as DuoChatLoader } from './components/chat/components/duo_chat_
5
7
  export { default as DuoChatMessage } from './components/chat/components/duo_chat_message/duo_chat_message';
6
8
  export { default as DuoChatMessageSources } from './components/chat/components/duo_chat_message_sources/duo_chat_message_sources';
7
9
  export { default as DuoChatPredefinedPrompts } from './components/chat/components/duo_chat_predefined_prompts/duo_chat_predefined_prompts';
8
- export { AGENT_MESSAGE_TYPE, FLOW_END_TYPE, INPUT_REQUEST_TYPE, TOOL_MESSAGE_TYPE, USER_MESSAGE_TYPE, VALID_MESSAGE_TYPES } from './components/chat/components/duo_chat_message/message_types/constants';
9
10
  export { default as MessageMap } from './components/chat/components/duo_chat_message/message_types/message_map';
10
11
  export { default as AgentMessage } from './components/chat/components/duo_chat_message/message_types/message_agent';
11
12
  export { default as InputRequestedMessage } from './components/chat/components/duo_chat_message/message_types/message_input_requested';
@@ -27,3 +28,12 @@ export { default as DuoLayout } from './components/ui/duo_layout/duo_layout';
27
28
  export { default as SideRail } from './components/ui/side_rail/side_rail';
28
29
  export { default as WebDuoChat } from './components/chat/web_duo_chat';
29
30
  export { default as WebAgenticDuoChat } from './components/agentic_chat/web_agentic_duo_chat';
31
+
32
+ // Legacy message type exports for backward compatibility
33
+ const AGENT_MESSAGE_TYPE = MESSAGE_MODEL_ROLES.agent;
34
+ const USER_MESSAGE_TYPE = MESSAGE_MODEL_ROLES.user;
35
+ const FLOW_END_TYPE = MESSAGE_MODEL_ROLES.workflow_end;
36
+ const INPUT_REQUEST_TYPE = MESSAGE_MODEL_ROLES.request;
37
+ const TOOL_MESSAGE_TYPE = MESSAGE_MODEL_ROLES.tool;
38
+
39
+ export { AGENT_MESSAGE_TYPE, FLOW_END_TYPE, INPUT_REQUEST_TYPE, TOOL_MESSAGE_TYPE, USER_MESSAGE_TYPE };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/duo-ui",
3
- "version": "15.13.0",
3
+ "version": "15.14.0",
4
4
  "description": "Duo UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -105,7 +105,7 @@
105
105
  "@gitlab/fonts": "^1.3.1",
106
106
  "@gitlab/stylelint-config": "6.3.0",
107
107
  "@gitlab/svgs": "^3.157.0",
108
- "@gitlab/ui": "^128.15.0",
108
+ "@gitlab/ui": "^129.2.0",
109
109
  "@jest/test-sequencer": "^29.7.0",
110
110
  "@rollup/plugin-commonjs": "^11.1.0",
111
111
  "@rollup/plugin-node-resolve": "^7.1.3",
@@ -124,10 +124,11 @@
124
124
  "@storybook/vue3": "^7.6.20",
125
125
  "@storybook/vue3-webpack5": "^7.6.20",
126
126
  "@types/jest-image-snapshot": "^6.4.0",
127
- "@vue/compat": "^3.2.40",
128
- "@vue/compiler-sfc": "^3.2.40",
127
+ "@vue/compat": "3.5.30",
128
+ "@vue/compiler-sfc": "3.5.30",
129
+ "@vue/server-renderer": "3.5.30",
129
130
  "@vue/test-utils": "1.3.0",
130
- "@vue/test-utils-vue3": "npm:@vue/test-utils@2.2.0",
131
+ "@vue/test-utils-vue3": "npm:@vue/test-utils@^2.4.6",
131
132
  "@vue/vue2-jest": "29.0.0",
132
133
  "@vue/vue3-jest": "^29.1.1",
133
134
  "@yarnpkg/lockfile": "^1.1.0",
@@ -192,7 +193,7 @@
192
193
  "vue-loader-vue3": "npm:vue-loader@17",
193
194
  "vue-router": "3",
194
195
  "vue-template-compiler": "2.7.16",
195
- "vue-test-utils-compat": "^0.0.10",
196
+ "vue-test-utils-compat": "^0.0.15",
196
197
  "webpack": "^5.9.0",
197
198
  "yargs": "^17.3.1"
198
199
  },
@@ -26,6 +26,7 @@ import { VIEW_TYPES } from '../chat/components/duo_chat_header/constants';
26
26
  import DuoChatLoader from '../chat/components/duo_chat_loader/duo_chat_loader.vue';
27
27
  import DuoChatPredefinedPrompts from '../chat/components/duo_chat_predefined_prompts/duo_chat_predefined_prompts.vue';
28
28
  import DuoChatConversation from '../chat/components/duo_chat_conversation/duo_chat_conversation.vue';
29
+ import { messageRenderersValidator } from '../chat/components/utils';
29
30
  import WebDuoChatHeader from '../chat/components/duo_chat_header/web_duo_chat_header.vue';
30
31
  import DuoChatThreads from '../chat/components/duo_chat_threads/duo_chat_threads.vue';
31
32
 
@@ -406,6 +407,18 @@ export default {
406
407
  required: false,
407
408
  default: false,
408
409
  },
410
+ /**
411
+ * Optional array of custom message renderers passed through to MessageMap.
412
+ * Each entry is an object with `component` (Vue component) and `matchMessage` (Function).
413
+ */
414
+ messageRenderers: {
415
+ type: Array,
416
+ required: false,
417
+ validator: messageRenderersValidator,
418
+ default() {
419
+ return [];
420
+ },
421
+ },
409
422
  },
410
423
  data() {
411
424
  return {
@@ -922,6 +935,7 @@ export default {
922
935
  :working-directory="workingDirectory"
923
936
  :trusted-urls="trustedUrls"
924
937
  :is-binary-feedback-enabled="isBinaryFeedbackEnabled"
938
+ :message-renderers="messageRenderers"
925
939
  @track-feedback="onTrackFeedback"
926
940
  @insert-code-snippet="onInsertCodeSnippet"
927
941
  @copy-code-snippet="onCopyCodeSnippet"
@@ -2,6 +2,7 @@
2
2
  import { translate } from '../../../../utils/i18n';
3
3
  import DuoChatMessage from '../duo_chat_message/duo_chat_message.vue';
4
4
  import DuoChatMessageToolApproval from '../duo_chat_message_tool_approval/message_tool_approval.vue';
5
+ import { messageRenderersValidator } from '../utils';
5
6
 
6
7
  const i18n = {
7
8
  CONVERSATION_NEW_CHAT: translate('DuoChatConversation.newChat', 'New chat'),
@@ -91,6 +92,18 @@ export default {
91
92
  required: false,
92
93
  default: false,
93
94
  },
95
+ /**
96
+ * Optional array of custom message renderers passed through to MessageMap.
97
+ * Each entry is an object with `component` (Vue component) and `matchMessage` (Function).
98
+ */
99
+ messageRenderers: {
100
+ type: Array,
101
+ required: false,
102
+ validator: messageRenderersValidator,
103
+ default() {
104
+ return [];
105
+ },
106
+ },
94
107
  },
95
108
  computed: {
96
109
  /**
@@ -196,6 +209,7 @@ export default {
196
209
  :working-directory="workingDirectory"
197
210
  :is-binary-feedback-enabled="isBinaryFeedbackEnabled"
198
211
  :show-binary-feedback="index === lastAssistantMessageIndex"
212
+ :message-renderers="messageRenderers"
199
213
  @track-feedback="onTrackFeedback"
200
214
  @insert-code-snippet="onInsertCodeSnippet"
201
215
  @copy-code-snippet="onCopyCodeSnippet"
@@ -14,7 +14,7 @@ import { MESSAGE_MODEL_ROLES, SELECTED_CONTEXT_ITEMS_DEFAULT_COLLAPSED } from '.
14
14
 
15
15
  import DocumentationSources from '../duo_chat_message_sources/duo_chat_message_sources.vue';
16
16
  // eslint-disable-next-line no-restricted-imports
17
- import { copyToClipboard, concatUntilEmpty } from '../utils';
17
+ import { copyToClipboard, concatUntilEmpty, messageRenderersValidator } from '../utils';
18
18
  import AgenticBinaryFeedback from '../../../agentic_chat/components/agentic_binary_feedback/agentic_binary_feedback.vue';
19
19
  import AgenticFeedbackPanel from '../../../agentic_chat/components/agentic_feedback_panel/agentic_feedback_panel.vue';
20
20
  import MessageActionBar from '../message_action_bar/message_action_bar.vue';
@@ -127,6 +127,18 @@ export default {
127
127
  required: false,
128
128
  default: false,
129
129
  },
130
+ /**
131
+ * Optional array of custom message renderers to pass to MessageMap.
132
+ * Each entry is an object with `component` (Vue component) and `matchMessage` (Function).
133
+ */
134
+ messageRenderers: {
135
+ type: Array,
136
+ required: false,
137
+ validator: messageRenderersValidator,
138
+ default() {
139
+ return [];
140
+ },
141
+ },
130
142
  },
131
143
  data() {
132
144
  return {
@@ -483,6 +495,7 @@ export default {
483
495
  <message-map
484
496
  v-else
485
497
  :message="message"
498
+ :message-renderers="messageRenderers"
486
499
  :with-feedback="withFeedback"
487
500
  :working-directory="workingDirectory"
488
501
  data-testid="workflow-message"
@@ -1,4 +1,5 @@
1
1
  // Direct imports for backward compatibility
2
+ import { MESSAGE_MODEL_ROLES, VALID_MESSAGE_TYPES } from '../../../constants';
2
3
  import AgentMessage from './message_agent.vue';
3
4
  import InputRequestedMessage from './message_input_requested.vue';
4
5
  import SystemMessage from './message_tool.vue';
@@ -6,14 +7,6 @@ import WorkflowEndMessage from './message_workflow_end.vue';
6
7
  import MessageMap from './message_map.vue';
7
8
 
8
9
  // Import constants and factory functions
9
- import {
10
- AGENT_MESSAGE_TYPE,
11
- USER_MESSAGE_TYPE,
12
- FLOW_END_TYPE,
13
- INPUT_REQUEST_TYPE,
14
- TOOL_MESSAGE_TYPE,
15
- VALID_MESSAGE_TYPES,
16
- } from './constants';
17
10
 
18
11
  export default {
19
12
  // Main wrapper component - recommended for new consumers
@@ -28,10 +21,6 @@ export default {
28
21
  WorkflowEndMessage,
29
22
 
30
23
  // Constants and utility functions
24
+ MESSAGE_MODEL_ROLES,
31
25
  VALID_MESSAGE_TYPES,
32
- AGENT_MESSAGE_TYPE,
33
- USER_MESSAGE_TYPE,
34
- FLOW_END_TYPE,
35
- INPUT_REQUEST_TYPE,
36
- TOOL_MESSAGE_TYPE,
37
26
  };
@@ -1,25 +1,41 @@
1
1
  <script>
2
+ import { MESSAGE_MODEL_ROLES, APPROVAL_TOOL_NAMES } from '../../../constants';
3
+ import { messageRenderersValidator } from '../../utils';
2
4
  import AgentMessage from './message_agent.vue';
3
5
  import UserMessage from './message_user.vue';
4
6
  import InputRequestedMessage from './message_input_requested.vue';
5
- import SystemMessage from './message_tool.vue';
7
+ import MessageToolVisualization from './message_tool.vue';
8
+ import MessageToolApproved from './message_tool_approved.vue';
6
9
  import WorkflowEndMessage from './message_workflow_end.vue';
7
10
 
8
- import {
9
- AGENT_MESSAGE_TYPE,
10
- FLOW_END_TYPE,
11
- INPUT_REQUEST_TYPE,
12
- TOOL_MESSAGE_TYPE,
13
- USER_MESSAGE_TYPE,
14
- } from './constants';
15
-
16
- const DEFAULT_COMPONENT_MAP = {
17
- [AGENT_MESSAGE_TYPE]: AgentMessage,
18
- [FLOW_END_TYPE]: WorkflowEndMessage,
19
- [INPUT_REQUEST_TYPE]: InputRequestedMessage,
20
- [TOOL_MESSAGE_TYPE]: SystemMessage,
21
- [USER_MESSAGE_TYPE]: UserMessage,
22
- };
11
+ const DEFAULT_COMPONENT_MAP = [
12
+ {
13
+ component: AgentMessage,
14
+ matchMessage: (message) => message.message_type === MESSAGE_MODEL_ROLES.agent,
15
+ },
16
+ {
17
+ component: WorkflowEndMessage,
18
+ matchMessage: (message) => message.message_type === MESSAGE_MODEL_ROLES.workflow_end,
19
+ },
20
+ {
21
+ component: InputRequestedMessage,
22
+ matchMessage: (message) => message.message_type === MESSAGE_MODEL_ROLES.request,
23
+ },
24
+ {
25
+ component: MessageToolApproved,
26
+ matchMessage: (message) =>
27
+ message.message_type === MESSAGE_MODEL_ROLES.tool &&
28
+ Object.values(APPROVAL_TOOL_NAMES).includes(message.tool_info?.name),
29
+ },
30
+ {
31
+ component: MessageToolVisualization,
32
+ matchMessage: (message) => message.message_type === MESSAGE_MODEL_ROLES.tool,
33
+ },
34
+ {
35
+ component: UserMessage,
36
+ matchMessage: (message) => message.message_type === MESSAGE_MODEL_ROLES.user,
37
+ },
38
+ ];
23
39
 
24
40
  export default {
25
41
  name: 'MessageMap',
@@ -35,13 +51,16 @@ export default {
35
51
  },
36
52
  },
37
53
  /**
38
- * Optional custom component map to override or add default message components
54
+ * Optional array of custom message renderers to override or extend the default message components.
55
+ * Each entry is an object with `component` (Vue component) and `matchMessage` (Function).
56
+ * Custom entries are evaluated before defaults, allowing overrides.
39
57
  */
40
- componentMap: {
41
- type: Object,
58
+ messageRenderers: {
59
+ type: Array,
42
60
  required: false,
61
+ validator: messageRenderersValidator,
43
62
  default() {
44
- return {};
63
+ return [];
45
64
  },
46
65
  },
47
66
  /**
@@ -62,17 +81,19 @@ export default {
62
81
  },
63
82
  },
64
83
  computed: {
65
- messageMap() {
66
- return { ...DEFAULT_COMPONENT_MAP, ...this.componentMap };
67
- },
68
84
  messageComponent() {
69
- return this.isValidMessageType ? this.messageMap[this.message?.message_type] : AgentMessage;
70
- },
71
- validMessageTypes() {
72
- return [...Object.keys(DEFAULT_COMPONENT_MAP), ...Object.keys(this.componentMap)];
73
- },
74
- isValidMessageType() {
75
- return this.validMessageTypes.includes(this.message?.message_type);
85
+ const combined = [...this.messageRenderers, ...DEFAULT_COMPONENT_MAP];
86
+ try {
87
+ const match = combined.find(({ matchMessage }) => matchMessage(this.message));
88
+ return match?.component ?? AgentMessage;
89
+ } catch (e) {
90
+ // eslint-disable-next-line no-console
91
+ console.warn(
92
+ 'Invalid message renderer found. Falling back to the default agent message renderer',
93
+ e
94
+ );
95
+ return AgentMessage;
96
+ }
76
97
  },
77
98
  },
78
99
  };
@@ -1,8 +1,6 @@
1
1
  <script>
2
2
  import { GlButton, GlCollapse, GlIcon, GlLink } from '@gitlab/ui';
3
3
  import { translate } from '../../../../../utils/i18n';
4
- import MessageToolApproval from '../../duo_chat_message_tool_approval/message_tool_approval.vue';
5
- import { APPROVAL_TOOL_NAMES } from '../../../constants';
6
4
  import MessageToolDetails from './message_tool_details.vue';
7
5
  import BaseMessage from './message_base.vue';
8
6
 
@@ -21,7 +19,6 @@ export default {
21
19
  name: 'DuoToolMessage',
22
20
  components: {
23
21
  BaseMessage,
24
- MessageToolApproval,
25
22
  MessageToolDetails,
26
23
  GlButton,
27
24
  GlCollapse,
@@ -153,9 +150,6 @@ export default {
153
150
  collapseIconName() {
154
151
  return this.isDetailsOpen ? 'chevron-down' : 'chevron-right';
155
152
  },
156
- requiresApproval() {
157
- return Object.values(APPROVAL_TOOL_NAMES).includes(this.toolName);
158
- },
159
153
  shouldShowDetails() {
160
154
  return (
161
155
  !hiddenSubTypes.includes(this.message?.message_sub_type) &&
@@ -185,13 +179,7 @@ export default {
185
179
  };
186
180
  </script>
187
181
  <template>
188
- <message-tool-approval
189
- v-if="requiresApproval"
190
- :messages="[message]"
191
- :working-directory="workingDirectory"
192
- approval-status="approved"
193
- />
194
- <base-message v-else :message="message">
182
+ <base-message :message="message">
195
183
  <template #message="{ content }">
196
184
  <div class="gl-border gl-flex-col gl-rounded-lg gl-border-default gl-p-4">
197
185
  <div class="gl-flex gl-flex-col gl-gap-2 gl-text-base gl-text-subtle">
@@ -0,0 +1,32 @@
1
+ <script>
2
+ import MessageToolApproval from '../../duo_chat_message_tool_approval/message_tool_approval.vue';
3
+
4
+ export default {
5
+ name: 'MessageToolApproved',
6
+ components: { MessageToolApproval },
7
+ props: {
8
+ message: {
9
+ required: true,
10
+ type: Object,
11
+ },
12
+ workingDirectory: {
13
+ required: false,
14
+ type: String,
15
+ default: '',
16
+ },
17
+ },
18
+ computed: {
19
+ messages() {
20
+ return [this.message];
21
+ },
22
+ },
23
+ };
24
+ </script>
25
+ <template>
26
+ <message-tool-approval
27
+ :messages="messages"
28
+ :working-directory="workingDirectory"
29
+ approval-status="approved"
30
+ v-on="$listeners"
31
+ />
32
+ </template>
@@ -1,3 +1,12 @@
1
+ export const isValidMessageRenderer = (renderer) =>
2
+ renderer !== null &&
3
+ typeof renderer === 'object' &&
4
+ typeof renderer.component === 'object' &&
5
+ typeof renderer.matchMessage === 'function';
6
+
7
+ export const messageRenderersValidator = (renderers) =>
8
+ Array.isArray(renderers) && renderers.every((renderer) => isValidMessageRenderer(renderer));
9
+
1
10
  export const concatUntilEmpty = (arr) => {
2
11
  if (!arr) return '';
3
12
 
@@ -31,6 +31,16 @@ export const MESSAGE_MODEL_ROLES = {
31
31
  workflow_end: 'workflow_end',
32
32
  };
33
33
 
34
+ export const VALID_MESSAGE_TYPES = [
35
+ MESSAGE_MODEL_ROLES.agent,
36
+ MESSAGE_MODEL_ROLES.workflow_end,
37
+ MESSAGE_MODEL_ROLES.request,
38
+ MESSAGE_MODEL_ROLES.tool,
39
+ MESSAGE_MODEL_ROLES.user,
40
+ MESSAGE_MODEL_ROLES.system,
41
+ MESSAGE_MODEL_ROLES.assistant,
42
+ ];
43
+
34
44
  export const SELECTED_CONTEXT_ITEMS_DEFAULT_COLLAPSED = true;
35
45
 
36
46
  // TODO this should be a shared util in GitLab UI
package/src/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { MESSAGE_MODEL_ROLES } from './components/chat/constants';
2
+
1
3
  export { default as DuoUserFeedback } from './components/user_feedback/user_feedback.vue';
2
4
 
3
5
  // Duo Chat component
@@ -9,14 +11,14 @@ export { default as DuoChatMessageSources } from './components/chat/components/d
9
11
  export { default as DuoChatPredefinedPrompts } from './components/chat/components/duo_chat_predefined_prompts/duo_chat_predefined_prompts.vue';
10
12
 
11
13
  // Duo message types
12
- export {
13
- VALID_MESSAGE_TYPES,
14
- AGENT_MESSAGE_TYPE,
15
- USER_MESSAGE_TYPE,
16
- FLOW_END_TYPE,
17
- INPUT_REQUEST_TYPE,
18
- TOOL_MESSAGE_TYPE,
19
- } from './components/chat/components/duo_chat_message/message_types/constants';
14
+ export { MESSAGE_MODEL_ROLES, VALID_MESSAGE_TYPES } from './components/chat/constants';
15
+
16
+ // Legacy message type exports for backward compatibility
17
+ export const AGENT_MESSAGE_TYPE = MESSAGE_MODEL_ROLES.agent;
18
+ export const USER_MESSAGE_TYPE = MESSAGE_MODEL_ROLES.user;
19
+ export const FLOW_END_TYPE = MESSAGE_MODEL_ROLES.workflow_end;
20
+ export const INPUT_REQUEST_TYPE = MESSAGE_MODEL_ROLES.request;
21
+ export const TOOL_MESSAGE_TYPE = MESSAGE_MODEL_ROLES.tool;
20
22
 
21
23
  // Duo message type components
22
24
  export { default as MessageMap } from './components/chat/components/duo_chat_message/message_types/message_map.vue';
@@ -1,11 +0,0 @@
1
- // Message type constants
2
- const AGENT_MESSAGE_TYPE = 'agent';
3
- const FLOW_END_TYPE = 'workflow_end';
4
- const INPUT_REQUEST_TYPE = 'request';
5
- const TOOL_MESSAGE_TYPE = 'tool';
6
- const USER_MESSAGE_TYPE = 'user';
7
-
8
- // Array of all valid message types for validation
9
- const VALID_MESSAGE_TYPES = [AGENT_MESSAGE_TYPE, FLOW_END_TYPE, INPUT_REQUEST_TYPE, TOOL_MESSAGE_TYPE, USER_MESSAGE_TYPE];
10
-
11
- export { AGENT_MESSAGE_TYPE, FLOW_END_TYPE, INPUT_REQUEST_TYPE, TOOL_MESSAGE_TYPE, USER_MESSAGE_TYPE, VALID_MESSAGE_TYPES };
@@ -1,15 +0,0 @@
1
- // Message type constants
2
- export const AGENT_MESSAGE_TYPE = 'agent';
3
- export const FLOW_END_TYPE = 'workflow_end';
4
- export const INPUT_REQUEST_TYPE = 'request';
5
- export const TOOL_MESSAGE_TYPE = 'tool';
6
- export const USER_MESSAGE_TYPE = 'user';
7
-
8
- // Array of all valid message types for validation
9
- export const VALID_MESSAGE_TYPES = [
10
- AGENT_MESSAGE_TYPE,
11
- FLOW_END_TYPE,
12
- INPUT_REQUEST_TYPE,
13
- TOOL_MESSAGE_TYPE,
14
- USER_MESSAGE_TYPE,
15
- ];