@gitlab/duo-ui 10.22.1 → 10.23.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 (25) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/components/agentic_chat/agentic_duo_chat.js +37 -18
  3. package/dist/components/agentic_chat/components/agentic_tool_approval_flow/agentic_tool_approval_flow.js +51 -3
  4. package/dist/components/agentic_chat/components/agentic_tool_approval_flow/agentic_tool_approval_modal/agentic_tool_approval_modal.js +64 -14
  5. package/dist/components/chat/components/duo_chat_header/duo_chat_header.js +4 -4
  6. package/dist/components/chat/duo_chat.js +54 -35
  7. package/dist/components.css +1 -1
  8. package/dist/components.css.map +1 -1
  9. package/dist/index.js +0 -2
  10. package/dist/tailwind.css +1 -1
  11. package/dist/tailwind.css.map +1 -1
  12. package/package.json +1 -2
  13. package/src/components/agentic_chat/agentic_duo_chat.vue +244 -210
  14. package/src/components/agentic_chat/components/agentic_tool_approval_flow/agentic_tool_approval_flow.vue +57 -2
  15. package/src/components/agentic_chat/components/agentic_tool_approval_flow/agentic_tool_approval_modal/agentic_tool_approval_modal.vue +105 -16
  16. package/src/components/chat/components/duo_chat_header/duo_chat_header.vue +22 -24
  17. package/src/components/chat/duo_chat.scss +2 -1
  18. package/src/components/chat/duo_chat.vue +238 -214
  19. package/src/index.js +0 -2
  20. package/translations.js +6 -5
  21. package/dist/components/ui/duo_layout/duo_layout.js +0 -100
  22. package/dist/components/ui/side_rail/side_rail.js +0 -67
  23. package/src/components/ui/duo_layout/duo_layout.md +0 -0
  24. package/src/components/ui/duo_layout/duo_layout.vue +0 -95
  25. package/src/components/ui/side_rail/side_rail.vue +0 -56
@@ -1,5 +1,5 @@
1
1
  <script>
2
- import { GlModal } from '@gitlab/ui';
2
+ import { GlModal, GlButton, GlDropdown, GlDropdownItem } from '@gitlab/ui';
3
3
  import { translate } from '../../../../../utils/i18n';
4
4
 
