@gitlab/duo-ui 11.1.2 → 11.2.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.
Files changed (31) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/components/agentic_chat/web_agentic_duo_chat.js +14 -4
  3. package/dist/components/chat/components/duo_chat_message/message_types/message_tool.js +8 -4
  4. package/dist/components/chat/components/duo_chat_message_tool_approval/components/base_tool_params.js +79 -0
  5. package/dist/components/chat/components/duo_chat_message_tool_approval/components/issuable_tool_params.js +116 -0
  6. package/dist/components/chat/components/duo_chat_message_tool_approval/components/run_command_tool_params.js +45 -5
  7. package/dist/components/chat/components/duo_chat_message_tool_approval/message_tool_approval.js +67 -20
  8. package/dist/components/chat/constants.js +12 -1
  9. package/dist/components/chat/mock_data.js +77 -1
  10. package/dist/components/chat/web_duo_chat.js +21 -7
  11. package/dist/components.css +1 -1
  12. package/dist/components.css.map +1 -1
  13. package/dist/tailwind.css +1 -1
  14. package/dist/tailwind.css.map +1 -1
  15. package/package.json +2 -2
  16. package/src/components/agentic_chat/web_agentic_duo_chat.vue +25 -9
  17. package/src/components/chat/components/duo_chat_message/message_types/message_tool.vue +23 -11
  18. package/src/components/chat/components/duo_chat_message_tool_approval/components/base_tool_params.vue +55 -0
  19. package/src/components/chat/components/duo_chat_message_tool_approval/components/issuable_tool_params.vue +112 -0
  20. package/src/components/chat/components/duo_chat_message_tool_approval/components/run_command_tool_params.vue +51 -6
  21. package/src/components/chat/components/duo_chat_message_tool_approval/message_tool_approval.vue +97 -22
  22. package/src/components/chat/constants.js +12 -0
  23. package/src/components/chat/mock_data.js +80 -0
  24. package/src/components/chat/web_duo_chat.scss +10 -0
  25. package/src/components/chat/web_duo_chat.vue +46 -12
  26. package/src/scss/components.scss +1 -0
  27. package/translations.js +26 -7
  28. package/dist/components/chat/components/duo_chat_message_tool_approval/components/create_issue_tool_params.js +0 -88
  29. package/dist/components/chat/components/duo_chat_message_tool_approval/components/create_merge_request_tool_params.js +0 -83
  30. package/src/components/chat/components/duo_chat_message_tool_approval/components/create_issue_tool_params.vue +0 -80
  31. package/src/components/chat/components/duo_chat_message_tool_approval/components/create_merge_request_tool_params.vue +0 -74
@@ -450,6 +450,13 @@ export default {
450
450
  activeThreadTitleForView() {
451
451
  return (this.currentView === VIEW_TYPES.CHAT && this.activeThreadTitle) || '';
452
452
  },
453
+ hasFooterControls() {
454
+ return (
455
+ this.$scopedSlots?.['footer-controls'] &&
456
+ typeof this.$scopedSlots['footer-controls'] === 'function' &&
457
+ this.$scopedSlots['footer-controls']()
458
+ );
459
+ },
453
460
  },
