@gitlab/duo-ui 15.9.0 → 15.10.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.
- package/dist/components/agentic_chat/components/agentic_binary_feedback/agentic_binary_feedback.js +24 -147
- package/dist/components/agentic_chat/components/agentic_feedback_panel/agentic_feedback_panel.js +185 -0
- package/dist/components/chat/components/duo_chat_message/duo_chat_message.js +21 -2
- package/dist/components/chat/components/duo_chat_threads/duo_chat_threads.js +45 -6
- package/dist/components/chat/components/message_action_bar/message_action_bar.js +43 -0
- package/dist/components/chat/constants.js +2 -1
- package/dist/components/chat/mock_data.js +60 -100
- package/dist/components.css +1 -1
- package/dist/components.css.map +1 -1
- package/dist/tailwind.css +1 -1
- package/dist/tailwind.css.map +1 -1
- package/package.json +1 -1
- package/src/components/agentic_chat/components/agentic_binary_feedback/agentic_binary_feedback.scss +2 -2
- package/src/components/agentic_chat/components/agentic_binary_feedback/agentic_binary_feedback.vue +35 -233
- package/src/components/agentic_chat/components/agentic_feedback_panel/agentic_feedback_panel.vue +228 -0
- package/src/components/chat/components/duo_chat_message/duo_chat_message.vue +64 -33
- package/src/components/chat/components/duo_chat_threads/duo_chat_threads.vue +56 -3
- package/src/components/chat/components/message_action_bar/message_action_bar.vue +11 -0
- package/src/components/chat/constants.js +2 -0
- package/src/components/chat/mock_data.js +60 -110
- package/translations.js +3 -0
package/src/components/agentic_chat/components/agentic_feedback_panel/agentic_feedback_panel.vue
ADDED
|
@@ -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>
|
|
@@ -16,6 +16,8 @@ import DocumentationSources from '../duo_chat_message_sources/duo_chat_message_s
|
|
|
16
16
|
// eslint-disable-next-line no-restricted-imports
|
|
17
17
|
import { copyToClipboard, concatUntilEmpty } from '../utils';
|
|
18
18
|
import AgenticBinaryFeedback from '../../../agentic_chat/components/agentic_binary_feedback/agentic_binary_feedback.vue';
|
|
19
|
+
import AgenticFeedbackPanel from '../../../agentic_chat/components/agentic_feedback_panel/agentic_feedback_panel.vue';
|
|
20
|
+
import MessageActionBar from '../message_action_bar/message_action_bar.vue';
|
|
19
21
|
import MessageFeedback from './message_feedback.vue';
|
|
20
22
|
import MarkdownRenderer from './markdown_renderer.vue';
|
|
21
23
|
import { CopyCodeElement } from './copy_code_element';
|
|
@@ -43,8 +45,10 @@ export default {
|
|
|
43
45
|
},
|
|
44
46
|
components: {
|
|
45
47
|
AgenticBinaryFeedback,
|
|
48
|
+
AgenticFeedbackPanel,
|
|
46
49
|
DocumentationSources,
|
|
47
50
|
DuoChatContextItemSelections,
|
|
51
|
+
MessageActionBar,
|
|
48
52
|
MessageFeedback,
|
|
49
53
|
MessageMap,
|
|
50
54
|
GlIcon,
|
|
@@ -130,6 +134,8 @@ export default {
|
|
|
130
134
|
messageChunks: [],
|
|
131
135
|
selectedContextItemsDefaultCollapsed: SELECTED_CONTEXT_ITEMS_DEFAULT_COLLAPSED,
|
|
132
136
|
copied: false,
|
|
137
|
+
feedbackChoice: null,
|
|
138
|
+
feedbackSubmitted: false,
|
|
133
139
|
};
|
|
134
140
|
},
|
|
135
141
|
computed: {
|
|
@@ -322,6 +328,16 @@ export default {
|
|
|
322
328
|
e.classList.remove(DUO_CODE_SCRIM_TOP_CLASS);
|
|
323
329
|
}
|
|
324
330
|
},
|
|
331
|
+
onFeedbackTypeSelected(type) {
|
|
332
|
+
this.feedbackChoice = type;
|
|
333
|
+
},
|
|
334
|
+
onFeedbackPanelSubmit(payload) {
|
|
335
|
+
this.feedbackSubmitted = true;
|
|
336
|
+
this.logEvent(payload);
|
|
337
|
+
},
|
|
338
|
+
onFeedbackPanelClose() {
|
|
339
|
+
this.feedbackChoice = null;
|
|
340
|
+
},
|
|
325
341
|
async copyMessage() {
|
|
326
342
|
try {
|
|
327
343
|
await copyToClipboard(this.message.content, this.$el);
|
|
@@ -403,42 +419,57 @@ export default {
|
|
|
403
419
|
|
|
404
420
|
<documentation-sources v-if="sources" :sources="sources" />
|
|
405
421
|
|
|
406
|
-
<
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
422
|
+
<template v-if="isAssistantMessage">
|
|
423
|
+
<message-action-bar>
|
|
424
|
+
<gl-animated-loader-icon v-if="isChunkAndNotCancelled" :is-on="true" />
|
|
425
|
+
<template v-if="shouldShowFeedbackLink && isBinaryFeedbackEnabled">
|
|
426
|
+
<agentic-binary-feedback
|
|
427
|
+
v-if="showBinaryFeedback"
|
|
428
|
+
:feedback-choice="feedbackChoice"
|
|
429
|
+
:submitted="feedbackSubmitted"
|
|
430
|
+
data-testid="agentic-feedback-latest"
|
|
431
|
+
@select-type="onFeedbackTypeSelected"
|
|
432
|
+
/>
|
|
433
|
+
<div
|
|
434
|
+
v-else
|
|
435
|
+
class="agentic-feedback-hover-wrapper"
|
|
436
|
+
:class="{
|
|
437
|
+
'-gl-mr-3 gl-w-0 gl-overflow-hidden gl-opacity-0':
|
|
438
|
+
!feedbackChoice && !feedbackSubmitted,
|
|
439
|
+
}"
|
|
440
|
+
data-testid="agentic-feedback-hover-container"
|
|
441
|
+
>
|
|
442
|
+
<agentic-binary-feedback
|
|
443
|
+
:feedback-choice="feedbackChoice"
|
|
444
|
+
:submitted="feedbackSubmitted"
|
|
445
|
+
@select-type="onFeedbackTypeSelected"
|
|
446
|
+
/>
|
|
447
|
+
</div>
|
|
448
|
+
</template>
|
|
449
|
+
<message-feedback
|
|
450
|
+
v-else-if="shouldShowFeedbackLink"
|
|
451
|
+
:has-feedback="hasFeedback"
|
|
415
452
|
@feedback="logEvent"
|
|
416
453
|
/>
|
|
417
|
-
<
|
|
418
|
-
v-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
:icon="copied ? 'check-circle-filled' : 'copy-to-clipboard'"
|
|
435
|
-
:class="{ '!gl-text-success': copied, '!gl-text-subtle': !copied }"
|
|
436
|
-
category="tertiary"
|
|
437
|
-
size="small"
|
|
438
|
-
@click="copyMessage"
|
|
439
|
-
@focusout="copied = false"
|
|
454
|
+
<gl-button
|
|
455
|
+
v-if="shouldShowCopyAction"
|
|
456
|
+
v-gl-tooltip
|
|
457
|
+
:title="copied ? $options.i18n.CHAT_MESSAGE_COPIED : $options.i18n.CHAT_MESSAGE_COPY"
|
|
458
|
+
:icon="copied ? 'check-circle-filled' : 'copy-to-clipboard'"
|
|
459
|
+
:class="{ '!gl-text-success': copied, '!gl-text-subtle': !copied }"
|
|
460
|
+
category="tertiary"
|
|
461
|
+
size="small"
|
|
462
|
+
@click="copyMessage"
|
|
463
|
+
@focusout="copied = false"
|
|
464
|
+
/>
|
|
465
|
+
</message-action-bar>
|
|
466
|
+
<agentic-feedback-panel
|
|
467
|
+
v-if="feedbackChoice && !feedbackSubmitted"
|
|
468
|
+
:feedback-type="feedbackChoice"
|
|
469
|
+
@submit="onFeedbackPanelSubmit"
|
|
470
|
+
@close="onFeedbackPanelClose"
|
|
440
471
|
/>
|
|
441
|
-
</
|
|
472
|
+
</template>
|
|
442
473
|
|
|
443
474
|
<duo-chat-context-item-selections
|
|
444
475
|
v-if="displaySelectedContextItems && isUserMessage"
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import { GlButton, GlAvatar } from '@gitlab/ui';
|
|
2
|
+
import { GlButton, GlAvatar, GlSearchBoxByType } from '@gitlab/ui';
|
|
3
|
+
import debounce from 'lodash/debounce';
|
|
3
4
|
import { sprintf, translate } from '../../../../utils/i18n';
|
|
4
5
|
import { formatLocalizedDate } from '../../../../utils/date';
|
|
6
|
+
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '../../constants';
|
|
5
7
|
import DuoChatThreadsEmpty from './duo_chat_threads_empty.vue';
|
|
6
8
|
import DuoChatThreadsSkeleton from './duo_chat_threads_skeleton_loader.vue';
|
|
7
9
|
|
|
@@ -13,6 +15,9 @@ const i18n = {
|
|
|
13
15
|
THREAD_DELETE_LABEL: translate('DuoChat.threadDeleteLabel', 'Delete this chat'),
|
|
14
16
|
OPEN_CHAT_LABEL: translate('DuoChat.openChatLabel', 'Open chat: %{title}'),
|
|
15
17
|
UNTITLED_CHAT_TITLE: translate('DuoChat.untitledChatTitle', 'Untitled Chat'),
|
|
18
|
+
SEARCH_PLACEHOLDER: translate('DuoChat.searchPlaceholder', 'Search chats...'),
|
|
19
|
+
NO_RESULTS: translate('DuoChat.noSearchResults', 'No results found'),
|
|
20
|
+
CHAT_HISTORY_SEARCH: translate('DuoChat.chatHistorySearch', 'Search chat history'),
|
|
16
21
|
};
|
|
17
22
|
|
|
18
23
|
export default {
|
|
@@ -21,6 +26,7 @@ export default {
|
|
|
21
26
|
components: {
|
|
22
27
|
GlButton,
|
|
23
28
|
GlAvatar,
|
|
29
|
+
GlSearchBoxByType,
|
|
24
30
|
DuoChatThreadsEmpty,
|
|
25
31
|
DuoChatThreadsSkeleton,
|
|
26
32
|
},
|
|
@@ -41,13 +47,30 @@ export default {
|
|
|
41
47
|
},
|
|
42
48
|
},
|
|
43
49
|
|
|
50
|
+
data() {
|
|
51
|
+
return {
|
|
52
|
+
searchQuery: '',
|
|
53
|
+
debouncedSearchQuery: '',
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
|
|
44
57
|
computed: {
|
|
58
|
+
filteredThreads() {
|
|
59
|
+
if (!this.debouncedSearchQuery.trim()) {
|
|
60
|
+
return this.threads;
|
|
61
|
+
}
|
|
62
|
+
const query = this.debouncedSearchQuery.trim();
|
|
63
|
+
return this.threads.filter((thread) => {
|
|
64
|
+
const { title = '', goal = '', agentName = '' } = thread;
|
|
65
|
+
return title.includes(query) || goal.includes(query) || agentName.includes(query);
|
|
66
|
+
});
|
|
67
|
+
},
|
|
45
68
|
groupedThreads() {
|
|
46
|
-
if (!this.
|
|
69
|
+
if (!this.hasFilteredThreads) {
|
|
47
70
|
return {};
|
|
48
71
|
}
|
|
49
72
|
|
|
50
|
-
return this.
|
|
73
|
+
return this.filteredThreads
|
|
51
74
|
.slice()
|
|
52
75
|
.sort((a, b) => this.compareThreadDates(b.updatedAt, a.updatedAt))
|
|
53
76
|
.sort((a, b) => this.compareThreadDates(b.lastUpdatedAt, a.lastUpdatedAt))
|
|
@@ -63,6 +86,21 @@ export default {
|
|
|
63
86
|
hasThreads() {
|
|
64
87
|
return this.threads.length > 0;
|
|
65
88
|
},
|
|
89
|
+
hasFilteredThreads() {
|
|
90
|
+
return this.filteredThreads.length > 0;
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
watch: {
|
|
95
|
+
searchQuery(newValue) {
|
|
96
|
+
this.updateDebouncedSearchQuery(newValue);
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
created() {
|
|
101
|
+
this.updateDebouncedSearchQuery = debounce((value) => {
|
|
102
|
+
this.debouncedSearchQuery = value;
|
|
103
|
+
}, DEFAULT_DEBOUNCE_AND_THROTTLE_MS);
|
|
66
104
|
},
|
|
67
105
|
|
|
68
106
|
methods: {
|
|
@@ -102,7 +140,22 @@ export default {
|
|
|
102
140
|
<div class="gl-flex gl-flex-col gl-overflow-hidden">
|
|
103
141
|
<duo-chat-threads-skeleton v-if="loading" />
|
|
104
142
|
<template v-else-if="hasThreads">
|
|
143
|
+
<div class="gl-px-4 gl-pt-4">
|
|
144
|
+
<span class="gl-sr-only">{{ $options.i18n.CHAT_HISTORY_SEARCH }}</span>
|
|
145
|
+
<gl-search-box-by-type
|
|
146
|
+
v-model="searchQuery"
|
|
147
|
+
data-testid="chat-threads-search-input"
|
|
148
|
+
:placeholder="$options.i18n.SEARCH_PLACEHOLDER"
|
|
149
|
+
/>
|
|
150
|
+
</div>
|
|
105
151
|
<div class="gl-grow gl-overflow-y-scroll gl-px-4 gl-py-5">
|
|
152
|
+
<p
|
|
153
|
+
v-if="!hasFilteredThreads"
|
|
154
|
+
data-testid="chat-threads-no-results"
|
|
155
|
+
class="gl-mb-0 gl-text-center gl-text-subtle"
|
|
156
|
+
>
|
|
157
|
+
{{ $options.i18n.NO_RESULTS }}
|
|
158
|
+
</p>
|
|
106
159
|
<div v-for="(threadsForDate, date) in groupedThreads" :key="date" class="gl-mb-3">
|
|
107
160
|
<div
|
|
108
161
|
data-testid="chat-threads-date-header"
|
|
@@ -7,6 +7,8 @@ export const CHAT_BASE_COMMANDS = [CHAT_RESET_MESSAGE, CHAT_CLEAR_MESSAGE, CHAT_
|
|
|
7
7
|
|
|
8
8
|
export const LOADING_TRANSITION_DURATION = 7500;
|
|
9
9
|
|
|
10
|
+
export const DEFAULT_DEBOUNCE_AND_THROTTLE_MS = 250;
|
|
11
|
+
|
|
10
12
|
export const DOCUMENTATION_SOURCE_TYPES = {
|
|
11
13
|
HANDBOOK: {
|
|
12
14
|
value: 'handbook',
|