@gitlab/duo-ui 8.3.0 → 8.5.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.
- package/CHANGELOG.md +35 -0
- package/dist/components/chat/components/duo_chat_header/duo_chat_header.js +7 -3
- package/dist/components/chat/components/duo_chat_threads/duo_chat_threads.js +96 -0
- package/dist/components/chat/components/duo_chat_threads/duo_chat_threads_empty.js +52 -0
- package/dist/components/chat/duo_chat.js +77 -9
- package/dist/components/chat/mock_data.js +72 -1
- package/dist/tailwind.css +1 -1
- package/dist/tailwind.css.map +1 -1
- package/dist/utils/date.js +27 -0
- package/package.json +1 -1
- package/src/components/chat/components/duo_chat_header/duo_chat_header.vue +16 -2
- package/src/components/chat/components/duo_chat_threads/duo_chat_threads.vue +122 -0
- package/src/components/chat/components/duo_chat_threads/duo_chat_threads_empty.vue +40 -0
- package/src/components/chat/duo_chat.vue +239 -168
- package/src/components/chat/mock_data.js +83 -0
- package/src/utils/date.js +24 -0
- package/translations.js +9 -1
|
@@ -25,17 +25,20 @@ import {
|
|
|
25
25
|
CHAT_INCLUDE_MESSAGE,
|
|
26
26
|
MESSAGE_MODEL_ROLES,
|
|
27
27
|
} from './constants';
|
|
28
|
+
import { VIEW_TYPES } from './components/duo_chat_header/constants';
|
|
28
29
|
import DuoChatLoader from './components/duo_chat_loader/duo_chat_loader.vue';
|
|
29
30
|
import DuoChatPredefinedPrompts from './components/duo_chat_predefined_prompts/duo_chat_predefined_prompts.vue';
|
|
30
31
|
import DuoChatConversation from './components/duo_chat_conversation/duo_chat_conversation.vue';
|
|
32
|
+
import DuoChatHeader from './components/duo_chat_header/duo_chat_header.vue';
|
|
33
|
+
import DuoChatThreads from './components/duo_chat_threads/duo_chat_threads.vue';
|
|
31
34
|
|
|
32
35
|
export const i18n = {
|
|
33
36
|
CHAT_DEFAULT_TITLE: translate('DuoChat.chatDefaultTitle', 'GitLab Duo Chat'),
|
|
37
|
+
CHAT_HISTORY_TITLE: translate('GlDuoChat.chatHistoryTitle', 'Chat history'),
|
|
34
38
|
CHAT_DISCLAMER: translate(
|
|
35
39
|
'GlDuoChat.chatDisclamer',
|
|
36
40
|
'Responses may be inaccurate. Verify before use.'
|
|
37
41
|
),
|
|
38
|
-
CHAT_CLOSE_LABEL: translate('DuoChat.chatCloseLabel', 'Close the Code Explanation'),
|
|
39
42
|
CHAT_EMPTY_STATE_TITLE: translate(
|
|
40
43
|
'DuoChat.chatEmptyStateTitle',
|
|
41
44
|
'👋 I am GitLab Duo Chat, your personal AI-powered assistant. How can I help you today?'
|
|
@@ -72,6 +75,17 @@ const itemsValidator = (items) => items.every(isMessage);
|
|
|
72
75
|
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
73
76
|
const slashCommandsValidator = (commands) => commands.every(isSlashCommand);
|
|
74
77
|
|
|
78
|
+
const isThread = (thread) =>
|
|
79
|
+
typeof thread === 'object' &&
|
|
80
|
+
typeof thread.id === 'string' &&
|
|
81
|
+
typeof thread.lastUpdatedAt === 'string' &&
|
|
82
|
+
typeof thread.createdAt === 'string' &&
|
|
83
|
+
typeof thread.conversationType === 'string' &&
|
|
84
|
+
(thread.title === null || typeof thread.title === 'string');
|
|
85
|
+
|
|
86
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
87
|
+
const threadListValidator = (threads) => threads.every(isThread);
|
|
88
|
+
|
|
75
89
|
export default {
|
|
76
90
|
name: 'DuoChat',
|
|
77
91
|
components: {
|
|
@@ -84,6 +98,8 @@ export default {
|
|
|
84
98
|
DuoChatLoader,
|
|
85
99
|
DuoChatPredefinedPrompts,
|
|
86
100
|
DuoChatConversation,
|
|
101
|
+
DuoChatHeader,
|
|
102
|
+
DuoChatThreads,
|
|
87
103
|
GlCard,
|
|
88
104
|
GlDropdownItem,
|
|
89
105
|
VueResizable,
|
|
@@ -138,6 +154,23 @@ export default {
|
|
|
138
154
|
default: () => [],
|
|
139
155
|
validator: itemsValidator,
|
|
140
156
|
},
|
|
157
|
+
/**
|
|
158
|
+
* The ID of the active thread (if any).
|
|
159
|
+
*/
|
|
160
|
+
activeThreadId: {
|
|
161
|
+
type: String,
|
|
162
|
+
required: false,
|
|
163
|
+
default: () => '',
|
|
164
|
+
},
|
|
165
|
+
/**
|
|
166
|
+
* The chat page that should be shown.
|
|
167
|
+
*/
|
|
168
|
+
multiThreadedView: {
|
|
169
|
+
type: String,
|
|
170
|
+
required: false,
|
|
171
|
+
default: VIEW_TYPES.LIST,
|
|
172
|
+
validator: (value) => [VIEW_TYPES.LIST, VIEW_TYPES.CHAT].includes(value),
|
|
173
|
+
},
|
|
141
174
|
/**
|
|
142
175
|
* Array of RequestIds that have been canceled.
|
|
143
176
|
*/
|
|
@@ -154,6 +187,16 @@ export default {
|
|
|
154
187
|
required: false,
|
|
155
188
|
default: '',
|
|
156
189
|
},
|
|
190
|
+
/**
|
|
191
|
+
* Array of messages to display in the chat.
|
|
192
|
+
*/
|
|
193
|
+
threadList: {
|
|
194
|
+
type: Array,
|
|
195
|
+
required: false,
|
|
196
|
+
default: () => [],
|
|
197
|
+
validator: threadListValidator,
|
|
198
|
+
},
|
|
199
|
+
|
|
157
200
|
/**
|
|
158
201
|
* Whether the chat is currently fetching a response from AI.
|
|
159
202
|
*/
|
|
@@ -236,6 +279,14 @@ export default {
|
|
|
236
279
|
required: false,
|
|
237
280
|
default: '',
|
|
238
281
|
},
|
|
282
|
+
/**
|
|
283
|
+
* Whether the chat is running in multi-threaded mode
|
|
284
|
+
*/
|
|
285
|
+
isMultithreaded: {
|
|
286
|
+
type: Boolean,
|
|
287
|
+
required: false,
|
|
288
|
+
default: false,
|
|
289
|
+
},
|
|
239
290
|
},
|
|
240
291
|
data() {
|
|
241
292
|
return {
|
|
@@ -247,9 +298,13 @@ export default {
|
|
|
247
298
|
compositionJustEnded: false,
|
|
248
299
|
contextItemsMenuIsOpen: false,
|
|
249
300
|
contextItemMenuRef: null,
|
|
301
|
+
currentView: this.multiThreadedView,
|
|
250
302
|
};
|
|
251
303
|
},
|
|
252
304
|
computed: {
|
|
305
|
+
shouldShowThreadList() {
|
|
306
|
+
return this.isMultithreaded && this.currentView === VIEW_TYPES.LIST;
|
|
307
|
+
},
|
|
253
308
|
withSlashCommands() {
|
|
254
309
|
return this.slashCommands.length > 0;
|
|
255
310
|
},
|
|
@@ -331,6 +386,9 @@ export default {
|
|
|
331
386
|
},
|
|
332
387
|
},
|
|
333
388
|
watch: {
|
|
389
|
+
multiThreadedView(newView) {
|
|
390
|
+
this.currentView = newView;
|
|
391
|
+
},
|
|
334
392
|
isLoading(newVal) {
|
|
335
393
|
if (!newVal && !this.isStreaming) {
|
|
336
394
|
this.displaySubmitButton = true; // Re-enable submit button when loading stops
|
|
@@ -363,6 +421,12 @@ export default {
|
|
|
363
421
|
},
|
|
364
422
|
|
|
365
423
|
methods: {
|
|
424
|
+
onGoBack() {
|
|
425
|
+
this.$emit('back-to-list');
|
|
426
|
+
},
|
|
427
|
+
onNewChat() {
|
|
428
|
+
this.$emit('new-chat');
|
|
429
|
+
},
|
|
366
430
|
updateSize(e) {
|
|
367
431
|
this.$emit('chat-resize', e);
|
|
368
432
|
},
|
|
@@ -551,6 +615,20 @@ export default {
|
|
|
551
615
|
setContextItemsMenuRef(ref) {
|
|
552
616
|
this.contextItemMenuRef = ref;
|
|
553
617
|
},
|
|
618
|
+
onSelectThread(thread) {
|
|
619
|
+
/**
|
|
620
|
+
* Emitted when a thread is selected from the history.
|
|
621
|
+
* @param {Object} thread The selected thread object
|
|
622
|
+
*/
|
|
623
|
+
this.$emit('thread-selected', thread);
|
|
624
|
+
},
|
|
625
|
+
onDeleteThread(threadId) {
|
|
626
|
+
/**
|
|
627
|
+
* Emitted when a thread is deleted from the history.
|
|
628
|
+
* @param {String} threadId The ID of the thread to delete
|
|
629
|
+
*/
|
|
630
|
+
this.$emit('delete-thread', threadId);
|
|
631
|
+
},
|
|
554
632
|
},
|
|
555
633
|
i18n,
|
|
556
634
|
};
|
|
@@ -585,184 +663,177 @@ export default {
|
|
|
585
663
|
role="complementary"
|
|
586
664
|
data-testid="chat-component"
|
|
587
665
|
>
|
|
588
|
-
<header
|
|
666
|
+
<duo-chat-header
|
|
589
667
|
v-if="showHeader"
|
|
590
|
-
|
|
591
|
-
:
|
|
592
|
-
'
|
|
593
|
-
|
|
594
|
-
|
|
668
|
+
:active-thread-id="activeThreadId"
|
|
669
|
+
:title="
|
|
670
|
+
isMultithreaded && currentView === 'list' ? $options.i18n.CHAT_HISTORY_TITLE : title
|
|
671
|
+
"
|
|
672
|
+
:error="error"
|
|
673
|
+
:is-multithreaded="isMultithreaded"
|
|
674
|
+
:current-view="currentView"
|
|
675
|
+
:should-render-resizable="shouldRenderResizable"
|
|
676
|
+
:badge-type="isMultithreaded ? null : badgeType"
|
|
677
|
+
@go-back="onGoBack"
|
|
678
|
+
@new-chat="onNewChat"
|
|
679
|
+
@close="hideChat"
|
|
595
680
|
>
|
|
596
|
-
<
|
|
597
|
-
<
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
category="tertiary"
|
|
601
|
-
variant="default"
|
|
602
|
-
icon="close"
|
|
603
|
-
size="small"
|
|
604
|
-
class="gl-ml-auto"
|
|
605
|
-
data-testid="chat-close-button"
|
|
606
|
-
:aria-label="$options.i18n.CHAT_CLOSE_LABEL"
|
|
607
|
-
@click="hideChat"
|
|
608
|
-
/>
|
|
609
|
-
</div>
|
|
610
|
-
|
|
611
|
-
<!--
|
|
612
|
-
@slot Subheader to be rendered right after the title. It is sticky and stays on top of the chat no matter the number of messages.
|
|
613
|
-
-->
|
|
614
|
-
<slot name="subheader"></slot>
|
|
681
|
+
<template #subheader>
|
|
682
|
+
<slot name="subheader"></slot>
|
|
683
|
+
</template>
|
|
684
|
+
</duo-chat-header>
|
|
615
685
|
|
|
616
|
-
|
|
617
|
-
<
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
686
|
+
<div v-if="shouldShowThreadList" class="gl-h-full">
|
|
687
|
+
<duo-chat-threads
|
|
688
|
+
:threads="threadList"
|
|
689
|
+
@new-chat="onNewChat"
|
|
690
|
+
@select-thread="onSelectThread"
|
|
691
|
+
@delete-thread="onDeleteThread"
|
|
692
|
+
@close="hideChat"
|
|
693
|
+
/>
|
|
694
|
+
</div>
|
|
695
|
+
<span v-else class="gl-h-full gl-flex gl-flex-col gl-justify-end">
|
|
696
|
+
<div
|
|
697
|
+
class="duo-chat-drawer-body gl-bg-default"
|
|
698
|
+
data-testid="chat-history"
|
|
699
|
+
@scroll="handleScrollingTrottled"
|
|
625
700
|
>
|
|
626
|
-
<
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
701
|
+
<transition-group
|
|
702
|
+
tag="section"
|
|
703
|
+
name="message"
|
|
704
|
+
class="duo-chat-history gl-flex gl-flex-col gl-justify-end"
|
|
705
|
+
:class="[
|
|
706
|
+
{
|
|
707
|
+
'gl-h-full': !hasMessages,
|
|
708
|
+
'force-scroll-bar': hasMessages,
|
|
709
|
+
},
|
|
710
|
+
]"
|
|
711
|
+
>
|
|
712
|
+
<duo-chat-conversation
|
|
713
|
+
v-for="(conversation, index) in conversations"
|
|
714
|
+
:key="`conversation-${index}`"
|
|
715
|
+
:enable-code-insertion="enableCodeInsertion"
|
|
716
|
+
:messages="conversation"
|
|
717
|
+
:canceled-request-ids="canceledRequestIds"
|
|
718
|
+
:show-delimiter="index > 0"
|
|
719
|
+
@track-feedback="onTrackFeedback"
|
|
720
|
+
@insert-code-snippet="onInsertCodeSnippet"
|
|
721
|
+
@copy-code-snippet="onCopyCodeSnippet"
|
|
722
|
+
@get-context-item-content="onGetContextItemContent"
|
|
723
|
+
/>
|
|
724
|
+
<template v-if="!hasMessages && !isLoading">
|
|
725
|
+
<div
|
|
726
|
+
key="empty-state-message"
|
|
727
|
+
class="duo-chat-message gl-rounded-bl-none gl-border-1 gl-border-solid gl-border-gray-50 gl-bg-gray-10 gl-p-4 gl-leading-20 gl-text-gray-900 gl-break-anywhere"
|
|
728
|
+
data-testid="gl-duo-chat-empty-state"
|
|
729
|
+
>
|
|
730
|
+
<p
|
|
731
|
+
v-if="emptyStateTitle"
|
|
732
|
+
data-testid="gl-duo-chat-empty-state-title"
|
|
733
|
+
class="gl-m-0"
|
|
734
|
+
>
|
|
735
|
+
{{ emptyStateTitle }}
|
|
736
|
+
</p>
|
|
737
|
+
<duo-chat-predefined-prompts
|
|
738
|
+
key="predefined-prompts"
|
|
739
|
+
:prompts="predefinedPrompts"
|
|
740
|
+
@click="sendPredefinedPrompt"
|
|
741
|
+
/>
|
|
742
|
+
</div>
|
|
743
|
+
</template>
|
|
744
|
+
<duo-chat-loader v-if="isLoading" key="loader" :tool-name="toolName" />
|
|
745
|
+
<div key="anchor" ref="anchor" class="scroll-anchor"></div>
|
|
746
|
+
</transition-group>
|
|
747
|
+
</div>
|
|
748
|
+
<footer
|
|
749
|
+
v-if="isChatAvailable"
|
|
750
|
+
data-testid="chat-footer"
|
|
751
|
+
class="duo-chat-drawer-footer duo-chat-drawer-footer-sticky gl-border-0 gl-bg-default gl-pb-3"
|
|
752
|
+
:class="{ 'duo-chat-drawer-body-scrim-on-footer': !scrolledToBottom }"
|
|
645
753
|
>
|
|
646
|
-
<
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
:enable-code-insertion="enableCodeInsertion"
|
|
650
|
-
:messages="conversation"
|
|
651
|
-
:canceled-request-ids="canceledRequestIds"
|
|
652
|
-
:show-delimiter="index > 0"
|
|
653
|
-
@track-feedback="onTrackFeedback"
|
|
654
|
-
@insert-code-snippet="onInsertCodeSnippet"
|
|
655
|
-
@copy-code-snippet="onCopyCodeSnippet"
|
|
656
|
-
@get-context-item-content="onGetContextItemContent"
|
|
657
|
-
/>
|
|
658
|
-
<template v-if="!hasMessages && !isLoading">
|
|
659
|
-
<div
|
|
660
|
-
key="empty-state-message"
|
|
661
|
-
class="duo-chat-message gl-rounded-bl-none gl-border-1 gl-border-solid gl-border-subtle gl-bg-subtle gl-p-4 gl-leading-20 gl-text-primary gl-break-anywhere"
|
|
662
|
-
data-testid="gl-duo-chat-empty-state"
|
|
663
|
-
>
|
|
664
|
-
<p v-if="emptyStateTitle" data-testid="gl-duo-chat-empty-state-title" class="gl-m-0">
|
|
665
|
-
{{ emptyStateTitle }}
|
|
666
|
-
</p>
|
|
667
|
-
<duo-chat-predefined-prompts
|
|
668
|
-
key="predefined-prompts"
|
|
669
|
-
:prompts="predefinedPrompts"
|
|
670
|
-
@click="sendPredefinedPrompt"
|
|
671
|
-
/>
|
|
672
|
-
</div>
|
|
673
|
-
</template>
|
|
674
|
-
<duo-chat-loader v-if="isLoading" key="loader" :tool-name="toolName" />
|
|
675
|
-
<div key="anchor" ref="anchor" class="scroll-anchor"></div>
|
|
676
|
-
</transition-group>
|
|
677
|
-
</div>
|
|
678
|
-
<footer
|
|
679
|
-
v-if="isChatAvailable"
|
|
680
|
-
data-testid="chat-footer"
|
|
681
|
-
class="duo-chat-drawer-footer duo-chat-drawer-footer-sticky gl-border-0 gl-bg-default gl-pb-3"
|
|
682
|
-
:class="{ 'duo-chat-drawer-body-scrim-on-footer': !scrolledToBottom }"
|
|
683
|
-
>
|
|
684
|
-
<gl-form data-testid="chat-prompt-form" @submit.stop.prevent="sendChatPrompt">
|
|
685
|
-
<div class="gl-relative gl-max-w-full">
|
|
686
|
-
<!--
|
|
754
|
+
<gl-form data-testid="chat-prompt-form" @submit.stop.prevent="sendChatPrompt">
|
|
755
|
+
<div class="gl-relative gl-max-w-full">
|
|
756
|
+
<!--
|
|
687
757
|
@slot For integrating `<gl-context-items-menu>` component if pinned-context should be available. The following scopedSlot properties are provided: `isOpen`, `onClose`, `setRef`, `focusPrompt`, which should be passed to the `<gl-context-items-menu>` component when rendering, e.g. `<template #context-items-menu="{ isOpen, onClose, setRef, focusPrompt }">` `<duo-chat-context-item-menu :ref="setRef" :open="isOpen" @close="onClose" @focus-prompt="focusPrompt" ...`
|
|
688
758
|
-->
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
759
|
+
<slot
|
|
760
|
+
name="context-items-menu"
|
|
761
|
+
:is-open="contextItemsMenuIsOpen"
|
|
762
|
+
:on-close="closeContextItemsMenuOpen"
|
|
763
|
+
:set-ref="setContextItemsMenuRef"
|
|
764
|
+
:focus-prompt="focusChatInput"
|
|
765
|
+
></slot>
|
|
766
|
+
</div>
|
|
697
767
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
>
|
|
703
|
-
<gl-card
|
|
704
|
-
v-if="shouldShowSlashCommands"
|
|
705
|
-
ref="commands"
|
|
706
|
-
class="slash-commands !gl-absolute gl-w-full -gl-translate-y-full gl-list-none gl-pl-0 gl-shadow-md"
|
|
707
|
-
body-class="!gl-p-2"
|
|
768
|
+
<gl-form-input-group>
|
|
769
|
+
<div
|
|
770
|
+
class="duo-chat-input gl-min-h-8 gl-max-w-full gl-grow gl-align-top"
|
|
771
|
+
:data-value="prompt"
|
|
708
772
|
>
|
|
709
|
-
<gl-
|
|
710
|
-
v-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
@click="selectSlashCommand(index)"
|
|
773
|
+
<gl-card
|
|
774
|
+
v-if="shouldShowSlashCommands"
|
|
775
|
+
ref="commands"
|
|
776
|
+
class="slash-commands !gl-absolute gl-w-full -gl-translate-y-full gl-list-none gl-pl-0 gl-shadow-md"
|
|
777
|
+
body-class="!gl-p-2"
|
|
715
778
|
>
|
|
716
|
-
<
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
779
|
+
<gl-dropdown-item
|
|
780
|
+
v-for="(command, index) in filteredSlashCommands"
|
|
781
|
+
:key="command.name"
|
|
782
|
+
:class="{ 'active-command': index === activeCommandIndex }"
|
|
783
|
+
@mouseenter.native="activeCommandIndex = index"
|
|
784
|
+
@click="selectSlashCommand(index)"
|
|
785
|
+
>
|
|
786
|
+
<span class="gl-flex gl-justify-between">
|
|
787
|
+
<span class="gl-block">{{ command.name }}</span>
|
|
788
|
+
<small class="gl-pl-3 gl-text-right gl-italic gl-text-subtle">{{
|
|
789
|
+
command.description
|
|
790
|
+
}}</small>
|
|
791
|
+
</span>
|
|
792
|
+
</gl-dropdown-item>
|
|
793
|
+
</gl-card>
|
|
724
794
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
795
|
+
<gl-form-textarea
|
|
796
|
+
ref="prompt"
|
|
797
|
+
v-model="prompt"
|
|
798
|
+
data-testid="chat-prompt-input"
|
|
799
|
+
class="gl-absolute !gl-h-full gl-rounded-br-none gl-rounded-tr-none !gl-bg-transparent !gl-py-4 !gl-shadow-none"
|
|
800
|
+
:class="{ 'gl-truncate': !prompt }"
|
|
801
|
+
:placeholder="inputPlaceholder"
|
|
802
|
+
autofocus
|
|
803
|
+
@keydown.enter.exact.native.prevent
|
|
804
|
+
@keyup.native="onInputKeyup"
|
|
805
|
+
@compositionend="compositionEnd"
|
|
806
|
+
/>
|
|
807
|
+
</div>
|
|
808
|
+
<template #append>
|
|
809
|
+
<gl-button
|
|
810
|
+
v-if="displaySubmitButton"
|
|
811
|
+
icon="paper-airplane"
|
|
812
|
+
category="primary"
|
|
813
|
+
variant="confirm"
|
|
814
|
+
class="!gl-absolute gl-bottom-2 gl-right-2 !gl-rounded-full"
|
|
815
|
+
type="submit"
|
|
816
|
+
data-testid="chat-prompt-submit-button"
|
|
817
|
+
:aria-label="$options.i18n.CHAT_SUBMIT_LABEL"
|
|
818
|
+
/>
|
|
819
|
+
<gl-button
|
|
820
|
+
v-else
|
|
821
|
+
icon="stop"
|
|
822
|
+
category="primary"
|
|
823
|
+
variant="default"
|
|
824
|
+
class="!gl-absolute gl-bottom-2 gl-right-2 !gl-rounded-full"
|
|
825
|
+
data-testid="chat-prompt-cancel-button"
|
|
826
|
+
:aria-label="$options.i18n.CHAT_CANCEL_LABEL"
|
|
827
|
+
@click="cancelPrompt"
|
|
828
|
+
/>
|
|
829
|
+
</template>
|
|
830
|
+
</gl-form-input-group>
|
|
831
|
+
</gl-form>
|
|
832
|
+
<p class="gl-mb-0 gl-mt-3 gl-px-4 gl-text-sm gl-text-secondary">
|
|
833
|
+
{{ $options.i18n.CHAT_DISCLAMER }}
|
|
834
|
+
</p>
|
|
835
|
+
</footer>
|
|
836
|
+
</span>
|
|
766
837
|
</aside>
|
|
767
838
|
</component>
|
|
768
839
|
</template>
|
|
@@ -176,3 +176,86 @@ export const INCLUDE_SLASH_COMMAND = {
|
|
|
176
176
|
name: CHAT_INCLUDE_MESSAGE,
|
|
177
177
|
description: 'Include additional context in the conversation.',
|
|
178
178
|
};
|
|
179
|
+
|
|
180
|
+
export const THREADLIST = [
|
|
181
|
+
{
|
|
182
|
+
__typename: 'AiConversationsThread',
|
|
183
|
+
id: 'gid://gitlab/Ai::Conversation::Thread/14',
|
|
184
|
+
lastUpdatedAt: '2025-01-31T11:12:05Z',
|
|
185
|
+
createdAt: '2025-01-31T11:12:00Z',
|
|
186
|
+
conversationType: 'DUO_CHAT',
|
|
187
|
+
title: 'On the Proper Filing of Form B-789 in Triplicate',
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
__typename: 'AiConversationsThread',
|
|
191
|
+
id: 'gid://gitlab/Ai::Conversation::Thread/13',
|
|
192
|
+
lastUpdatedAt: '2025-01-31T14:30:42Z',
|
|
193
|
+
createdAt: '2025-01-31T14:30:36Z',
|
|
194
|
+
conversationType: 'DUO_CHAT',
|
|
195
|
+
title: 'The Case of the Missing Permission Slip',
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
__typename: 'AiConversationsThread',
|
|
199
|
+
id: 'gid://gitlab/Ai::Conversation::Thread/12',
|
|
200
|
+
lastUpdatedAt: '2025-01-31T14:30:08Z',
|
|
201
|
+
createdAt: '2025-01-31T14:30:03Z',
|
|
202
|
+
conversationType: 'DUO_CHAT',
|
|
203
|
+
title: 'Regarding the Metamorphosis of Code Review #2187',
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
__typename: 'AiConversationsThread',
|
|
207
|
+
id: 'gid://gitlab/Ai::Conversation::Thread/11',
|
|
208
|
+
lastUpdatedAt: '2025-01-24T14:29:51Z',
|
|
209
|
+
createdAt: '2025-01-24T14:29:36Z',
|
|
210
|
+
conversationType: 'DUO_CHAT',
|
|
211
|
+
title: "An Investigation into the Disappearance of Yesterday's Commits",
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
__typename: 'AiConversationsThread',
|
|
215
|
+
id: 'gid://gitlab/Ai::Conversation::Thread/10',
|
|
216
|
+
lastUpdatedAt: '2025-01-24T14:29:25Z',
|
|
217
|
+
createdAt: '2025-01-24T14:28:50Z',
|
|
218
|
+
conversationType: 'DUO_CHAT',
|
|
219
|
+
title: 'The Curious Incident of the Bug in the Runtime',
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
__typename: 'AiConversationsThread',
|
|
223
|
+
id: 'gid://gitlab/Ai::Conversation::Thread/9',
|
|
224
|
+
lastUpdatedAt: '2025-01-15T14:24:30Z',
|
|
225
|
+
createdAt: '2025-01-15T14:24:26Z',
|
|
226
|
+
conversationType: 'DUO_CHAT',
|
|
227
|
+
title: 'Notes on the Inexplicable Behavior of the Legacy System',
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
__typename: 'AiConversationsThread',
|
|
231
|
+
id: 'gid://gitlab/Ai::Conversation::Thread/8',
|
|
232
|
+
lastUpdatedAt: '2025-01-15T14:24:21Z',
|
|
233
|
+
createdAt: '2025-01-15T14:24:17Z',
|
|
234
|
+
conversationType: 'DUO_CHAT',
|
|
235
|
+
title: 'The Trial of the Undefined Variable',
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
__typename: 'AiConversationsThread',
|
|
239
|
+
id: 'gid://gitlab/Ai::Conversation::Thread/7',
|
|
240
|
+
lastUpdatedAt: '2025-01-15T14:12:13Z',
|
|
241
|
+
createdAt: '2025-01-15T14:12:08Z',
|
|
242
|
+
conversationType: 'DUO_CHAT',
|
|
243
|
+
title: 'Waiting for the Database Response',
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
__typename: 'AiConversationsThread',
|
|
247
|
+
id: 'gid://gitlab/Ai::Conversation::Thread/6',
|
|
248
|
+
lastUpdatedAt: '2025-01-08T14:11:46Z',
|
|
249
|
+
createdAt: '2025-01-08T14:11:38Z',
|
|
250
|
+
conversationType: 'DUO_CHAT',
|
|
251
|
+
title: 'The Endless Corridor of Dependencies',
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
__typename: 'AiConversationsThread',
|
|
255
|
+
id: 'gid://gitlab/Ai::Conversation::Thread/5',
|
|
256
|
+
lastUpdatedAt: '2025-01-08T14:11:24Z',
|
|
257
|
+
createdAt: '2025-01-08T14:11:24Z',
|
|
258
|
+
conversationType: 'DUO_CHAT',
|
|
259
|
+
title: 'Before the Configuration Committee',
|
|
260
|
+
},
|
|
261
|
+
];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function getOrdinalSuffix(day) {
|
|
2
|
+
if (day > 3 && day < 21) return 'th';
|
|
3
|
+
switch (day % 10) {
|
|
4
|
+
case 1:
|
|
5
|
+
return 'st';
|
|
6
|
+
case 2:
|
|
7
|
+
return 'nd';
|
|
8
|
+
case 3:
|
|
9
|
+
return 'rd';
|
|
10
|
+
default:
|
|
11
|
+
return 'th';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function formatDate(dateStr) {
|
|
16
|
+
const date = new Date(dateStr);
|
|
17
|
+
const day = date.getDate();
|
|
18
|
+
const suffix = getOrdinalSuffix(day);
|
|
19
|
+
|
|
20
|
+
const month = new Intl.DateTimeFormat('en-US', { month: 'long' }).format(date);
|
|
21
|
+
const year = new Intl.DateTimeFormat('en-US', { year: 'numeric' }).format(date);
|
|
22
|
+
|
|
23
|
+
return `${month} ${day}${suffix}, ${year}`;
|
|
24
|
+
}
|
package/translations.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
export default {
|
|
3
3
|
'DuoChat.chatBackLabel': 'Back to History',
|
|
4
4
|
'DuoChat.chatCancelLabel': 'Cancel',
|
|
5
|
-
'DuoChat.chatCloseLabel': 'Close the Code Explanation',
|
|
6
5
|
'DuoChat.chatDefaultPredefinedPromptsChangePassword': 'How do I change my password in GitLab?',
|
|
7
6
|
'DuoChat.chatDefaultPredefinedPromptsCloneRepository': 'How do I clone a repository?',
|
|
8
7
|
'DuoChat.chatDefaultPredefinedPromptsCreateTemplate': 'How do I create a template?',
|
|
@@ -10,12 +9,20 @@ export default {
|
|
|
10
9
|
'DuoChat.chatDefaultTitle': 'GitLab Duo Chat',
|
|
11
10
|
'DuoChat.chatEmptyStateTitle':
|
|
12
11
|
'👋 I am GitLab Duo Chat, your personal AI-powered assistant. How can I help you today?',
|
|
12
|
+
'DuoChat.chatHistoryInfo': 'Chats with no activity in the last 30 days are deleted.',
|
|
13
|
+
'DuoChat.chatHistoryToolTip': 'Chat History',
|
|
13
14
|
'DuoChat.chatNewLabel': 'New Chat',
|
|
15
|
+
'DuoChat.chatNewToolTip': 'New Chat',
|
|
14
16
|
'DuoChat.chatPromptPlaceholderDefault': 'GitLab Duo Chat',
|
|
15
17
|
'DuoChat.chatPromptPlaceholderWithCommands': 'Type /help to learn more',
|
|
16
18
|
'DuoChat.chatSubmitLabel': 'Send chat message.',
|
|
17
19
|
'DuoChat.chatTitle': 'GitLab Duo Chat',
|
|
18
20
|
'DuoChat.closeChatHeaderLabel': 'Close Chat',
|
|
21
|
+
'DuoChat.emptyHistoryAlt':
|
|
22
|
+
'Clock icon with circular arrow, indicating chat history or time-based functionality',
|
|
23
|
+
'DuoChat.emptyHistoryCopy': 'Your previous chats will appear here.',
|
|
24
|
+
'DuoChat.emptyHistoryTitle': 'See your chat history',
|
|
25
|
+
'DuoChat.threadDeleteLabel': 'Delete this thread.',
|
|
19
26
|
'DuoChatContextItemDetailsModal.contentErrorMessage': 'Item content could not be displayed.',
|
|
20
27
|
'DuoChatContextItemDetailsModal.title': 'Preview',
|
|
21
28
|
'DuoChatContextItemMenu.emptyStateMessage': 'No results found',
|
|
@@ -42,6 +49,7 @@ export default {
|
|
|
42
49
|
'DuoChatMessage.modalTitle': 'Give feedback on GitLab Duo Chat',
|
|
43
50
|
'DuoChatMessageSources.messageSources': null,
|
|
44
51
|
'GlDuoChat.chatDisclamer': 'Responses may be inaccurate. Verify before use.',
|
|
52
|
+
'GlDuoChat.chatHistoryTitle': 'Chat history',
|
|
45
53
|
'GlDuoWorkflowPanel.collapseButtonTitle': 'Collapse',
|
|
46
54
|
'GlDuoWorkflowPanel.expandButtonTitle': 'Expand',
|
|
47
55
|
'GlDuoWorkflowPrompt.cancelButtonText': 'Cancel',
|