454
461
  watch: {
455
462
  multiThreadedView(newView) {
@@ -866,7 +873,7 @@ export default {
866
873
  </div>
867
874
 
868
875
  <div
869
- class="duo-chat-input 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"
876
+ class="duo-chat-input web-only 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"
870
877
  >
871
878
  <div
872
879
  class="gl-flex gl-justify-between gl-border-0 gl-border-b-1 gl-border-solid gl-border-strong gl-px-4 gl-py-4"
@@ -874,7 +881,7 @@ export default {
874
881
  <div>{{ $options.i18n.CHAT_MODEL_PLACEHOLDER }}</div>
875
882
  <div><slot name="agentic-switch"></slot></div>
876
883
  </div>
877
- <div :data-value="prompt" class="gl-h-[40px] gl-grow">
884
+ <div :data-value="prompt" class="gl-h-[80px] gl-grow">
878
885
  <gl-card
879
886
  v-if="shouldShowSlashCommands"
880
887
  ref="commands"
@@ -929,25 +936,34 @@ export default {
929
936
  <template #remaining-character-count-text="{ count }">
930
937
  <span
931
938
  v-if="count <= promptLengthWarningCount"
932
- class="gl-absolute gl-bottom-[-25px] gl-right-px gl-pr-3"
939
+ class="gl-absolute gl-right-px gl-mt-3 gl-pr-3 gl-text-sm"
940
+ :class="{
941
+ 'gl-bottom-[-5rem]': hasFooterControls,
942
+ 'gl-bottom-[-1.6rem]': !hasFooterControls,
943
+ }"
933
944
  >
934
945
  {{ remainingCharacterCountMessage(count) }}
935
946
  </span>
936
947
  </template>
937
948
  <template #character-count-over-limit-text="{ count }">
938
- <span class="gl-absolute gl-bottom-[-25px] gl-right-px gl-pr-3">{{
939
- overLimitCharacterCountMessage(count)
940
- }}</span>
949
+ <span
950
+ class="gl-absolute gl-bottom-[-1.6rem] gl-right-0 gl-mt-3 gl-pr-3 gl-text-sm"
951
+ :class="{
952
+ 'gl-bottom-[-5rem]': hasFooterControls,
953
+ 'gl-bottom-[-1.6rem]': !hasFooterControls,
954
+ }"
955
+ >{{ overLimitCharacterCountMessage(count) }}</span
956
+ >
941
957
  </template>
942
958
  </gl-form-textarea>
943
959
  </div>
944
- <div class="gl-flex gl-justify-end gl-px-3 gl-pb-3">
960
+ <div class="gl-absolute gl-bottom-0 gl-right-0 gl-px-3 gl-pb-3">
945
961
  <gl-button
946
962
  v-if="canSubmit"
947
963
  icon="paper-airplane"
948
964
  category="primary"
949
965
  variant="confirm"
950
- class="gl-bottom-2 gl-right-2 gl-ml-auto !gl-rounded-full"
966
+ class="!gl-rounded-full"
951
967
  type="submit"
952
968
  :disabled="isPromptEmpty || !hasValidPrompt"
953
969
  data-testid="chat-prompt-submit-button"
@@ -958,7 +974,7 @@ export default {
958
974
  icon="stop"
959
975
  category="primary"
960
976
  variant="default"
961
- class="gl-bottom-2 gl-right-2 !gl-rounded-full"
977
+ class="!gl-rounded-full"
962
978
  data-testid="chat-prompt-cancel-button"
963
979
  :aria-label="$options.i18n.CHAT_CANCEL_LABEL"
964
980
  @click="cancelPrompt"
@@ -1,7 +1,8 @@
1
1
  <script>
2
2
  import { GlIcon, GlLink } from '@gitlab/ui';
3
3
  import { translate } from '../../../../../utils/i18n';
4
- import MessageToolCommandOutput from '../../duo_chat_message_tool_command_output/message_tool_command_output.vue';
4
+ import MessageToolApproval from '../../duo_chat_message_tool_approval/message_tool_approval.vue';
5
+ import { APPROVAL_TOOL_NAMES } from '../../../constants';
5
6
  import BaseMessage from './message_base.vue';
6
7
 
7
8
  export const i18n = {
@@ -19,7 +20,7 @@ export default {
19
20
  name: 'DuoToolMessage',
20
21
  components: {
21
22
  BaseMessage,
22
- MessageToolCommandOutput,
23
+ MessageToolApproval,
23
24
  GlIcon,
24
25
  GlLink,
25
26
  },
@@ -99,6 +100,18 @@ export default {
99
100
  toolWorkingDirectory() {
100
101
  return this.workingDirectory;
101
102
  },
103
+ requiresApproval() {
104
+ return [
105
+ APPROVAL_TOOL_NAMES.runCommand,
106
+ APPROVAL_TOOL_NAMES.runGitCommand,
107
+ APPROVAL_TOOL_NAMES.createIssue,
108
+ APPROVAL_TOOL_NAMES.updateIssue,
109
+ APPROVAL_TOOL_NAMES.createMergeRequest,
110
+ APPROVAL_TOOL_NAMES.updateMergeRequest,
111
+ APPROVAL_TOOL_NAMES.createEpic,
112
+ APPROVAL_TOOL_NAMES.updateEpic,
113
+ ].includes(this.toolName);
114
+ },
102
115
  },
103
116
  methods: {
104
117
  onOpenFilePath(filePath) {
@@ -111,16 +124,15 @@ export default {
111
124
  };
112
125
  </script>
113
126
  <template>
114
- <base-message :message="message">
127
+ <message-tool-approval
128
+ v-if="requiresApproval"
129
+ :message="message"
130
+ :working-directory="workingDirectory"
131
+ approval-status="approved"
132
+ />
133
+ <base-message v-else :message="message">
115
134
  <template #message="{ content }">
116
- <message-tool-command-output
117
- v-if="hasCommandOutput"
118
- :command-header="$options.i18n.RAN_TERMINAL_COMMAND"
119
- :command-action="toolName"
120
- :command-output="toolResponseContent"
121
- :working-directory="toolWorkingDirectory"
122
- />
123
- <div v-else class="gl-flex gl-items-baseline !gl-text-subtle">
135
+ <div class="gl-flex gl-items-baseline !gl-text-subtle">
124
136
  <div class="gl-mr-3 gl-flex-shrink-0">
125
137
  <gl-icon :name="iconName" />
126
138
  </div>
@@ -0,0 +1,55 @@
1
+ <script>
2
+ import { GlAccordion, GlAccordionItem, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
3
+ import { translate } from '../../../../../utils/i18n';
4
+ import PreBlock from './pre_block.vue';
5
+
6
+ export default {
7
+ name: 'BaseToolParams',
8
+ components: {
9
+ GlAccordion,
10
+ GlAccordionItem,
11
+ PreBlock,
12
+ },
13
+ directives: {
14
+ SafeHtml,
15
+ },
16
+ props: {
17
+ message: {
18
+ type: String,
19
+ required: false,
20
+ default: '',
21
+ },
22
+ description: {
23
+ type: String,
24
+ required: false,
25
+ default: '',
26
+ },
27
+ customAccordionTitle: {
28
+ type: String,
29
+ required: false,
30
+ default: '',
31
+ },
32
+ },
33
+ computed: {
34
+ accordionTitle() {
35
+ return this.customAccordionTitle || this.$options.i18n.ACCORDION_TITLE;
36
+ },
37
+ },
38
+ i18n: {
39
+ ACCORDION_TITLE: translate('BaseToolParams.ACCORDION_TITLE', 'Read description'),
40
+ },
41
+ };
42
+ </script>
43
+ <template>
44
+ <div class="gl-flex gl-flex-col">
45
+ <div v-if="message" v-safe-html="message"></div>
46
+ <slot v-else></slot>
47
+ <gl-accordion v-if="description" class="-gl-ml-2 gl-mt-3" :header-level="3">
48
+ <slot name="params-description">
49
+ <gl-accordion-item :title="accordionTitle">
50
+ <pre-block>{{ description }}</pre-block>
51
+ </gl-accordion-item>
52
+ </slot>
53
+ </gl-accordion>
54
+ </div>
55
+ </template>
@@ -0,0 +1,112 @@
1
+ <script>
2
+ import { sprintf, translate } from '../../../../../utils/i18n';
3
+ import { APPROVAL_TOOL_NAMES } from '../../../constants';
4
+ import BaseToolParams from './base_tool_params.vue';
5
+
6
+ export default {
7
+ name: 'IssuableToolParams',
8
+ components: {
9
+ BaseToolParams,
10
+ },
11
+ props: {
12
+ toolName: {
13
+ type: String,
14
+ required: true,
15
+ validator: (toolName) =>
16
+ [
17
+ APPROVAL_TOOL_NAMES.createIssue,
18
+ APPROVAL_TOOL_NAMES.updateIssue,
19
+ APPROVAL_TOOL_NAMES.createEpic,
20
+ APPROVAL_TOOL_NAMES.updateEpic,
21
+ APPROVAL_TOOL_NAMES.createMergeRequest,
22
+ APPROVAL_TOOL_NAMES.updateMergeRequest,
23
+ ].includes(toolName),
24
+ },
25
+ toolParams: {
26
+ type: Object,
27
+ required: true,
28
+ },
29
+ },
30
+ computed: {
31
+ project() {
32
+ return this.toolParams?.projectPath || this.toolParams?.projectId;
33
+ },
34
+ title() {
35
+ return this.toolParams?.title;
36
+ },
37
+ labels() {
38
+ return this.toolParams?.labels;
39
+ },
40
+ description() {
41
+ return this.toolParams?.description;
42
+ },
43
+ sourceBranch() {
44
+ return this.toolParams?.sourceBranch;
45
+ },
46
+ targetBranch() {
47
+ return this.toolParams?.targetBranch;
48
+ },
49
+ message() {
50
+ let message = this.$options.i18n[this.toolName] || '';
51
+ const labelsMessage = this.$options.i18n.ASSIGN_LABELS_MESSAGE;
52
+ const branchMessage = this.$options.i18n.BRANCH_MESSAGE;
53
+
54
+ if (this.title) {
55
+ message = `${message} ${this.$options.i18n.TITLE_MESSAGE}`;
56
+ }
57
+
58
+ if (this.sourceBranch && this.targetBranch) {
59
+ message = `${message} ${branchMessage}`;
60
+ }
61
+
62
+ if (this.labels) {
63
+ message = `${message} ${labelsMessage}`;
64
+ }
65
+
66
+ return sprintf(message, { project: this.project, ...this.toolParams });
67
+ },
68
+ },
69
+ i18n: {
70
+ [APPROVAL_TOOL_NAMES.createIssue]: translate(
71
+ 'IssuableToolParams.CREATE_ISSUE',
72
+ 'Open an issue in project <code>%{project}</code>.'
73
+ ),
74
+ [APPROVAL_TOOL_NAMES.updateIssue]: translate(
75
+ 'IssuableToolParams.UPDATE_ISSUE',
76
+ 'Update issue <code>%{issueIid}</code> in project <code>%{project}</code>.'
77
+ ),
78
+ [APPROVAL_TOOL_NAMES.createEpic]: translate(
79
+ 'IssuableToolParams.CREATE_EPIC',
80
+ 'Create an epic in group <code>%{groupId}</code>.'
81
+ ),
82
+ [APPROVAL_TOOL_NAMES.updateEpic]: translate(
83
+ 'IssuableToolParams.UPDATE_EPIC',
84
+ 'Update epic <code>%{epicId}</code> in group <code>%{groupId}</code>.'
85
+ ),
86
+ [APPROVAL_TOOL_NAMES.createMergeRequest]: translate(
87
+ 'IssuableToolParams.CREATE_MERGE_REQUEST',
88
+ 'Open a merge request in project <code>%{project}</code>.'
89
+ ),
90
+ [APPROVAL_TOOL_NAMES.updateMergeRequest]: translate(
91
+ 'IssuableToolParams.UPDATE_MERGE_REQUEST',
92
+ 'Update merge request <code>%{mergeRequestIid}</code> in project <code>%{project}</code>.'
93
+ ),
94
+ TITLE_MESSAGE: translate(
95
+ 'IssuableToolParams.TITLE_MESSAGE',
96
+ 'Set the title "<em>%{title}</em>".'
97
+ ),
98
+ BRANCH_MESSAGE: translate(
99
+ 'IssuableToolParams.BRANCH_MESSAGE',
100
+ 'From branch <code>%{sourceBranch}</code> to branch <code>%{targetBranch}</code>.'
101
+ ),
102
+ ASSIGN_LABELS_MESSAGE: translate(
103
+ 'CreateIssueToolParams.ASSIGN_LABELS_MESSAGE',
104
+ 'Assign the labels <code>%{labels}</code>.'
105
+ ),
106
+ ACCORDION_TITLE: translate('CreateIssueToolParams.ACCORDION_TITLE', 'Read description'),
107
+ },
108
+ };
109
+ </script>
110
+ <template>
111
+ <base-tool-params :message="message" :description="description" />
112
+ </template>
@@ -1,30 +1,75 @@
1
1
  <script>
2
2
  import { GlIcon } from '@gitlab/ui';
3
+ import { translate } from '../../../../../utils/i18n';
4
+ import { APPROVAL_TOOL_NAMES } from '../../../constants';
5
+ import BaseToolParams from './base_tool_params.vue';
3
6
 
4
7
  export default {
5
8
  name: 'RunCommandToolParams',
6
9
  components: {
7
10
  GlIcon,
11
+ BaseToolParams,
8
12
  },
9
13
  props: {
10
- program: {
14
+ toolName: {
11
15
  type: String,
12
16
  required: true,
13
17
  },
18
+ program: {
19
+ type: String,
20
+ required: false,
21
+ default: '',
22
+ },
14
23
  args: {
15
24
  type: String,
16
- required: true,
25
+ required: false,
26
+ default: '',
27
+ },
28
+ command: {
29
+ type: String,
30
+ required: false,
31
+ default: '',
32
+ },
33
+ toolResponse: {
34
+ type: Object,
35
+ required: false,
36
+ default: null,
37
+ },
38
+ workingDirectory: {
39
+ type: String,
40
+ required: false,
41
+ default: '',
17
42
  },
18
43
  },
19
44
  computed: {
20
45
  formattedCommand() {
21
- return `${this.program} ${this.args}`;
46
+ if (this.toolName === APPROVAL_TOOL_NAMES.runCommand) {
47
+ return this.command?.length ? this.command : `${this.program} ${this.args}`;
48
+ }
49
+
50
+ return `git ${this.command} ${this.args}`;
22
51
  },
52
+ withWorkingDirectory() {
53
+ return this.workingDirectory?.length
54
+ ? `${this.workingDirectory}> ${this.formattedCommand}`
55
+ : this.formattedCommand;
56
+ },
57
+ commandOutput() {
58
+ return this.toolResponse?.content;
59
+ },
60
+ },
61
+ i18n: {
62
+ SEE_COMMAND_OUTPUT: translate('RunCommandToolParams.ACCORDION_TITLE', 'Expand command output'),
23
63
  },
24
64
  };
25
65
  </script>
26
66
  <template>
27
- <div class="gl-flex gl-items-center gl-gap-3">
28
- <gl-icon name="terminal" /> <code>{{ formattedCommand }}</code>
29
- </div>
67
+ <base-tool-params
68
+ :description="commandOutput"
69
+ :custom-accordion-title="$options.i18n.SEE_COMMAND_OUTPUT"
70
+ >
71
+ <div class="gl-flex gl-items-center gl-gap-3">
72
+ <gl-icon name="terminal" /> <code>{{ withWorkingDirectory }}</code>
73
+ </div>
74
+ </base-tool-params>
30
75
  </template>
@@ -10,25 +10,22 @@ import {
10
10
  } from '@gitlab/ui';
11
11
  import { translate } from '../../../../utils/i18n';
12
12
  import { convertKeysToCamelCase } from '../../../../utils/object';
13
- import { acceptedApproveToolPayloads } from '../../constants';
13
+ import { acceptedApproveToolPayloads, APPROVAL_TOOL_NAMES } from '../../constants';
14
14
  import CreateCommitToolParams from './components/create_commit_tool_params.vue';
15
- import CreateIssueToolParams from './components/create_issue_tool_params.vue';
16
- import CreateMergeRequestToolParams from './components/create_merge_request_tool_params.vue';
15
+ import IssuableToolParams from './components/issuable_tool_params.vue';
17
16
  import RunCommandToolParams from './components/run_command_tool_params.vue';
18
17
 
19
- export const APPROVAL_TOOL_NAMES = {
20
- createCommit: 'create_commit',
21
- createIssue: 'create_issue',
22
- createMergeRequest: 'create_merge_request',
23
- runCommand: 'run_command',
24
- };
25
-
26
18
  export const PROCESSING_STATE = {
27
19
  APPROVING: 'approving',
28
20
  DENYING: 'denying',
29
21
  NONE: null,
30
22
  };
31
23
 
24
+ export const TOOL_STATUS = {
25
+ Pending: 'pending',
26
+ Approved: 'approved',
27
+ };
28
+
32
29
  export const i18n = {
33
30
  TOOL_APPROVAL_DESCRIPTION: translate(
34
31
  'MessageToolApproval.toolApprovalDescription',
@@ -62,6 +59,18 @@ export const i18n = {
62
59
  'MessageToolApproval.createIssue',
63
60
  'Duo wants to open an issue.'
64
61
  ),
62
+ [APPROVAL_TOOL_NAMES.updateIssue]: translate(
63
+ 'MessageToolApproval.updateIssue',
64
+ 'Duo wants to update an issue.'
65
+ ),
66
+ [APPROVAL_TOOL_NAMES.createEpic]: translate(
67
+ 'MessageToolApproval.createEpic',
68
+ 'Duo wants to create an epic.'
69
+ ),
70
+ [APPROVAL_TOOL_NAMES.updateEpic]: translate(
71
+ 'MessageToolApproval.updateEpic',
72
+ 'Duo wants to update an epic.'
73
+ ),
65
74
  [APPROVAL_TOOL_NAMES.createMergeRequest]: translate(
66
75
  'MessageToolApproval.createMergeRequest',
67
76
  'Duo wants to create a merge request.'
@@ -70,15 +79,33 @@ export const i18n = {
70
79
  'MessageToolApproval.runCommand',
71
80
  'Duo wants to run a command.'
72
81
  ),
82
+ [APPROVAL_TOOL_NAMES.runGitCommand]: translate(
83
+ 'MessageToolApproval.runGitCommand',
84
+ 'Duo wants to run a git command.'
85
+ ),
73
86
  },
74
- TOOL_STATUS: translate('MessageToolApproval.toolStatus', 'Pending'),
87
+ [TOOL_STATUS.Pending]: translate('MessageToolApproval.toolStatusPending', 'Pending'),
88
+ [TOOL_STATUS.Approved]: translate('MessageToolApproval.toolStatusApproved', 'Approved'),
89
+ TOGGLE_PARAMS_BUTTON_EXPAND: translate(
90
+ 'MessageToolApproval.collapseButtonCollapsed',
91
+ 'Display tool details'
92
+ ),
93
+ TOGGLE_PARAMS_BUTTON_COLLAPSE: translate(
94
+ 'MessageToolApproval.collapseButtonExpanded',
95
+ 'Hide tool details'
96
+ ),
75
97
  };
76
98
 
77
99
  const TOOL_PARAMS_VIEW_COMPONENTS = {
78
100
  [APPROVAL_TOOL_NAMES.createCommit]: 'CreateCommitToolParams',
79
- [APPROVAL_TOOL_NAMES.createIssue]: 'CreateIssueToolParams',
80
- [APPROVAL_TOOL_NAMES.createMergeRequest]: 'CreateMergeRequestToolParams',
101
+ [APPROVAL_TOOL_NAMES.createIssue]: 'IssuableToolParams',
102
+ [APPROVAL_TOOL_NAMES.updateIssue]: 'IssuableToolParams',
103
+ [APPROVAL_TOOL_NAMES.createEpic]: 'IssuableToolParams',
104
+ [APPROVAL_TOOL_NAMES.updateEpic]: 'IssuableToolParams',
105
+ [APPROVAL_TOOL_NAMES.createMergeRequest]: 'IssuableToolParams',
106
+ [APPROVAL_TOOL_NAMES.updateMergeRequest]: 'IssuableToolParams',
81
107
  [APPROVAL_TOOL_NAMES.runCommand]: 'RunCommandToolParams',
108
+ [APPROVAL_TOOL_NAMES.runGitCommand]: 'RunCommandToolParams',
82
109
  };
83
110
 
84
111
  export default {
@@ -92,8 +119,7 @@ export default {
92
119
  GlDropdown,
93
120
  GlDropdownItem,
94
121
  CreateCommitToolParams,
95
- CreateIssueToolParams,
96
- CreateMergeRequestToolParams,
122
+ IssuableToolParams,
97
123
  RunCommandToolParams,
98
124
  },
99
125
  props: {
@@ -101,11 +127,21 @@ export default {
101
127
  required: true,
102
128
  type: Object,
103
129
  },
130
+ workingDirectory: {
131
+ type: String,
132
+ required: false,
133
+ default: '',
134
+ },
104
135
  isProcessing: {
105
136
  type: Boolean,
106
137
  required: false,
107
138
  default: false,
108
139
  },
140
+ approvalStatus: {
141
+ type: String,
142
+ required: false,
143
+ default: TOOL_STATUS.Pending,
144
+ },
109
145
  /**
110
146
  * Array of approval options for multi-approval functionality
111
147
  * @property {string} type - The approval type (approve-tool-once, approve-for-session, always-approve-tool)
@@ -137,6 +173,7 @@ export default {
137
173
  showDenialReason: false,
138
174
  denialReason: '',
139
175
  localProcessingState: PROCESSING_STATE.NONE,
176
+ collapsed: this.approvalStatus === TOOL_STATUS.Approved,
140
177
  };
141
178
  },
142
179
  computed: {
@@ -146,6 +183,9 @@ export default {
146
183
  toolParameters() {
147
184
  return this.message?.tool_info?.args || {};
148
185
  },
186
+ toolResponse() {
187
+ return this.message?.tool_info?.tool_response;
188
+ },
149
189
  camelCaseToolParameters() {
150
190
  return convertKeysToCamelCase(this.toolParameters);
151
191
  },
@@ -153,7 +193,27 @@ export default {
153
193
  return Object.keys(this.toolParameters).length > 0;
154
194
  },
155
195
  toolApprovalTitle() {
156
- return i18n.TOOL_APPROVAL_TITLES[this.toolName];
196
+ return i18n.TOOL_APPROVAL_TITLES[this.toolName] || this.toolName;
197
+ },
198
+ toolStatusLabel() {
199
+ return i18n[this.approvalStatus];
200
+ },
201
+ toolStatusVariant() {
202
+ return {
203
+ [TOOL_STATUS.Pending]: 'neutral',
204
+ [TOOL_STATUS.Approved]: 'info',
205
+ }[this.approvalStatus];
206
+ },
207
+ collapsible() {
208
+ return this.approvalStatus === TOOL_STATUS.Approved;
209
+ },
210
+ collapseButtonProps() {
211
+ return this.collapsed
212
+ ? { icon: 'chevron-right', title: i18n.TOGGLE_PARAMS_BUTTON_EXPAND }
213
+ : { icon: 'chevron-down', title: i18n.TOGGLE_PARAMS_BUTTON_COLLAPSE };
214
+ },
215
+ isApproved() {
216
+ return this.approvalStatus === TOOL_STATUS.Approved;
157
217
  },
158
218
  isApproving() {
159
219
  return this.isProcessing && this.localProcessingState === PROCESSING_STATE.APPROVING;
@@ -247,14 +307,25 @@ export default {
247
307
  </script>
248
308
 
249
309
  <template>
250
- <gl-card>
310
+ <gl-card class="gl-w-full" :body-class="{ 'gl-hidden': collapsed }">
251
311
  <template #header>
252
- <div class="gl-flex gl-items-center gl-justify-between">
312
+ <div class="gl-flex gl-items-center gl-justify-between gl-gap-3">
253
313
  <span>
254
- {{ toolApprovalTitle }}
314
+ <gl-button
315
+ v-if="collapsible"
316
+ variant="default"
317
+ category="tertiary"
318
+ size="small"
319
+ data-testid="toggle-details-button"
320
+ v-bind="collapseButtonProps"
321
+ @click="collapsed = !collapsed"
322
+ />
323
+ <span>
324
+ {{ toolApprovalTitle }}
325
+ </span>
255
326
  </span>
256
- <gl-badge>
257
- {{ $options.i18n.TOOL_STATUS }}
327
+ <gl-badge :variant="toolStatusVariant">
328
+ {{ toolStatusLabel }}
258
329
  </gl-badge>
259
330
  </div>
260
331
  </template>
@@ -262,6 +333,10 @@ export default {
262
333
  :is="toolParamsViewComponent"
263
334
  v-if="toolParamsViewComponent"
264
335
  class="gl-leading-20"
336
+ :tool-name="toolName"
337
+ :tool-params="camelCaseToolParameters"
338
+ :tool-response="toolResponse"
339
+ :working-directory="workingDirectory"
265
340
  v-bind="camelCaseToolParameters"
266
341
  />
267
342
  <div
@@ -283,7 +358,7 @@ export default {
283
358
  </span>
284
359
  </div>
285
360
  </div>
286
- <template #footer>
361
+ <template v-if="!isApproved" #footer>
287
362
  <div v-if="!showDenialReason" class="gl-flex gl-gap-2">
288
363
  <!-- Split button when multiple approval options available -->
289
364
  <gl-dropdown
@@ -49,3 +49,15 @@ export const acceptedApproveToolPayloads = {
49
49
  APPROVE_SESSION,
50
50
  APPROVE_ALL_TOOLS,
51
51
  };
52
+
53
+ export const APPROVAL_TOOL_NAMES = {
54
+ createCommit: 'create_commit',
55
+ createIssue: 'create_issue',
56
+ updateIssue: 'update_issue',
57
+ createEpic: 'create_epic',
58
+ updateEpic: 'update_epic',
59
+ createMergeRequest: 'create_merge_request',
60
+ updateMergeRequest: 'update_merge_request',
61
+ runCommand: 'run_command',
62
+ runGitCommand: 'run_git_command',
63
+ };