@gitlab/duo-ui 13.10.1 → 13.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/components/chat/components/duo_chat_conversation/duo_chat_conversation.js +29 -9
- package/dist/components/chat/components/duo_chat_message/message_types/message_tool.js +2 -2
- package/dist/components/chat/components/duo_chat_message_tool_approval/message_tool_approval.js +33 -10
- package/package.json +1 -1
- package/src/components/chat/components/duo_chat_conversation/duo_chat_conversation.vue +30 -10
- package/src/components/chat/components/duo_chat_message/message_types/message_tool.vue +8 -8
- package/src/components/chat/components/duo_chat_message_tool_approval/message_tool_approval.vue +60 -26
- package/translations.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## [13.10.2](https://gitlab.com/gitlab-org/duo-ui/compare/v13.10.1...v13.10.2) (2025-11-10)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **DuoChat:** Accept both Object and Array for messages prop ([ee52b96](https://gitlab.com/gitlab-org/duo-ui/commit/ee52b96ffc2bf03488e3037165124993ef59ac8f))
|
|
7
|
+
* **DuoChat:** Display all tools in multi-tool approval requests ([49a4bfc](https://gitlab.com/gitlab-org/duo-ui/commit/49a4bfccecf83fe6cfb7b0757a3b00a38140201a))
|
|
8
|
+
|
|
1
9
|
## [13.10.1](https://gitlab.com/gitlab-org/duo-ui/compare/v13.10.0...v13.10.1) (2025-11-10)
|
|
2
10
|
|
|
3
11
|
|
|
@@ -80,17 +80,37 @@ var script = {
|
|
|
80
80
|
}
|
|
81
81
|
},
|
|
82
82
|
computed: {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Checks if the last message in the conversation is a tool approval request
|
|
85
|
+
* @returns {boolean} True if last message is awaiting tool approval
|
|
86
|
+
*/
|
|
87
|
+
isLastMessageToolApproval() {
|
|
88
|
+
const lastMsg = this.messages[this.messages.length - 1];
|
|
89
|
+
return (lastMsg === null || lastMsg === void 0 ? void 0 : lastMsg.message_type) === 'request' && Boolean(lastMsg === null || lastMsg === void 0 ? void 0 : lastMsg.tool_info);
|
|
90
|
+
},
|
|
91
|
+
/**
|
|
92
|
+
* Collects all consecutive tool approval requests at the end of the messages array.
|
|
93
|
+
* @returns {Array} Array of all pending tool approval requests
|
|
94
|
+
*/
|
|
95
|
+
pendingToolApprovals() {
|
|
96
|
+
// Early return if last message isn't a tool request
|
|
97
|
+
if (!this.isLastMessageToolApproval) {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Find the last non-tool message
|
|
102
|
+
const lastNonToolIndex = this.messages.findLastIndex(msg => msg.message_type !== 'request' || !msg.tool_info);
|
|
103
|
+
|
|
104
|
+
// Slice from after the last non-tool message to the end
|
|
105
|
+
return this.messages.slice(lastNonToolIndex + 1);
|
|
86
106
|
},
|
|
87
|
-
|
|
88
|
-
return this.
|
|
107
|
+
hasPendingToolApprovals() {
|
|
108
|
+
return this.pendingToolApprovals.length > 0;
|
|
89
109
|
},
|
|
90
110
|
toolApprovalOptions() {
|
|
91
|
-
var _this$
|
|
92
|
-
|
|
93
|
-
return (_this$
|
|
111
|
+
var _this$pendingToolAppr;
|
|
112
|
+
const lastIndex = this.pendingToolApprovals.length - 1;
|
|
113
|
+
return (_this$pendingToolAppr = this.pendingToolApprovals[lastIndex]) === null || _this$pendingToolAppr === void 0 ? void 0 : _this$pendingToolAppr.approvalOptions;
|
|
94
114
|
}
|
|
95
115
|
},
|
|
96
116
|
methods: {
|
|
@@ -132,7 +152,7 @@ const __vue_script__ = script;
|
|
|
132
152
|
/* template */
|
|
133
153
|
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{class:[
|
|
134
154
|
'gl-flex gl-flex-col gl-justify-end gl-gap-6',
|
|
135
|
-
{ '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},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.
|
|
155
|
+
{ '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},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)};
|
|
136
156
|
var __vue_staticRenderFns__ = [];
|
|
137
157
|
|
|
138
158
|
/* style */
|
|
@@ -177,9 +177,9 @@ var script = {
|
|
|
177
177
|
const __vue_script__ = script;
|
|
178
178
|
|
|
179
179
|
/* template */
|
|
180
|
-
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){
|
|
180
|
+
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){
|
|
181
181
|
var content = ref.content;
|
|
182
|
-
return [
|
|
182
|
+
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)]}}])})};
|
|
183
183
|
var __vue_staticRenderFns__ = [];
|
|
184
184
|
|
|
185
185
|
/* style */
|
package/dist/components/chat/components/duo_chat_message_tool_approval/message_tool_approval.js
CHANGED
|
@@ -50,7 +50,8 @@ const i18n = {
|
|
|
50
50
|
[TOOL_STATUS.Pending]: translate('MessageToolApproval.toolStatusPending', 'Pending'),
|
|
51
51
|
[TOOL_STATUS.Approved]: translate('MessageToolApproval.toolStatusApproved', 'Approved'),
|
|
52
52
|
TOGGLE_PARAMS_BUTTON_EXPAND: translate('MessageToolApproval.collapseButtonCollapsed', 'Display tool details'),
|
|
53
|
-
TOGGLE_PARAMS_BUTTON_COLLAPSE: translate('MessageToolApproval.collapseButtonExpanded', 'Hide tool details')
|
|
53
|
+
TOGGLE_PARAMS_BUTTON_COLLAPSE: translate('MessageToolApproval.collapseButtonExpanded', 'Hide tool details'),
|
|
54
|
+
MULTI_TOOL_TITLE: translate('MessageToolApproval.multiToolTitle', 'Duo wants to execute %{count} tools.')
|
|
54
55
|
};
|
|
55
56
|
const TOOL_PARAMS_VIEW_COMPONENTS = {
|
|
56
57
|
[APPROVAL_TOOL_NAMES.createCommit]: 'CreateCommitToolParams',
|
|
@@ -84,9 +85,9 @@ var script = {
|
|
|
84
85
|
PreBlock
|
|
85
86
|
},
|
|
86
87
|
props: {
|
|
87
|
-
|
|
88
|
+
messages: {
|
|
88
89
|
required: true,
|
|
89
|
-
type:
|
|
90
|
+
type: Array
|
|
90
91
|
},
|
|
91
92
|
workingDirectory: {
|
|
92
93
|
type: String,
|
|
@@ -134,17 +135,23 @@ var script = {
|
|
|
134
135
|
};
|
|
135
136
|
},
|
|
136
137
|
computed: {
|
|
138
|
+
multipleApprovalsNeeded() {
|
|
139
|
+
return this.messages.length > 1;
|
|
140
|
+
},
|
|
141
|
+
primaryMessage() {
|
|
142
|
+
return this.messages[0] || null;
|
|
143
|
+
},
|
|
137
144
|
toolName() {
|
|
138
|
-
var _this$
|
|
139
|
-
return ((_this$
|
|
145
|
+
var _this$primaryMessage, _this$primaryMessage$;
|
|
146
|
+
return ((_this$primaryMessage = this.primaryMessage) === null || _this$primaryMessage === void 0 ? void 0 : (_this$primaryMessage$ = _this$primaryMessage.tool_info) === null || _this$primaryMessage$ === void 0 ? void 0 : _this$primaryMessage$.name) || this.$options.i18n.TOOL_UNKNOWN;
|
|
140
147
|
},
|
|
141
148
|
toolParameters() {
|
|
142
|
-
var _this$
|
|
143
|
-
return ((_this$
|
|
149
|
+
var _this$primaryMessage2, _this$primaryMessage3;
|
|
150
|
+
return ((_this$primaryMessage2 = this.primaryMessage) === null || _this$primaryMessage2 === void 0 ? void 0 : (_this$primaryMessage3 = _this$primaryMessage2.tool_info) === null || _this$primaryMessage3 === void 0 ? void 0 : _this$primaryMessage3.args) || {};
|
|
144
151
|
},
|
|
145
152
|
toolResponse() {
|
|
146
|
-
var _this$
|
|
147
|
-
return (_this$
|
|
153
|
+
var _this$primaryMessage4, _this$primaryMessage5;
|
|
154
|
+
return (_this$primaryMessage4 = this.primaryMessage) === null || _this$primaryMessage4 === void 0 ? void 0 : (_this$primaryMessage5 = _this$primaryMessage4.tool_info) === null || _this$primaryMessage5 === void 0 ? void 0 : _this$primaryMessage5.tool_response;
|
|
148
155
|
},
|
|
149
156
|
camelCaseToolParameters() {
|
|
150
157
|
return convertKeysToCamelCase(this.toolParameters);
|
|
@@ -153,6 +160,12 @@ var script = {
|
|
|
153
160
|
return Object.keys(this.toolParameters).length > 0;
|
|
154
161
|
},
|
|
155
162
|
toolApprovalTitle() {
|
|
163
|
+
// Multiple tools: show generic count message
|
|
164
|
+
if (this.multipleApprovalsNeeded) {
|
|
165
|
+
return this.$options.i18n.MULTI_TOOL_TITLE.replace('%{count}', this.messages.length);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Single tool: show specific tool message
|
|
156
169
|
return i18n.TOOL_APPROVAL_TITLES[this.toolName] || this.toolName;
|
|
157
170
|
},
|
|
158
171
|
toolStatusLabel() {
|
|
@@ -262,6 +275,12 @@ var script = {
|
|
|
262
275
|
this.localProcessingState = PROCESSING_STATE.NONE;
|
|
263
276
|
this.showDenialReason = false;
|
|
264
277
|
this.denialReason = '';
|
|
278
|
+
},
|
|
279
|
+
convertKeysToCamelCase(obj) {
|
|
280
|
+
return convertKeysToCamelCase(obj);
|
|
281
|
+
},
|
|
282
|
+
getToolParamsComponent(toolName) {
|
|
283
|
+
return TOOL_PARAMS_VIEW_COMPONENTS[toolName];
|
|
265
284
|
}
|
|
266
285
|
},
|
|
267
286
|
i18n,
|
|
@@ -272,7 +291,11 @@ var script = {
|
|
|
272
291
|
const __vue_script__ = script;
|
|
273
292
|
|
|
274
293
|
/* template */
|
|
275
|
-
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('gl-card',{staticClass:"gl-w-full",class:{ 'message-tool-approval-collapsed': _vm.collapsed },attrs:{"header-class":{ 'message-tool-approval-collapsed-header': _vm.collapsed },"body-class":{ 'gl-hidden': _vm.collapsed }},scopedSlots:_vm._u([{key:"header",fn:function(){return [_c('div',{staticClass:"gl-flex gl-items-center gl-justify-between gl-gap-3"},[_c('span',[(_vm.collapsible)?_c('gl-button',_vm._b({attrs:{"variant":"default","category":"tertiary","size":"small","data-testid":"toggle-details-button"},on:{"click":function($event){_vm.collapsed = !_vm.collapsed;}}},'gl-button',_vm.collapseButtonProps,false)):_vm._e(),_vm._v(" "),_c('span',[_vm._v("\n "+_vm._s(_vm.toolApprovalTitle)+"\n ")])],1),_vm._v(" "),_c('gl-badge',{attrs:{"variant":_vm.toolStatusVariant}},[_vm._v("\n "+_vm._s(_vm.toolStatusLabel)+"\n ")])],1)]},proxy:true},(!_vm.isApproved)?{key:"footer",fn:function(){return [(!_vm.showDenialReason)?_c('div',{staticClass:"gl-flex gl-gap-2"},[(_vm.showSplitButton)?_c('gl-dropdown',{attrs:{"variant":"confirm","size":"small","split":"","text":_vm.primaryApprovalOption.text,"disabled":_vm.buttonsDisabled || _vm.primaryApprovalOption.disabled,"loading":_vm.isApproving,"data-testid":"approve-dropdown"},on:{"click":_vm.handlePrimaryApprove}},_vm._l((_vm.additionalApprovalOptions),function(option){return _c('gl-dropdown-item',{key:option.type,attrs:{"disabled":option.disabled,"data-testid":"approve-dropdown-item"},on:{"click":function($event){return _vm.handleDropdownSelection(option)}}},[_vm._v("\n "+_vm._s(option.text)+"\n ")])}),1):_c('gl-button',{attrs:{"variant":"confirm","size":"small","data-testid":"approve-tool-inline","disabled":_vm.buttonsDisabled || _vm.primaryApprovalOption.disabled,"loading":_vm.isApproving},on:{"click":_vm.handlePrimaryApprove}},[_vm._v("\n "+_vm._s(_vm.primaryApprovalOption.text)+"\n ")]),_vm._v(" "),_c('gl-button',{attrs:{"size":"small","data-testid":"deny-tool-inline","disabled":_vm.buttonsDisabled,"loading":_vm.isDenying},on:{"click":_vm.handleDeny}},[_vm._v("\n "+_vm._s(_vm.denyButtonText)+"\n ")])],1):_c('div',[_c('gl-form-group',{staticClass:"gl-mb-3",attrs:{"label":_vm.$options.i18n.DENIAL_REASON_LABEL,"label-for":"inline-rejection-reason","optional":true}},[_c('gl-form-textarea',{attrs:{"id":"inline-rejection-reason","placeholder":_vm.$options.i18n.DENIAL_REASON_PLACEHOLDER,"rows":2,"no-resize":true,"submit-on-enter":false,"disabled":_vm.buttonsDisabled,"data-testid":"denial-reason-textarea","autofocus":""},on:{"submit":_vm.submitDenial},model:{value:(_vm.denialReason),callback:function ($$v) {_vm.denialReason=$$v;},expression:"denialReason"}})],1),_vm._v(" "),_c('div',{staticClass:"gl-flex gl-gap-3"},[_c('gl-button',{attrs:{"size":"small","data-testid":"submit-denial","variant":"confirm","disabled":_vm.buttonsDisabled,"loading":_vm.isDenying},on:{"click":_vm.submitDenial}},[_vm._v("\n "+_vm._s(_vm.denyButtonText)+"\n ")]),_vm._v(" "),_c('gl-button',{attrs:{"size":"small","data-testid":"cancel-denial","disabled":_vm.buttonsDisabled},on:{"click":_vm.cancelDenial}},[_vm._v("\n "+_vm._s(_vm.$options.i18n.CANCEL_TEXT)+"\n ")])],1)],1)]},proxy:true}:null],null,true)},[_vm._v(" "),(_vm.
|
|
294
|
+
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('gl-card',{staticClass:"gl-w-full",class:{ 'message-tool-approval-collapsed': _vm.collapsed },attrs:{"header-class":{ 'message-tool-approval-collapsed-header': _vm.collapsed },"body-class":{ 'gl-hidden': _vm.collapsed }},scopedSlots:_vm._u([{key:"header",fn:function(){return [_c('div',{staticClass:"gl-flex gl-items-center gl-justify-between gl-gap-3"},[_c('span',[(_vm.collapsible)?_c('gl-button',_vm._b({attrs:{"variant":"default","category":"tertiary","size":"small","data-testid":"toggle-details-button"},on:{"click":function($event){_vm.collapsed = !_vm.collapsed;}}},'gl-button',_vm.collapseButtonProps,false)):_vm._e(),_vm._v(" "),_c('span',[_vm._v("\n "+_vm._s(_vm.toolApprovalTitle)+"\n ")])],1),_vm._v(" "),_c('gl-badge',{attrs:{"variant":_vm.toolStatusVariant}},[_vm._v("\n "+_vm._s(_vm.toolStatusLabel)+"\n ")])],1)]},proxy:true},(!_vm.isApproved)?{key:"footer",fn:function(){return [(!_vm.showDenialReason)?_c('div',{staticClass:"gl-flex gl-gap-2"},[(_vm.showSplitButton)?_c('gl-dropdown',{attrs:{"variant":"confirm","size":"small","split":"","text":_vm.primaryApprovalOption.text,"disabled":_vm.buttonsDisabled || _vm.primaryApprovalOption.disabled,"loading":_vm.isApproving,"data-testid":"approve-dropdown"},on:{"click":_vm.handlePrimaryApprove}},_vm._l((_vm.additionalApprovalOptions),function(option){return _c('gl-dropdown-item',{key:option.type,attrs:{"disabled":option.disabled,"data-testid":"approve-dropdown-item"},on:{"click":function($event){return _vm.handleDropdownSelection(option)}}},[_vm._v("\n "+_vm._s(option.text)+"\n ")])}),1):_c('gl-button',{attrs:{"variant":"confirm","size":"small","data-testid":"approve-tool-inline","disabled":_vm.buttonsDisabled || _vm.primaryApprovalOption.disabled,"loading":_vm.isApproving},on:{"click":_vm.handlePrimaryApprove}},[_vm._v("\n "+_vm._s(_vm.primaryApprovalOption.text)+"\n ")]),_vm._v(" "),_c('gl-button',{attrs:{"size":"small","data-testid":"deny-tool-inline","disabled":_vm.buttonsDisabled,"loading":_vm.isDenying},on:{"click":_vm.handleDeny}},[_vm._v("\n "+_vm._s(_vm.denyButtonText)+"\n ")])],1):_c('div',[_c('gl-form-group',{staticClass:"gl-mb-3",attrs:{"label":_vm.$options.i18n.DENIAL_REASON_LABEL,"label-for":"inline-rejection-reason","optional":true}},[_c('gl-form-textarea',{attrs:{"id":"inline-rejection-reason","placeholder":_vm.$options.i18n.DENIAL_REASON_PLACEHOLDER,"rows":2,"no-resize":true,"submit-on-enter":false,"disabled":_vm.buttonsDisabled,"data-testid":"denial-reason-textarea","autofocus":""},on:{"submit":_vm.submitDenial},model:{value:(_vm.denialReason),callback:function ($$v) {_vm.denialReason=$$v;},expression:"denialReason"}})],1),_vm._v(" "),_c('div',{staticClass:"gl-flex gl-gap-3"},[_c('gl-button',{attrs:{"size":"small","data-testid":"submit-denial","variant":"confirm","disabled":_vm.buttonsDisabled,"loading":_vm.isDenying},on:{"click":_vm.submitDenial}},[_vm._v("\n "+_vm._s(_vm.denyButtonText)+"\n ")]),_vm._v(" "),_c('gl-button',{attrs:{"size":"small","data-testid":"cancel-denial","disabled":_vm.buttonsDisabled},on:{"click":_vm.cancelDenial}},[_vm._v("\n "+_vm._s(_vm.$options.i18n.CANCEL_TEXT)+"\n ")])],1)],1)]},proxy:true}:null],null,true)},[_vm._v(" "),_vm._l((_vm.messages),function(toolMsg,index){return _c('div',{key:toolMsg.id || index},[(_vm.getToolParamsComponent(toolMsg.tool_info && toolMsg.tool_info.name))?_c(_vm.getToolParamsComponent(toolMsg.tool_info && toolMsg.tool_info.name),_vm._b({tag:"component",class:['gl-leading-20', { 'gl-border-t gl-mt-3 gl-border-gray-100 gl-pt-3': index > 0 }],attrs:{"tool-name":toolMsg.tool_info && toolMsg.tool_info.name,"tool-params":_vm.convertKeysToCamelCase((toolMsg.tool_info && toolMsg.tool_info.args) || {}),"tool-response":toolMsg.tool_info && toolMsg.tool_info.tool_response,"working-directory":_vm.workingDirectory}},'component',_vm.convertKeysToCamelCase((toolMsg.tool_info && toolMsg.tool_info.args) || {}),false)):_c('figure',{staticClass:"gl-m-0 gl-flex gl-flex-col gl-gap-2",class:{ 'gl-border-t gl-mt-3 gl-border-gray-100 gl-pt-3': index > 0 }},[_c('figcaption',{staticClass:"gl-text-subtle"},[_vm._v("\n "+_vm._s(_vm.$options.i18n.REQUEST_TEXT)+"\n ")]),_vm._v(" "),(
|
|
295
|
+
toolMsg.tool_info &&
|
|
296
|
+
toolMsg.tool_info.args &&
|
|
297
|
+
Object.keys(toolMsg.tool_info.args).length > 0
|
|
298
|
+
)?_c('pre-block',{attrs:{"data-testid":"tool-parameters"}},[_vm._v(_vm._s(JSON.stringify(toolMsg.tool_info.args, null, 2)))]):_c('span',{staticClass:"gl-text-sm gl-text-gray-500",attrs:{"data-testid":"no-parameters-message"}},[_vm._v("\n "+_vm._s(_vm.$options.i18n.NO_PARAMETERS_TEXT)+"\n ")])],1)],1)})],2)};
|
|
276
299
|
var __vue_staticRenderFns__ = [];
|
|
277
300
|
|
|
278
301
|
/* style */
|
package/package.json
CHANGED
|
@@ -82,18 +82,38 @@ export default {
|
|
|
82
82
|
},
|
|
83
83
|
},
|
|
84
84
|
computed: {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Checks if the last message in the conversation is a tool approval request
|
|
87
|
+
* @returns {boolean} True if last message is awaiting tool approval
|
|
88
|
+
*/
|
|
89
|
+
isLastMessageToolApproval() {
|
|
90
|
+
const lastMsg = this.messages[this.messages.length - 1];
|
|
91
|
+
return lastMsg?.message_type === 'request' && Boolean(lastMsg?.tool_info);
|
|
92
|
+
},
|
|
93
|
+
/**
|
|
94
|
+
* Collects all consecutive tool approval requests at the end of the messages array.
|
|
95
|
+
* @returns {Array} Array of all pending tool approval requests
|
|
96
|
+
*/
|
|
97
|
+
pendingToolApprovals() {
|
|
98
|
+
// Early return if last message isn't a tool request
|
|
99
|
+
if (!this.isLastMessageToolApproval) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Find the last non-tool message
|
|
104
|
+
const lastNonToolIndex = this.messages.findLastIndex(
|
|
105
|
+
(msg) => msg.message_type !== 'request' || !msg.tool_info
|
|
89
106
|
);
|
|
107
|
+
|
|
108
|
+
// Slice from after the last non-tool message to the end
|
|
109
|
+
return this.messages.slice(lastNonToolIndex + 1);
|
|
90
110
|
},
|
|
91
|
-
|
|
92
|
-
return this.
|
|
111
|
+
hasPendingToolApprovals() {
|
|
112
|
+
return this.pendingToolApprovals.length > 0;
|
|
93
113
|
},
|
|
94
114
|
toolApprovalOptions() {
|
|
95
|
-
|
|
96
|
-
return this.
|
|
115
|
+
const lastIndex = this.pendingToolApprovals.length - 1;
|
|
116
|
+
return this.pendingToolApprovals[lastIndex]?.approvalOptions;
|
|
97
117
|
},
|
|
98
118
|
},
|
|
99
119
|
methods: {
|
|
@@ -161,8 +181,8 @@ export default {
|
|
|
161
181
|
@open-file-path="onOpenFilePath"
|
|
162
182
|
/>
|
|
163
183
|
<duo-chat-message-tool-approval
|
|
164
|
-
v-if="
|
|
165
|
-
:
|
|
184
|
+
v-if="hasPendingToolApprovals"
|
|
185
|
+
:messages="pendingToolApprovals"
|
|
166
186
|
:is-processing="isToolApprovalProcessing"
|
|
167
187
|
:approval-options="toolApprovalOptions"
|
|
168
188
|
@approve-tool="onApproveToolCall"
|
|
@@ -171,15 +171,15 @@ export default {
|
|
|
171
171
|
};
|
|
172
172
|
</script>
|
|
173
173
|
<template>
|
|
174
|
-
<
|
|
174
|
+
<message-tool-approval
|
|
175
|
+
v-if="requiresApproval"
|
|
176
|
+
:messages="[message]"
|
|
177
|
+
:working-directory="workingDirectory"
|
|
178
|
+
approval-status="approved"
|
|
179
|
+
/>
|
|
180
|
+
<base-message v-else :message="message">
|
|
175
181
|
<template #message="{ content }">
|
|
176
|
-
<
|
|
177
|
-
v-if="requiresApproval"
|
|
178
|
-
:message="message"
|
|
179
|
-
:working-directory="workingDirectory"
|
|
180
|
-
approval-status="approved"
|
|
181
|
-
/>
|
|
182
|
-
<div v-else class="gl-border gl-flex-col gl-rounded-lg gl-border-default gl-p-4">
|
|
182
|
+
<div class="gl-border gl-flex-col gl-rounded-lg gl-border-default gl-p-4">
|
|
183
183
|
<div class="gl-flex gl-flex-col gl-gap-2 gl-text-base gl-text-subtle">
|
|
184
184
|
<div class="gl-flex gl-items-start gl-gap-x-3">
|
|
185
185
|
<div class="gl-mt-1 gl-flex gl-flex-shrink-0">
|
package/src/components/chat/components/duo_chat_message_tool_approval/message_tool_approval.vue
CHANGED
|
@@ -115,6 +115,10 @@ export const i18n = {
|
|
|
115
115
|
'MessageToolApproval.collapseButtonExpanded',
|
|
116
116
|
'Hide tool details'
|
|
117
117
|
),
|
|
118
|
+
MULTI_TOOL_TITLE: translate(
|
|
119
|
+
'MessageToolApproval.multiToolTitle',
|
|
120
|
+
'Duo wants to execute %{count} tools.'
|
|
121
|
+
),
|
|
118
122
|
};
|
|
119
123
|
|
|
120
124
|
const TOOL_PARAMS_VIEW_COMPONENTS = {
|
|
@@ -150,9 +154,9 @@ export default {
|
|
|
150
154
|
PreBlock,
|
|
151
155
|
},
|
|
152
156
|
props: {
|
|
153
|
-
|
|
157
|
+
messages: {
|
|
154
158
|
required: true,
|
|
155
|
-
type:
|
|
159
|
+
type: Array,
|
|
156
160
|
},
|
|
157
161
|
workingDirectory: {
|
|
158
162
|
type: String,
|
|
@@ -204,14 +208,20 @@ export default {
|
|
|
204
208
|
};
|
|
205
209
|
},
|
|
206
210
|
computed: {
|
|
211
|
+
multipleApprovalsNeeded() {
|
|
212
|
+
return this.messages.length > 1;
|
|
213
|
+
},
|
|
214
|
+
primaryMessage() {
|
|
215
|
+
return this.messages[0] || null;
|
|
216
|
+
},
|
|
207
217
|
toolName() {
|
|
208
|
-
return this.
|
|
218
|
+
return this.primaryMessage?.tool_info?.name || this.$options.i18n.TOOL_UNKNOWN;
|
|
209
219
|
},
|
|
210
220
|
toolParameters() {
|
|
211
|
-
return this.
|
|
221
|
+
return this.primaryMessage?.tool_info?.args || {};
|
|
212
222
|
},
|
|
213
223
|
toolResponse() {
|
|
214
|
-
return this.
|
|
224
|
+
return this.primaryMessage?.tool_info?.tool_response;
|
|
215
225
|
},
|
|
216
226
|
camelCaseToolParameters() {
|
|
217
227
|
return convertKeysToCamelCase(this.toolParameters);
|
|
@@ -220,6 +230,12 @@ export default {
|
|
|
220
230
|
return Object.keys(this.toolParameters).length > 0;
|
|
221
231
|
},
|
|
222
232
|
toolApprovalTitle() {
|
|
233
|
+
// Multiple tools: show generic count message
|
|
234
|
+
if (this.multipleApprovalsNeeded) {
|
|
235
|
+
return this.$options.i18n.MULTI_TOOL_TITLE.replace('%{count}', this.messages.length);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Single tool: show specific tool message
|
|
223
239
|
return i18n.TOOL_APPROVAL_TITLES[this.toolName] || this.toolName;
|
|
224
240
|
},
|
|
225
241
|
toolStatusLabel() {
|
|
@@ -327,6 +343,12 @@ export default {
|
|
|
327
343
|
this.showDenialReason = false;
|
|
328
344
|
this.denialReason = '';
|
|
329
345
|
},
|
|
346
|
+
convertKeysToCamelCase(obj) {
|
|
347
|
+
return convertKeysToCamelCase(obj);
|
|
348
|
+
},
|
|
349
|
+
getToolParamsComponent(toolName) {
|
|
350
|
+
return TOOL_PARAMS_VIEW_COMPONENTS[toolName];
|
|
351
|
+
},
|
|
330
352
|
},
|
|
331
353
|
i18n,
|
|
332
354
|
APPROVAL_TOOL_NAMES,
|
|
@@ -361,27 +383,39 @@ export default {
|
|
|
361
383
|
</gl-badge>
|
|
362
384
|
</div>
|
|
363
385
|
</template>
|
|
364
|
-
<
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
<
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
386
|
+
<div v-for="(toolMsg, index) in messages" :key="toolMsg.id || index">
|
|
387
|
+
<component
|
|
388
|
+
:is="getToolParamsComponent(toolMsg.tool_info && toolMsg.tool_info.name)"
|
|
389
|
+
v-if="getToolParamsComponent(toolMsg.tool_info && toolMsg.tool_info.name)"
|
|
390
|
+
:class="['gl-leading-20', { 'gl-border-t gl-mt-3 gl-border-gray-100 gl-pt-3': index > 0 }]"
|
|
391
|
+
:tool-name="toolMsg.tool_info && toolMsg.tool_info.name"
|
|
392
|
+
:tool-params="convertKeysToCamelCase((toolMsg.tool_info && toolMsg.tool_info.args) || {})"
|
|
393
|
+
v-bind="convertKeysToCamelCase((toolMsg.tool_info && toolMsg.tool_info.args) || {})"
|
|
394
|
+
:tool-response="toolMsg.tool_info && toolMsg.tool_info.tool_response"
|
|
395
|
+
:working-directory="workingDirectory"
|
|
396
|
+
/>
|
|
397
|
+
<figure
|
|
398
|
+
v-else
|
|
399
|
+
class="gl-m-0 gl-flex gl-flex-col gl-gap-2"
|
|
400
|
+
:class="{ 'gl-border-t gl-mt-3 gl-border-gray-100 gl-pt-3': index > 0 }"
|
|
401
|
+
>
|
|
402
|
+
<figcaption class="gl-text-subtle">
|
|
403
|
+
{{ $options.i18n.REQUEST_TEXT }}
|
|
404
|
+
</figcaption>
|
|
405
|
+
<pre-block
|
|
406
|
+
v-if="
|
|
407
|
+
toolMsg.tool_info &&
|
|
408
|
+
toolMsg.tool_info.args &&
|
|
409
|
+
Object.keys(toolMsg.tool_info.args).length > 0
|
|
410
|
+
"
|
|
411
|
+
data-testid="tool-parameters"
|
|
412
|
+
>{{ JSON.stringify(toolMsg.tool_info.args, null, 2) }}</pre-block
|
|
413
|
+
>
|
|
414
|
+
<span v-else class="gl-text-sm gl-text-gray-500" data-testid="no-parameters-message">
|
|
415
|
+
{{ $options.i18n.NO_PARAMETERS_TEXT }}
|
|
416
|
+
</span>
|
|
417
|
+
</figure>
|
|
418
|
+
</div>
|
|
385
419
|
<template v-if="!isApproved" #footer>
|
|
386
420
|
<div v-if="!showDenialReason" class="gl-flex gl-gap-2">
|
|
387
421
|
<!-- Split button when multiple approval options available -->
|
package/translations.js
CHANGED
|
@@ -163,6 +163,7 @@ export default {
|
|
|
163
163
|
"Tell Duo why you're rejecting this tool execution...",
|
|
164
164
|
'MessageToolApproval.denyText': 'Deny',
|
|
165
165
|
'MessageToolApproval.denyingText': 'Denying...',
|
|
166
|
+
'MessageToolApproval.multiToolTitle': 'Duo wants to execute %{count} tools.',
|
|
166
167
|
'MessageToolApproval.noParametersText': 'No parameters will be sent with this request.',
|
|
167
168
|
'MessageToolApproval.parametersText': 'Request parameters',
|
|
168
169
|
'MessageToolApproval.runCommand': 'Duo wants to run a command.',
|