5
5
  export const i18n = {
@@ -11,6 +11,11 @@ export const i18n = {
11
11
  TOOL_LABEL: translate('AgenticToolApprovalModal.toolLabel', 'Tool:'),
12
12
  TOOL_UNKNOWN: translate('AgenticToolApprovalModal.toolUnknown', 'Unknown'),
13
13
  APPROVE_TEXT: translate('AgenticToolApprovalModal.approveText', 'Approve'),
14
+ APPROVE_ONCE_TEXT: translate('AgenticToolApprovalModal.approveOnceText', 'Approve Once'),
15
+ APPROVE_ALL_TEXT: translate(
16
+ 'AgenticToolApprovalModal.approveAllText',
17
+ 'Always approve this tool'
18
+ ),
14
19
  DENY_TEXT: translate('AgenticToolApprovalModal.denyText', 'Deny'),
15
20
  };
16
21
 
@@ -18,6 +23,9 @@ export default {
18
23
  name: 'AgenticToolApprovalModal',
19
24
  components: {
20
25
  GlModal,
26
+ GlButton,
27
+ GlDropdown,
28
+ GlDropdownItem,
21
29
  },
22
30
  props: {
23
31
  /**
@@ -48,18 +56,36 @@ export default {
48
56
  required: false,
49
57
  default: i18n.DEFAULT_DESCRIPTION,
50
58
  },
59
+ approvalOptions: {
60
+ type: Array,
61
+ required: false,
62
+ default: () => [
63
+ {
64
+ type: 'approve-tool-once',
65
+ text: 'Approve',
66
+ primary: true,
67
+ },
68
+ ],
69
+ },
70
+ },
71
+ data() {
72
+ return {
73
+ isApprovalAction: false,
74
+ };
51
75
  },
52
76
  computed: {
53
- approveAction() {
54
- return {
55
- text: this.$options.i18n.APPROVE_TEXT,
56
- attributes: {
57
- variant: 'confirm',
58
- icon: 'play',
59
- size: 'small',
60
- 'data-testid': 'approve-tool',
61
- },
62
- };
77
+ safeApprovalOptions() {
78
+ return this.approvalOptions || [];
79
+ },
80
+ primaryApprovalOption() {
81
+ const primary = this.safeApprovalOptions.find((option) => option.primary === true);
82
+ return primary || this.safeApprovalOptions[0] || {};
83
+ },
84
+ additionalApprovalOptions() {
85
+ return this.safeApprovalOptions.filter((option) => option.primary !== true);
86
+ },
87
+ showSplitButton() {
88
+ return this.safeApprovalOptions.length > 1;
63
89
  },
64
90
  denyAction() {
65
91
  return {
@@ -77,11 +103,28 @@ export default {
77
103
  },
78
104
  },
79
105
  methods: {
106
+ handlePrimaryApprove() {
107
+ /**
108
+ * Emitted when the user clicks the primary approve button
109
+ */
110
+ this.isApprovalAction = true;
111
+ this.$emit('approve', { type: this.primaryApprovalOption.type });
112
+ },
113
+ handleDropdownSelection(option) {
114
+ /**
115
+ * Emitted when the user selects an option from the dropdown
116
+ * @param {Object} option - The selected dropdown option
117
+ */
118
+ this.isApprovalAction = true;
119
+ this.$emit('approve', { type: option.type });
120
+ },
80
121
  handleApprove() {
81
122
  /**
123
+ * Legacy method for backwards compatibility - defaults to 'approve-tool-once' approval
82
124
  * Emitted when the user approves the tool execution
83
125
  */
84
- this.$emit('approve');
126
+ this.isApprovalAction = true;
127
+ this.$emit('approve', { type: 'approve-tool-once' });
85
128
  },
86
129
  handleDeny() {
87
130
  /**
@@ -90,6 +133,12 @@ export default {
90
133
  this.$emit('deny');
91
134
  },
92
135
  handleModalHide(event) {
136
+ // If the modal is being hidden by an approval action, don't emit deny-force
137
+ if (this.isApprovalAction) {
138
+ this.isApprovalAction = false;
139
+ return;
140
+ }
141
+
93
142
  // If the modal is being hidden by the X button or ESC key,
94
143
  // treat it as a deny-force action
95
144
  if (event.trigger !== 'ok' && event.trigger !== 'cancel') {
@@ -106,13 +155,11 @@ export default {
106
155
  :visible="visible"
107
156
  modal-id="agentic-tool-approval-modal"
108
157
  :title="$options.i18n.TITLE"
109
- :action-primary="approveAction"
110
- :action-cancel="denyAction"
158
+ :action-primary="null"
159
+ :action-cancel="null"
111
160
  :no-close-on-backdrop="true"
112
161
  :no-close-on-esc="true"
113
162
  data-testid="agentic-tool-approval-modal"
114
- @primary="handleApprove"
115
- @canceled="handleDeny"
116
163
  @hide="handleModalHide"
117
164
  >
118
165
  <div class="gl-mb-4">
@@ -134,5 +181,47 @@ export default {
134
181
  >
135
182
  </div>
136
183
  </div>
184
+
185
+ <template #modal-footer>
186
+ <div class="gl-flex gl-justify-end gl-gap-3">
187
+ <gl-button v-bind="denyAction.attributes" @click="handleDeny">
188
+ {{ denyAction.text }}
189
+ </gl-button>
190
+
191
+ <!-- Split button when multiple options available -->
192
+ <gl-dropdown
193
+ v-if="showSplitButton"
194
+ variant="confirm"
195
+ size="small"
196
+ split
197
+ :text="primaryApprovalOption.text"
198
+ :disabled="primaryApprovalOption.disabled"
199
+ right
200
+ data-testid="approve-dropdown"
201
+ @click="handlePrimaryApprove"
202
+ >
203
+ <gl-dropdown-item
204
+ v-for="option in additionalApprovalOptions"
205
+ :key="option.type"
206
+ :disabled="option.disabled"
207
+ @click="handleDropdownSelection(option)"
208
+ >
209
+ {{ option.text }}
210
+ </gl-dropdown-item>
211
+ </gl-dropdown>
212
+
213
+ <!-- Simple button when only one option -->
214
+ <gl-button
215
+ v-else
216
+ variant="confirm"
217
+ size="small"
218
+ :disabled="primaryApprovalOption.disabled"
219
+ data-testid="approve-button"
220
+ @click="handlePrimaryApprove"
221
+ >
222
+ {{ primaryApprovalOption.text }}
223
+ </gl-button>
224
+ </div>
225
+ </template>
137
226
  </gl-modal>
138
227
  </template>
@@ -3,10 +3,10 @@ import Vue from 'vue';
3
3
  import {
4
4
  GlAlert,
5
5
  GlBadge,
6
- GlAvatar,
7
6
  GlButton,
8
7
  GlDropdown,
9
8
  GlDropdownItem,
9
+ GlExperimentBadge,
10
10
  GlSafeHtmlDirective as SafeHtml,
11
11
  GlTooltipDirective,
12
12
  GlToast,
@@ -43,10 +43,10 @@ export default {
43
43
  components: {
44
44
  GlAlert,
45
45
  GlBadge,
46
- GlAvatar,
47
46
  GlButton,
48
47
  GlDropdown,
49
48
  GlDropdownItem,
49
+ GlExperimentBadge,
50
50
  GlDisclosureDropdown,
51
51
  },
52
52
  directives: {
@@ -140,31 +140,19 @@ export default {
140
140
 
141
141
  <template>
142
142
  <header data-testid="chat-header" class="gl-border-b gl-shrink-0 gl-bg-default gl-p-0">
143
- <div class="gl-border-b gl-flex gl-w-full gl-items-center gl-px-5 gl-py-3">
144
- <h4
145
- v-if="subtitle"
146
- class="gl-mb-0 gl-shrink-0 gl-overflow-hidden gl-text-ellipsis gl-whitespace-nowrap gl-pr-3 gl-text-sm gl-font-normal gl-text-subtle"
147
- data-testid="chat-subtitle"
148
- >
149
- {{ subtitle }}
150
- </h4>
151
- <gl-button
152
- category="tertiary"
153
- variant="default"
154
- icon="close"
155
- size="small"
156
- class="gl-ml-auto"
157
- data-testid="chat-close-button"
158
- :aria-label="$options.i18n.CHAT_CLOSE_LABEL"
159
- @click="$emit('close')"
160
- />
161
- </div>
162
143
  <div class="drawer-title gl-flex gl-items-center gl-justify-start gl-p-5">
163
- <div class="gl-flex gl-flex-1 gl-overflow-hidden">
164
- <gl-avatar :size="32" :entity-name="title" shape="circle" class="gl-mr-3" />
144
+ <div class="gl-flex-1 gl-overflow-hidden">
165
145
  <div class="gl-flex gl-items-center">
166
- <h3 class="gl-my-0 gl-text-[0.875rem]">{{ title }}</h3>
146
+ <h3 class="gl-my-0 gl-text-size-h2">{{ title }}</h3>
147
+ <gl-experiment-badge v-if="badgeType" :type="badgeType" container-id="chat-component" />
167
148
  </div>
149
+ <h4
150
+ v-if="subtitle"
151
+ class="gl-mb-0 gl-overflow-hidden gl-text-ellipsis gl-whitespace-nowrap gl-pr-3 gl-text-sm gl-font-normal gl-text-subtle"
152
+ data-testid="chat-subtitle"
153
+ >
154
+ {{ subtitle }}
155
+ </h4>
168
156
  </div>
169
157
 
170
158
  <div class="gl-flex gl-gap-3">
@@ -254,6 +242,16 @@ export default {
254
242
  </span>
255
243
  </gl-dropdown-item>
256
244
  </gl-dropdown>
245
+ <gl-button
246
+ category="tertiary"
247
+ variant="default"
248
+ icon="close"
249
+ size="small"
250
+ class="gl-ml-auto"
251
+ data-testid="chat-close-button"
252
+ :aria-label="$options.i18n.CHAT_CLOSE_LABEL"
253
+ @click="$emit('close')"
254
+ />
257
255
  </div>
258
256
  </div>
259
257
 
@@ -141,6 +141,7 @@ $drawer-width: 400px;
141
141
  overflow: hidden;
142
142
  background: var(--gl-control-background-color-default);
143
143
  box-shadow: inset 0 0 0 $gl-border-size-1 var(--gl-control-border-color-default);
144
+ border-radius: px-to-rem(20px);
144
145
 
145
146
  &:focus-within {
146
147
  @include gl-focus($color: var(--gl-control-border-color-focus));
@@ -151,6 +152,7 @@ $drawer-width: 400px;
151
152
  resize: none;
152
153
  max-height: 240px;
153
154
  padding-right: 40px;
155
+ border-radius: px-to-rem(20px);
154
156
  }
155
157
 
156
158
  &::after {
@@ -158,7 +160,6 @@ $drawer-width: 400px;
158
160
  @apply gl-invisible;
159
161
  @apply gl-p-4;
160
162
  @apply gl-font-regular;
161
- @apply gl-absolute;
162
163
  padding-right: 40px;
163
164
  word-break: break-word;
164
165
  }