@gitlab/duo-ui 15.10.0 → 15.11.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 (36) hide show
  1. package/dist/components/agentic_chat/agentic_duo_chat.js +1 -1
  2. package/dist/components/agentic_chat/components/agentic_binary_feedback/agentic_binary_feedback.js +24 -147
  3. package/dist/components/agentic_chat/components/agentic_feedback_panel/agentic_feedback_panel.js +185 -0
  4. package/dist/components/agentic_chat/web_agentic_duo_chat.js +1 -1
  5. package/dist/components/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu.js +1 -1
  6. package/dist/components/chat/components/duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections.js +1 -1
  7. package/dist/components/chat/components/duo_chat_message/duo_chat_message.js +22 -3
  8. package/dist/components/chat/components/duo_chat_message/message_types/message_tool_kv_section.js +1 -1
  9. package/dist/components/chat/components/duo_chat_message_tool_approval/message_tool_approval.js +1 -2
  10. package/dist/components/chat/components/duo_chat_threads/duo_chat_threads.js +1 -1
  11. package/dist/components/chat/components/message_action_bar/message_action_bar.js +43 -0
  12. package/dist/components/chat/duo_chat.js +1 -1
  13. package/dist/components/chat/web_duo_chat.js +1 -1
  14. package/dist/components.css +1 -1
  15. package/dist/components.css.map +1 -1
  16. package/dist/tailwind.css +1 -1
  17. package/dist/tailwind.css.map +1 -1
  18. package/dist/utils/i18n.js +1 -1
  19. package/dist/utils/object.js +1 -2
  20. package/package.json +5 -5
  21. package/src/components/agentic_chat/agentic_duo_chat.vue +1 -1
  22. package/src/components/agentic_chat/components/agentic_binary_feedback/agentic_binary_feedback.scss +2 -2
  23. package/src/components/agentic_chat/components/agentic_binary_feedback/agentic_binary_feedback.vue +35 -233
  24. package/src/components/agentic_chat/components/agentic_feedback_panel/agentic_feedback_panel.vue +228 -0
  25. package/src/components/agentic_chat/web_agentic_duo_chat.vue +1 -1
  26. package/src/components/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu.vue +1 -1
  27. package/src/components/chat/components/duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections.vue +1 -1
  28. package/src/components/chat/components/duo_chat_message/duo_chat_message.vue +65 -34
  29. package/src/components/chat/components/duo_chat_message/message_types/message_tool_kv_section.vue +1 -1
  30. package/src/components/chat/components/duo_chat_message_tool_approval/message_tool_approval.vue +1 -2
  31. package/src/components/chat/components/duo_chat_threads/duo_chat_threads.vue +1 -1
  32. package/src/components/chat/components/message_action_bar/message_action_bar.vue +11 -0
  33. package/src/components/chat/duo_chat.vue +1 -1
  34. package/src/components/chat/web_duo_chat.vue +1 -1
  35. package/src/utils/i18n.js +1 -1
  36. package/src/utils/object.js +1 -2
@@ -1,173 +1,61 @@
1
1
  <script>
2
- import { nextTick } from 'vue';
3
- import { GlButton, GlIcon, GlTooltipDirective, GlFormTextarea, GlForm } from '@gitlab/ui';
2
+ import { GlButton, GlIcon, GlTooltipDirective } from '@gitlab/ui';
4
3
  import { translate } from '@gitlab/ui/dist/utils/i18n';
5
4
 
6
5
  export const i18n = {
7
6
  THANKS: translate('AgenticBinaryFeedback.thanks', 'Thanks for the feedback!'),
8
7
  THUMBS_UP: translate('AgenticBinaryFeedback.thumbsUp', 'Helpful'),
9
8
  THUMBS_DOWN: translate('AgenticBinaryFeedback.thumbsDown', 'Not helpful'),
10
- TELL_US_MORE: translate('AgenticBinaryFeedback.tellUsMore', 'Tell us more'),
11
- SUBMIT: translate('AgenticBinaryFeedback.submit', 'Submit'),
12
- BACK: translate('AgenticBinaryFeedback.back', 'Back'),
13
- CLOSE: translate('AgenticBinaryFeedback.close', 'Close'),
14
- FEEDBACK_PLACEHOLDER: translate('AgenticBinaryFeedback.placeholder', 'Share your feedback...'),
15
- THUMBS_UP_HEADER: translate('AgenticBinaryFeedback.thumbsUpHeader', 'What made this helpful?'),
16
- THUMBS_DOWN_HEADER: translate(
17
- 'AgenticBinaryFeedback.thumbsDownHeader',
18
- 'What would have been more helpful?'
19
- ),
20
- TOO_GENERIC: translate('AgenticBinaryFeedback.tooGeneric', 'Too generic'),
21
- MISSING_STEPS: translate('AgenticBinaryFeedback.missingSteps', 'Missing steps'),
22
- WRONG_CONTEXT: translate('AgenticBinaryFeedback.wrongContext', 'Wrong context'),
23
- OUTDATED_INCORRECT: translate('AgenticBinaryFeedback.outdatedIncorrect', 'Outdated/incorrect'),
24
- SOLVED_PROBLEM: translate('AgenticBinaryFeedback.solvedProblem', 'Solved my problem'),
25
- SAVED_TIME: translate('AgenticBinaryFeedback.savedTime', 'Saved me time'),
26
- GOOD_EXAMPLES: translate('AgenticBinaryFeedback.goodExamples', 'Good examples'),
27
- ACCURATE_INFO: translate('AgenticBinaryFeedback.accurateInfo', 'Accurate information'),
28
- };
29
-
30
- export const thumbsDownReasons = [
31
- { key: 'too_generic', label: i18n.TOO_GENERIC },
32
- { key: 'missing_steps', label: i18n.MISSING_STEPS },
33
- { key: 'wrong_context', label: i18n.WRONG_CONTEXT },
34
- { key: 'outdated_incorrect', label: i18n.OUTDATED_INCORRECT },
35
- { key: 'tell_us_more', label: i18n.TELL_US_MORE, isCustom: true },
36
- ];
37
-
38
- export const thumbsUpReasons = [
39
- { key: 'solved_problem', label: i18n.SOLVED_PROBLEM },
40
- { key: 'saved_time', label: i18n.SAVED_TIME },
41
- { key: 'good_examples', label: i18n.GOOD_EXAMPLES },
42
- { key: 'accurate_info', label: i18n.ACCURATE_INFO },
43
- { key: 'tell_us_more', label: i18n.TELL_US_MORE, isCustom: true },
44
- ];
45
-
46
- export const FEEDBACK_VIEW = {
47
- CLOSED: 'closed',
48
- REASON_LIST: 'reason_list',
49
- TELL_US_MORE: 'tell_us_more',
50
9
  };
51
10
 
52
11
  export default {
53
12
  name: 'AgenticBinaryFeedback',
54
13
  i18n,
55
- thumbsUpReasons,
56
- thumbsDownReasons,
57
14
  components: {
58
15
  GlButton,
59
16
  GlIcon,
60
- GlFormTextarea,
61
- GlForm,
62
17
  },
63
18
  directives: {
64
19
  GlTooltip: GlTooltipDirective,
65
20
  },
66
- data() {
67
- return {
68
- feedbackType: null,
69
- feedbackView: FEEDBACK_VIEW.CLOSED,
70
- customFeedback: '',
71
- feedbackSubmitted: false,
72
- };
21
+ props: {
22
+ feedbackChoice: {
23
+ type: String,
24
+ required: false,
25
+ default: null,
26
+ validator: (value) => value === null || ['thumbs_up', 'thumbs_down'].includes(value),
27
+ },
28
+ submitted: {
29
+ type: Boolean,
30
+ required: false,
31
+ default: false,
32
+ },
73
33
  },
74
34
  computed: {
75
35
  isThumbsUp() {
76
- return this.feedbackType === 'thumbs_up';
36
+ return this.feedbackChoice === 'thumbs_up';
77
37
  },
78
38
  isThumbsDown() {
79
- return this.feedbackType === 'thumbs_down';
80
- },
81
- reasonOptions() {
82
- return this.isThumbsUp ? this.$options.thumbsUpReasons : this.$options.thumbsDownReasons;
83
- },
84
- reasonHeader() {
85
- return this.isThumbsUp
86
- ? this.$options.i18n.THUMBS_UP_HEADER
87
- : this.$options.i18n.THUMBS_DOWN_HEADER;
88
- },
89
- isOverCharacterLimit() {
90
- return this.customFeedback.length > 140;
39
+ return this.feedbackChoice === 'thumbs_down';
91
40
  },
92
- trimmedFeedback() {
93
- return this.customFeedback.trim();
41
+ showButtons() {
42
+ return !this.feedbackChoice && !this.submitted;
94
43
  },
95
- isTextareaValid() {
96
- return this.trimmedFeedback.length === 0 ? null : !this.isOverCharacterLimit;
97
- },
98
- canSubmitCustomFeedback() {
99
- return this.trimmedFeedback.length > 0 && !this.isOverCharacterLimit;
100
- },
101
- characterCountText() {
102
- const remaining = 140 - this.customFeedback.length;
103
- if (remaining >= 0) {
104
- return remaining === 1
105
- ? `${remaining} character remaining`
106
- : `${remaining} characters remaining`;
107
- }
108
- const over = Math.abs(remaining);
109
- return over === 1 ? `${over} character over limit` : `${over} characters over limit`;
110
- },
111
- isDropdownOpen() {
112
- return this.feedbackView !== FEEDBACK_VIEW.CLOSED;
113
- },
114
- isReasonListVisible() {
115
- return this.feedbackView === FEEDBACK_VIEW.REASON_LIST;
116
- },
117
- isTellUsMoreVisible() {
118
- return this.feedbackView === FEEDBACK_VIEW.TELL_US_MORE;
44
+ showSelectedIcon() {
45
+ return this.feedbackChoice && !this.submitted;
119
46
  },
120
47
  },
121
48
  methods: {
122
49
  selectFeedbackType(type) {
123
- this.feedbackType = type;
124
- this.feedbackView = FEEDBACK_VIEW.REASON_LIST;
125
- this.customFeedback = '';
126
- this.scrollToDropdown();
127
- },
128
- scrollToDropdown() {
129
- nextTick(() => {
130
- const el = this.$refs.dropdown?.$el || this.$refs.dropdown;
131
- el?.scrollIntoView?.({ behavior: 'smooth', block: 'nearest' });
132
- });
133
- },
134
- selectReason(reason) {
135
- if (reason.isCustom) {
136
- this.feedbackView = FEEDBACK_VIEW.TELL_US_MORE;
137
- return;
138
- }
139
- this.submitFeedback(reason.key);
140
- },
141
- submitCustomFeedback() {
142
- this.submitFeedback(`custom_${this.customFeedback.trim()}`);
143
- },
144
- submitFeedback(feedbackReason) {
145
- this.$emit('feedback', {
146
- feedbackType: this.feedbackType,
147
- feedbackReason,
148
- });
149
- this.feedbackView = FEEDBACK_VIEW.CLOSED;
150
- this.feedbackSubmitted = true;
151
- },
152
- goBackToReasons() {
153
- this.feedbackView = FEEDBACK_VIEW.REASON_LIST;
154
- this.customFeedback = '';
155
- },
156
- closeDropdown() {
157
- this.feedbackView = FEEDBACK_VIEW.CLOSED;
158
- this.customFeedback = '';
159
- this.feedbackType = null;
50
+ this.$emit('select-type', type);
160
51
  },
161
52
  },
162
53
  };
163
54
  </script>
164
55
 
165
56
  <template>
166
- <div
167
- class="agentic-binary-feedback gl-flex gl-items-center gl-gap-2"
168
- :class="{ 'gl-relative': !isDropdownOpen }"
169
- >
170
- <template v-if="!feedbackSubmitted && !isDropdownOpen">
57
+ <div class="agentic-binary-feedback gl-flex gl-items-center gl-gap-2">
58
+ <template v-if="showButtons">
171
59
  <gl-button
172
60
  v-gl-tooltip
173
61
  :title="$options.i18n.THUMBS_UP"
@@ -190,106 +78,20 @@ export default {
190
78
  />
191
79
  </template>
192
80
 
193
- <template v-else-if="isDropdownOpen">
194
- <div class="gl-flex gl-h-6 gl-items-center">
195
- <gl-icon
196
- :name="isThumbsUp ? 'thumb-up' : 'thumb-down'"
197
- :size="16"
198
- :class="isThumbsUp ? 'gl-text-success' : 'gl-text-danger'"
199
- data-testid="selected-thumb-icon"
200
- />
201
- </div>
202
-
203
- <div
204
- ref="dropdown"
205
- class="feedback-reason-dropdown gl-l-0 gl-r-0 gl-border gl-absolute gl-top-full gl-mb-4 gl-mt-4 gl-w-full gl-rounded-lg gl-border-default gl-bg-subtle gl-p-3"
206
- data-testid="feedback-reason-dropdown"
207
- >
208
- <div class="gl-mb-3 gl-flex gl-items-center gl-justify-between gl-gap-2">
209
- <p class="gl-m-0 gl-text-sm gl-font-bold" data-testid="reason-header">
210
- {{ reasonHeader }}
211
- </p>
212
- <div class="gl-flex gl-shrink-0 gl-items-center gl-gap-1">
213
- <gl-button
214
- v-if="isTellUsMoreVisible"
215
- icon="go-back"
216
- category="tertiary"
217
- size="small"
218
- :aria-label="$options.i18n.BACK"
219
- data-testid="back-button"
220
- @click="goBackToReasons"
221
- />
222
- <gl-button
223
- icon="close"
224
- category="tertiary"
225
- size="small"
226
- class="gl-mr-[-5px]"
227
- :aria-label="$options.i18n.CLOSE"
228
- data-testid="close-dropdown-button"
229
- @click="closeDropdown"
230
- />
231
- </div>
232
- </div>
233
-
234
- <div
235
- v-if="isReasonListVisible"
236
- class="gl-flex gl-flex-wrap gl-gap-2"
237
- data-testid="reason-buttons"
238
- >
239
- <gl-button
240
- v-for="reason in reasonOptions"
241
- :key="reason.key"
242
- category="secondary"
243
- size="small"
244
- class="gl-rounded-full"
245
- :icon="reason.isCustom ? 'pencil' : undefined"
246
- :data-testid="`reason-${reason.key}`"
247
- @click="selectReason(reason)"
248
- >
249
- {{ reason.label }}
250
- </gl-button>
251
- </div>
252
-
253
- <div
254
- v-if="isTellUsMoreVisible"
255
- class="gl-flex gl-w-full gl-flex-col gl-gap-3"
256
- data-testid="tell-us-more-view"
257
- >
258
- <gl-form
259
- class="gl-flex gl-w-full gl-flex-col gl-gap-3"
260
- @submit.prevent="canSubmitCustomFeedback ? submitCustomFeedback() : null"
261
- >
262
- <gl-form-textarea
263
- v-model="customFeedback"
264
- :state="isTextareaValid"
265
- :placeholder="$options.i18n.FEEDBACK_PLACEHOLDER"
266
- :rows="3"
267
- :max-rows="5"
268
- data-testid="custom-feedback-textarea"
269
- />
270
- <div class="gl-flex gl-items-center gl-justify-between">
271
- <gl-button
272
- variant="confirm"
273
- size="small"
274
- type="submit"
275
- data-testid="submit-custom-feedback-button"
276
- >
277
- {{ $options.i18n.SUBMIT }}
278
- </gl-button>
279
- <span
280
- :class="isOverCharacterLimit ? 'gl-text-danger' : 'gl-text-subtle'"
281
- class="gl-text-sm"
282
- data-testid="character-count"
283
- >
284
- {{ characterCountText }}
285
- </span>
286
- </div>
287
- </gl-form>
288
- </div>
289
- </div>
290
- </template>
81
+ <div v-else-if="showSelectedIcon" class="gl-flex gl-h-6 gl-items-center">
82
+ <gl-icon
83
+ :name="isThumbsUp ? 'thumb-up' : 'thumb-down'"
84
+ :size="16"
85
+ :class="isThumbsUp ? 'gl-text-success' : 'gl-text-danger'"
86
+ data-testid="selected-thumb-icon"
87
+ />
88
+ </div>
291
89
 
292
- <div v-else class="gl-relative gl-flex gl-items-center gl-gap-2" data-testid="feedback-given">
90
+ <div
91
+ v-else-if="submitted"
92
+ class="gl-relative gl-flex gl-items-center gl-gap-2"
93
+ data-testid="feedback-given"
94
+ >
293
95
  <div class="feedback-colored-icon" data-testid="feedback-colored-icon">
294
96
  <gl-icon
295
97
  v-if="isThumbsUp"
@@ -0,0 +1,228 @@
1
+ <script>
2
+ import { GlButton, GlFormTextarea, GlForm } from '@gitlab/ui';
3
+ import { translate } from '@gitlab/ui/dist/utils/i18n';
4
+
5
+ export const i18n = {
6
+ TELL_US_MORE: translate('AgenticBinaryFeedback.tellUsMore', 'Tell us more'),
7
+ SUBMIT: translate('AgenticBinaryFeedback.submit', 'Submit'),
8
+ BACK: translate('AgenticBinaryFeedback.back', 'Back'),
9
+ CLOSE: translate('AgenticBinaryFeedback.close', 'Close'),
10
+ FEEDBACK_PLACEHOLDER: translate('AgenticBinaryFeedback.placeholder', 'Share your feedback...'),
11
+ THUMBS_UP_HEADER: translate('AgenticBinaryFeedback.thumbsUpHeader', 'What made this helpful?'),
12
+ THUMBS_DOWN_HEADER: translate(
13
+ 'AgenticBinaryFeedback.thumbsDownHeader',
14
+ 'What would have been more helpful?'
15
+ ),
16
+ TOO_GENERIC: translate('AgenticBinaryFeedback.tooGeneric', 'Too generic'),
17
+ MISSING_STEPS: translate('AgenticBinaryFeedback.missingSteps', 'Missing steps'),
18
+ WRONG_CONTEXT: translate('AgenticBinaryFeedback.wrongContext', 'Wrong context'),
19
+ OUTDATED_INCORRECT: translate('AgenticBinaryFeedback.outdatedIncorrect', 'Outdated/incorrect'),
20
+ SOLVED_PROBLEM: translate('AgenticBinaryFeedback.solvedProblem', 'Solved my problem'),
21
+ SAVED_TIME: translate('AgenticBinaryFeedback.savedTime', 'Saved me time'),
22
+ GOOD_EXAMPLES: translate('AgenticBinaryFeedback.goodExamples', 'Good examples'),
23
+ ACCURATE_INFO: translate('AgenticBinaryFeedback.accurateInfo', 'Accurate information'),
24
+ };
25
+
26
+ export const PANEL_VIEW = {
27
+ REASON_LIST: 'reason_list',
28
+ TELL_US_MORE: 'tell_us_more',
29
+ };
30
+
31
+ export const thumbsDownReasons = [
32
+ { key: 'too_generic', label: i18n.TOO_GENERIC },
33
+ { key: 'missing_steps', label: i18n.MISSING_STEPS },
34
+ { key: 'wrong_context', label: i18n.WRONG_CONTEXT },
35
+ { key: 'outdated_incorrect', label: i18n.OUTDATED_INCORRECT },
36
+ { key: 'tell_us_more', label: i18n.TELL_US_MORE, isCustom: true },
37
+ ];
38
+
39
+ export const thumbsUpReasons = [
40
+ { key: 'solved_problem', label: i18n.SOLVED_PROBLEM },
41
+ { key: 'saved_time', label: i18n.SAVED_TIME },
42
+ { key: 'good_examples', label: i18n.GOOD_EXAMPLES },
43
+ { key: 'accurate_info', label: i18n.ACCURATE_INFO },
44
+ { key: 'tell_us_more', label: i18n.TELL_US_MORE, isCustom: true },
45
+ ];
46
+
47
+ const CHARACTER_LIMIT = 140;
48
+
49
+ export default {
50
+ name: 'AgenticFeedbackPanel',
51
+ i18n,
52
+ thumbsUpReasons,
53
+ thumbsDownReasons,
54
+ components: {
55
+ GlButton,
56
+ GlFormTextarea,
57
+ GlForm,
58
+ },
59
+ props: {
60
+ feedbackType: {
61
+ type: String,
62
+ required: true,
63
+ validator: (value) => ['thumbs_up', 'thumbs_down'].includes(value),
64
+ },
65
+ },
66
+ data() {
67
+ return {
68
+ panelView: PANEL_VIEW.REASON_LIST,
69
+ customFeedback: '',
70
+ };
71
+ },
72
+ computed: {
73
+ isThumbsUp() {
74
+ return this.feedbackType === 'thumbs_up';
75
+ },
76
+ reasonOptions() {
77
+ return this.isThumbsUp ? this.$options.thumbsUpReasons : this.$options.thumbsDownReasons;
78
+ },
79
+ reasonHeader() {
80
+ return this.isThumbsUp
81
+ ? this.$options.i18n.THUMBS_UP_HEADER
82
+ : this.$options.i18n.THUMBS_DOWN_HEADER;
83
+ },
84
+ isOverCharacterLimit() {
85
+ return this.customFeedback.length > CHARACTER_LIMIT;
86
+ },
87
+ trimmedFeedback() {
88
+ return this.customFeedback.trim();
89
+ },
90
+ isTextareaValid() {
91
+ return this.trimmedFeedback.length === 0 ? null : !this.isOverCharacterLimit;
92
+ },
93
+ canSubmitCustomFeedback() {
94
+ return this.trimmedFeedback.length > 0 && !this.isOverCharacterLimit;
95
+ },
96
+ characterCountText() {
97
+ const remaining = CHARACTER_LIMIT - this.customFeedback.length;
98
+ if (remaining >= 0) {
99
+ return remaining === 1
100
+ ? `${remaining} character remaining`
101
+ : `${remaining} characters remaining`;
102
+ }
103
+ const over = Math.abs(remaining);
104
+ return over === 1 ? `${over} character over limit` : `${over} characters over limit`;
105
+ },
106
+ isReasonListVisible() {
107
+ return this.panelView === PANEL_VIEW.REASON_LIST;
108
+ },
109
+ isTellUsMoreVisible() {
110
+ return this.panelView === PANEL_VIEW.TELL_US_MORE;
111
+ },
112
+ },
113
+ methods: {
114
+ selectReason(reason) {
115
+ if (reason.isCustom) {
116
+ this.panelView = PANEL_VIEW.TELL_US_MORE;
117
+ return;
118
+ }
119
+ this.$emit('submit', {
120
+ feedbackType: this.feedbackType,
121
+ feedbackReason: reason.key,
122
+ });
123
+ },
124
+ submitCustomFeedback() {
125
+ this.$emit('submit', {
126
+ feedbackType: this.feedbackType,
127
+ feedbackReason: `custom_${this.customFeedback.trim()}`,
128
+ });
129
+ },
130
+ goBackToReasons() {
131
+ this.panelView = PANEL_VIEW.REASON_LIST;
132
+ this.customFeedback = '';
133
+ },
134
+ close() {
135
+ this.$emit('close');
136
+ },
137
+ },
138
+ };
139
+ </script>
140
+
141
+ <template>
142
+ <div
143
+ class="agentic-feedback-panel gl-border gl-mt-2 gl-rounded-lg gl-border-default gl-bg-subtle gl-p-3"
144
+ data-testid="feedback-reason-dropdown"
145
+ >
146
+ <div class="gl-mb-3 gl-flex gl-items-center gl-justify-between gl-gap-2">
147
+ <p class="gl-m-0 gl-text-sm gl-font-bold" data-testid="reason-header">
148
+ {{ reasonHeader }}
149
+ </p>
150
+ <div class="gl-flex gl-shrink-0 gl-items-center gl-gap-1">
151
+ <gl-button
152
+ v-if="isTellUsMoreVisible"
153
+ icon="go-back"
154
+ category="tertiary"
155
+ size="small"
156
+ :aria-label="$options.i18n.BACK"
157
+ data-testid="back-button"
158
+ @click="goBackToReasons"
159
+ />
160
+ <gl-button
161
+ icon="close"
162
+ category="tertiary"
163
+ size="small"
164
+ class="gl-mr-[-5px]"
165
+ :aria-label="$options.i18n.CLOSE"
166
+ data-testid="close-dropdown-button"
167
+ @click="close"
168
+ />
169
+ </div>
170
+ </div>
171
+
172
+ <div
173
+ v-if="isReasonListVisible"
174
+ class="gl-flex gl-flex-wrap gl-gap-2"
175
+ data-testid="reason-buttons"
176
+ >
177
+ <gl-button
178
+ v-for="reason in reasonOptions"
179
+ :key="reason.key"
180
+ category="secondary"
181
+ size="small"
182
+ class="gl-rounded-full"
183
+ :icon="reason.isCustom ? 'pencil' : undefined"
184
+ :data-testid="`reason-${reason.key}`"
185
+ @click="selectReason(reason)"
186
+ >
187
+ {{ reason.label }}
188
+ </gl-button>
189
+ </div>
190
+
191
+ <div
192
+ v-if="isTellUsMoreVisible"
193
+ class="gl-flex gl-w-full gl-flex-col gl-gap-3"
194
+ data-testid="tell-us-more-view"
195
+ >
196
+ <gl-form
197
+ class="gl-flex gl-w-full gl-flex-col gl-gap-3"
198
+ @submit.prevent="canSubmitCustomFeedback ? submitCustomFeedback() : null"
199
+ >
200
+ <gl-form-textarea
201
+ v-model="customFeedback"
202
+ :state="isTextareaValid"
203
+ :placeholder="$options.i18n.FEEDBACK_PLACEHOLDER"
204
+ :rows="3"
205
+ :max-rows="5"
206
+ data-testid="custom-feedback-textarea"
207
+ />
208
+ <div class="gl-flex gl-items-center gl-justify-between">
209
+ <gl-button
210
+ variant="confirm"
211
+ size="small"
212
+ type="submit"
213
+ data-testid="submit-custom-feedback-button"
214
+ >
215
+ {{ $options.i18n.SUBMIT }}
216
+ </gl-button>
217
+ <span
218
+ :class="isOverCharacterLimit ? 'gl-text-danger' : 'gl-text-subtle'"
219
+ class="gl-text-sm"
220
+ data-testid="character-count"
221
+ >
222
+ {{ characterCountText }}
223
+ </span>
224
+ </div>
225
+ </gl-form>
226
+ </div>
227
+ </div>
228
+ </template>
@@ -1,5 +1,5 @@
1
1
  <script>
2
- import throttle from 'lodash/throttle';
2
+ import { throttle } from 'lodash-es';
3
3
 
4
4
  import {
5
5
  GlButton,
@@ -1,5 +1,5 @@
1
1
  <script>
2
- import debounce from 'lodash/debounce';
2
+ import { debounce } from 'lodash-es';
3
3
  import { GlButton, GlCard } from '@gitlab/ui';
4
4
  import { translate } from '../../../../../utils/i18n';
5
5
  import DuoChatContextItemSelections from '../duo_chat_context_item_selections/duo_chat_context_item_selections.vue';
@@ -1,5 +1,5 @@
1
1
  <script>
2
- import uniqueId from 'lodash/uniqueId';
2
+ import { uniqueId } from 'lodash-es';
3
3
  import { GlIcon, GlToken, GlTruncate } from '@gitlab/ui';
4
4
 
5
5
  import DuoChatContextItemPopover from '../duo_chat_context_item_popover/duo_chat_context_item_popover.vue';