@gitlab/duo-ui 10.23.0 → 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.
- package/CHANGELOG.md +7 -0
- package/dist/components/agentic_chat/agentic_duo_chat.js +37 -18
- package/dist/components/chat/components/duo_chat_header/duo_chat_header.js +4 -4
- package/dist/components/chat/duo_chat.js +54 -35
- package/dist/components.css +1 -1
- package/dist/components.css.map +1 -1
- package/dist/index.js +0 -2
- package/dist/tailwind.css +1 -1
- package/dist/tailwind.css.map +1 -1
- package/package.json +1 -2
- package/src/components/agentic_chat/agentic_duo_chat.vue +244 -210
- package/src/components/chat/components/duo_chat_header/duo_chat_header.vue +22 -24
- package/src/components/chat/duo_chat.scss +2 -1
- package/src/components/chat/duo_chat.vue +238 -214
- package/src/index.js +0 -2
- package/translations.js +4 -5
- package/dist/components/ui/duo_layout/duo_layout.js +0 -100
- package/dist/components/ui/side_rail/side_rail.js +0 -67
- package/src/components/ui/duo_layout/duo_layout.md +0 -0
- package/src/components/ui/duo_layout/duo_layout.vue +0 -95
- package/src/components/ui/side_rail/side_rail.vue +0 -56
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import throttle from 'lodash/throttle';
|
|
3
|
+
import VueResizable from 'vue-resizable';
|
|
3
4
|
|
|
4
5
|
import {
|
|
5
6
|
GlButton,
|
|
6
7
|
GlDropdownItem,
|
|
7
8
|
GlCard,
|
|
9
|
+
GlAlert,
|
|
10
|
+
GlFormInputGroup,
|
|
8
11
|
GlFormTextarea,
|
|
9
12
|
GlForm,
|
|
13
|
+
GlExperimentBadge,
|
|
10
14
|
GlSafeHtmlDirective as SafeHtml,
|
|
11
15
|
} from '@gitlab/ui';
|
|
12
16
|
|
|
@@ -34,7 +38,7 @@ export const i18n = {
|
|
|
34
38
|
CHAT_HISTORY_TITLE: translate('AgenticDuoChat.chatHistoryTitle', 'Chat history'),
|
|
35
39
|
CHAT_DISCLAMER: translate(
|
|
36
40
|
'AgenticDuoChat.chatDisclamer',
|
|
37
|
-
'Responses
|
|
41
|
+
'Chat can autonomously change code. Responses and changes can be inaccurate. Review carefully.'
|
|
38
42
|
),
|
|
39
43
|
CHAT_EMPTY_STATE_TITLE: translate(
|
|
40
44
|
'AgenticDuoChat.chatEmptyStateTitle',
|
|
@@ -42,10 +46,6 @@ export const i18n = {
|
|
|
42
46
|
),
|
|
43
47
|
CHAT_PROMPT_PLACEHOLDER_DEFAULT: translate(
|
|
44
48
|
'AgenticDuoChat.chatPromptPlaceholderDefault',
|
|
45
|
-
"Let's work through this together..."
|
|
46
|
-
),
|
|
47
|
-
CHAT_MODEL_PLACEHOLDER: translate(
|
|
48
|
-
'AgenticDuoChat.chatModelPlaceholder',
|
|
49
49
|
'GitLab Duo Agentic Chat'
|
|
50
50
|
),
|
|
51
51
|
CHAT_PROMPT_PLACEHOLDER_WITH_COMMANDS: translate(
|
|
@@ -102,8 +102,11 @@ export default {
|
|
|
102
102
|
name: 'DuoChat',
|
|
103
103
|
components: {
|
|
104
104
|
GlButton,
|
|
105
|
+
GlAlert,
|
|
106
|
+
GlFormInputGroup,
|
|
105
107
|
GlFormTextarea,
|
|
106
108
|
GlForm,
|
|
109
|
+
GlExperimentBadge,
|
|
107
110
|
DuoChatLoader,
|
|
108
111
|
DuoChatPredefinedPrompts,
|
|
109
112
|
DuoChatConversation,
|
|
@@ -111,6 +114,7 @@ export default {
|
|
|
111
114
|
DuoChatThreads,
|
|
112
115
|
GlCard,
|
|
113
116
|
GlDropdownItem,
|
|
117
|
+
VueResizable,
|
|
114
118
|
},
|
|
115
119
|
directives: {
|
|
116
120
|
SafeHtml,
|
|
@@ -339,6 +343,7 @@ export default {
|
|
|
339
343
|
},
|
|
340
344
|
data() {
|
|
341
345
|
return {
|
|
346
|
+
isHidden: false,
|
|
342
347
|
prompt: '',
|
|
343
348
|
scrolledToBottom: true,
|
|
344
349
|
activeCommandIndex: 0,
|
|
@@ -456,6 +461,7 @@ export default {
|
|
|
456
461
|
if (!loading && !this.isStreaming) {
|
|
457
462
|
this.canSubmit = true; // Re-enable submit button when loading stops
|
|
458
463
|
}
|
|
464
|
+
this.isHidden = false;
|
|
459
465
|
},
|
|
460
466
|
isStreaming(streaming) {
|
|
461
467
|
if (!streaming && !this.isLoading) {
|
|
@@ -496,10 +502,14 @@ export default {
|
|
|
496
502
|
this.focusChatInput();
|
|
497
503
|
});
|
|
498
504
|
},
|
|
505
|
+
updateSize(e) {
|
|
506
|
+
this.$emit('chat-resize', e);
|
|
507
|
+
},
|
|
499
508
|
compositionEnd() {
|
|
500
509
|
this.compositionJustEnded = true;
|
|
501
510
|
},
|
|
502
511
|
hideChat() {
|
|
512
|
+
this.isHidden = true;
|
|
503
513
|
/**
|
|
504
514
|
* Emitted when clicking the cross in the title and the chat gets closed.
|
|
505
515
|
*/
|
|
@@ -755,221 +765,245 @@ export default {
|
|
|
755
765
|
};
|
|
756
766
|
</script>
|
|
757
767
|
<template>
|
|
758
|
-
<
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
768
|
+
<component
|
|
769
|
+
:is="shouldRenderResizable ? 'vue-resizable' : 'div'"
|
|
770
|
+
:width="shouldRenderResizable ? dimensions.width : null"
|
|
771
|
+
:height="shouldRenderResizable ? dimensions.height : null"
|
|
772
|
+
:max-width="shouldRenderResizable ? dimensions.maxWidth : null"
|
|
773
|
+
:max-height="shouldRenderResizable ? dimensions.maxHeight : null"
|
|
774
|
+
:min-width="shouldRenderResizable ? dimensions.minWidth : null"
|
|
775
|
+
:left="shouldRenderResizable ? dimensions.left : null"
|
|
776
|
+
:top="shouldRenderResizable ? dimensions.top : null"
|
|
777
|
+
:fit-parent="true"
|
|
778
|
+
:min-height="shouldRenderResizable ? dimensions.minHeight : null"
|
|
779
|
+
:class="{
|
|
780
|
+
'duo-chat-resizable': shouldRenderResizable,
|
|
781
|
+
'non-resizable-wrapper': !shouldRenderResizable,
|
|
782
|
+
}"
|
|
783
|
+
:active="shouldRenderResizable ? ['l', 't', 'lt'] : null"
|
|
784
|
+
@resize:end="updateSize"
|
|
763
785
|
>
|
|
764
|
-
<
|
|
765
|
-
v-if="
|
|
766
|
-
|
|
767
|
-
:
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
:badge-type="isMultithreaded ? null : badgeType"
|
|
775
|
-
:session-id="sessionId"
|
|
776
|
-
:agents="agents"
|
|
777
|
-
@go-back="onGoBack"
|
|
778
|
-
@new-chat="onNewChat"
|
|
779
|
-
@close="hideChat"
|
|
780
|
-
>
|
|
781
|
-
<template #subheader>
|
|
782
|
-
<slot name="subheader"></slot>
|
|
783
|
-
</template>
|
|
784
|
-
</duo-chat-header>
|
|
785
|
-
|
|
786
|
-
<div
|
|
787
|
-
class="gl-flex gl-flex-1 gl-flex-grow gl-flex-col gl-overflow-y-auto gl-overscroll-contain gl-bg-inherit"
|
|
788
|
-
data-testid="chat-history"
|
|
789
|
-
@scroll="handleScrollingThrottled"
|
|
786
|
+
<aside
|
|
787
|
+
v-if="!isHidden"
|
|
788
|
+
id="chat-component"
|
|
789
|
+
:class="{
|
|
790
|
+
'resizable-content': shouldRenderResizable,
|
|
791
|
+
'duo-chat-drawer': !shouldRenderResizable,
|
|
792
|
+
}"
|
|
793
|
+
class="markdown-code-block duo-chat gl-bottom-0 gl-flex gl-max-h-full gl-flex-col"
|
|
794
|
+
role="complementary"
|
|
795
|
+
data-testid="chat-component"
|
|
790
796
|
>
|
|
791
|
-
<duo-chat-
|
|
792
|
-
v-if="
|
|
793
|
-
|
|
794
|
-
:
|
|
797
|
+
<duo-chat-header
|
|
798
|
+
v-if="showHeader"
|
|
799
|
+
ref="header"
|
|
800
|
+
:active-thread-id="activeThreadId"
|
|
801
|
+
:title="
|
|
802
|
+
isMultithreaded && currentView === 'list' ? $options.i18n.CHAT_HISTORY_TITLE : title
|
|
803
|
+
"
|
|
804
|
+
:subtitle="activeThreadTitleForView"
|
|
805
|
+
:error="error"
|
|
806
|
+
:is-multithreaded="isMultithreaded"
|
|
807
|
+
:current-view="currentView"
|
|
808
|
+
:should-render-resizable="shouldRenderResizable"
|
|
809
|
+
:badge-type="isMultithreaded ? null : badgeType"
|
|
810
|
+
:session-id="sessionId"
|
|
811
|
+
:agents="agents"
|
|
812
|
+
@go-back="onGoBack"
|
|
795
813
|
@new-chat="onNewChat"
|
|
796
|
-
@select-thread="onSelectThread"
|
|
797
|
-
@delete-thread="onDeleteThread"
|
|
798
814
|
@close="hideChat"
|
|
799
|
-
/>
|
|
800
|
-
<transition-group
|
|
801
|
-
v-else
|
|
802
|
-
mode="out-in"
|
|
803
|
-
tag="section"
|
|
804
|
-
name="message"
|
|
805
|
-
class="duo-chat-history gl-mt-auto gl-p-5"
|
|
806
815
|
>
|
|
807
|
-
<
|
|
808
|
-
|
|
809
|
-
:key="`conversation-${index}`"
|
|
810
|
-
:enable-code-insertion="enableCodeInsertion"
|
|
811
|
-
:messages="conversation"
|
|
812
|
-
:show-delimiter="index > 0"
|
|
813
|
-
:with-feedback="withFeedback"
|
|
814
|
-
:is-tool-approval-processing="isToolApprovalProcessing"
|
|
815
|
-
:working-directory="workingDirectory"
|
|
816
|
-
@track-feedback="onTrackFeedback"
|
|
817
|
-
@insert-code-snippet="onInsertCodeSnippet"
|
|
818
|
-
@copy-code-snippet="onCopyCodeSnippet"
|
|
819
|
-
@copy-message="onCopyMessage"
|
|
820
|
-
@get-context-item-content="onGetContextItemContent"
|
|
821
|
-
@approve-tool="onApproveToolCall"
|
|
822
|
-
@deny-tool="onDenyToolCall"
|
|
823
|
-
@open-file-path="onOpenFilePath"
|
|
824
|
-
/>
|
|
825
|
-
<template v-if="!hasMessages && !isLoading">
|
|
826
|
-
<div
|
|
827
|
-
key="empty-state-message"
|
|
828
|
-
class="duo-chat-message gl-rounded-bl-none gl-leading-20 gl-text-gray-900 gl-break-anywhere"
|
|
829
|
-
data-testid="gl-duo-chat-empty-state"
|
|
830
|
-
>
|
|
831
|
-
<p v-if="emptyStateTitle" data-testid="gl-duo-chat-empty-state-title" class="gl-m-0">
|
|
832
|
-
{{ emptyStateTitle }}
|
|
833
|
-
</p>
|
|
834
|
-
<duo-chat-predefined-prompts
|
|
835
|
-
key="predefined-prompts"
|
|
836
|
-
:prompts="predefinedPrompts"
|
|
837
|
-
@click="sendPredefinedPrompt"
|
|
838
|
-
/>
|
|
839
|
-
</div>
|
|
816
|
+
<template #subheader>
|
|
817
|
+
<slot name="subheader"></slot>
|
|
840
818
|
</template>
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
819
|
+
</duo-chat-header>
|
|
820
|
+
|
|
821
|
+
<div
|
|
822
|
+
class="gl-flex gl-flex-1 gl-flex-grow gl-flex-col gl-overflow-y-auto gl-overscroll-contain gl-bg-inherit"
|
|
823
|
+
data-testid="chat-history"
|
|
824
|
+
@scroll="handleScrollingThrottled"
|
|
825
|
+
>
|
|
826
|
+
<duo-chat-threads
|
|
827
|
+
v-if="shouldShowThreadList"
|
|
828
|
+
:threads="threadList"
|
|
829
|
+
:preferred-locale="preferredLocale"
|
|
830
|
+
@new-chat="onNewChat"
|
|
831
|
+
@select-thread="onSelectThread"
|
|
832
|
+
@delete-thread="onDeleteThread"
|
|
833
|
+
@close="hideChat"
|
|
834
|
+
/>
|
|
835
|
+
<transition-group
|
|
836
|
+
v-else
|
|
837
|
+
mode="out-in"
|
|
838
|
+
tag="section"
|
|
839
|
+
name="message"
|
|
840
|
+
class="duo-chat-history gl-mt-auto gl-p-5"
|
|
841
|
+
>
|
|
842
|
+
<duo-chat-conversation
|
|
843
|
+
v-for="(conversation, index) in conversations"
|
|
844
|
+
:key="`conversation-${index}`"
|
|
845
|
+
:enable-code-insertion="enableCodeInsertion"
|
|
846
|
+
:messages="conversation"
|
|
847
|
+
:show-delimiter="index > 0"
|
|
848
|
+
:with-feedback="withFeedback"
|
|
849
|
+
:is-tool-approval-processing="isToolApprovalProcessing"
|
|
850
|
+
:working-directory="workingDirectory"
|
|
851
|
+
@track-feedback="onTrackFeedback"
|
|
852
|
+
@insert-code-snippet="onInsertCodeSnippet"
|
|
853
|
+
@copy-code-snippet="onCopyCodeSnippet"
|
|
854
|
+
@copy-message="onCopyMessage"
|
|
855
|
+
@get-context-item-content="onGetContextItemContent"
|
|
856
|
+
@approve-tool="onApproveToolCall"
|
|
857
|
+
@deny-tool="onDenyToolCall"
|
|
858
|
+
@open-file-path="onOpenFilePath"
|
|
859
|
+
/>
|
|
860
|
+
<template v-if="!hasMessages && !isLoading">
|
|
861
|
+
<div
|
|
862
|
+
key="empty-state-message"
|
|
863
|
+
class="duo-chat-message gl-rounded-bl-none gl-leading-20 gl-text-gray-900 gl-break-anywhere"
|
|
864
|
+
data-testid="gl-duo-chat-empty-state"
|
|
865
|
+
>
|
|
866
|
+
<p v-if="emptyStateTitle" data-testid="gl-duo-chat-empty-state-title" class="gl-m-0">
|
|
867
|
+
{{ emptyStateTitle }}
|
|
868
|
+
</p>
|
|
869
|
+
<duo-chat-predefined-prompts
|
|
870
|
+
key="predefined-prompts"
|
|
871
|
+
:prompts="predefinedPrompts"
|
|
872
|
+
@click="sendPredefinedPrompt"
|
|
873
|
+
/>
|
|
874
|
+
</div>
|
|
875
|
+
</template>
|
|
876
|
+
<duo-chat-loader v-if="isLoading" key="loader" :tool-name="toolName" />
|
|
877
|
+
<div key="anchor" ref="anchor" class="scroll-anchor"></div>
|
|
878
|
+
</transition-group>
|
|
879
|
+
</div>
|
|
880
|
+
<footer
|
|
881
|
+
v-if="isChatAvailable && !shouldShowThreadList"
|
|
882
|
+
data-testid="chat-footer"
|
|
883
|
+
class="duo-chat-drawer-footer gl-relative gl-z-2 gl-shrink-0 gl-border-0 gl-bg-default gl-pb-3"
|
|
884
|
+
:class="{ 'duo-chat-drawer-body-scrim-on-footer': !scrolledToBottom }"
|
|
885
|
+
>
|
|
886
|
+
<gl-form data-testid="chat-prompt-form" @submit.stop.prevent="sendChatPrompt">
|
|
887
|
+
<div class="gl-relative gl-max-w-full">
|
|
888
|
+
<!--
|
|
854
889
|
@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" ...`
|
|
855
890
|
-->
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
</div>
|
|
864
|
-
|
|
865
|
-
<div
|
|
866
|
-
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"
|
|
867
|
-
>
|
|
868
|
-
<div
|
|
869
|
-
class="gl-flex gl-justify-between gl-border-0 gl-border-b-1 gl-border-solid gl-border-[#DCDCDE] gl-px-4 gl-py-4"
|
|
870
|
-
>
|
|
871
|
-
<div>{{ $options.i18n.CHAT_MODEL_PLACEHOLDER }}</div>
|
|
872
|
-
<div><slot name="agentic-switch"></slot></div>
|
|
891
|
+
<slot
|
|
892
|
+
name="context-items-menu"
|
|
893
|
+
:is-open="contextItemsMenuIsOpen"
|
|
894
|
+
:on-close="closeContextItemsMenuOpen"
|
|
895
|
+
:set-ref="setContextItemsMenuRef"
|
|
896
|
+
:focus-prompt="focusChatInput"
|
|
897
|
+
></slot>
|
|
873
898
|
</div>
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
body-class="!gl-p-2"
|
|
899
|
+
|
|
900
|
+
<gl-form-input-group>
|
|
901
|
+
<div
|
|
902
|
+
class="duo-chat-input gl-min-h-8 gl-max-w-full gl-grow gl-align-top"
|
|
903
|
+
:data-value="prompt"
|
|
880
904
|
>
|
|
881
|
-
<gl-
|
|
882
|
-
v-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
@click="selectSlashCommand(index)"
|
|
905
|
+
<gl-card
|
|
906
|
+
v-if="shouldShowSlashCommands"
|
|
907
|
+
ref="commands"
|
|
908
|
+
class="slash-commands !gl-absolute gl-w-full -gl-translate-y-full gl-list-none gl-pl-0 gl-shadow-md"
|
|
909
|
+
body-class="!gl-p-2"
|
|
887
910
|
>
|
|
888
|
-
<
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
</gl-dropdown-item>
|
|
895
|
-
</gl-card>
|
|
896
|
-
|
|
897
|
-
<gl-form-textarea
|
|
898
|
-
ref="prompt"
|
|
899
|
-
v-model="prompt"
|
|
900
|
-
:disabled="!canSubmit"
|
|
901
|
-
data-testid="chat-prompt-input"
|
|
902
|
-
:placeholder="inputPlaceholder"
|
|
903
|
-
:character-count-limit="maxPromptLength"
|
|
904
|
-
:textarea-classes="[
|
|
905
|
-
'!gl-h-full',
|
|
906
|
-
'!gl-bg-transparent',
|
|
907
|
-
'!gl-py-4',
|
|
908
|
-
'!gl-shadow-none',
|
|
909
|
-
'form-control',
|
|
910
|
-
'gl-form-input',
|
|
911
|
-
'gl-form-textarea',
|
|
912
|
-
{ 'gl-truncate': !prompt },
|
|
913
|
-
]"
|
|
914
|
-
aria-label="Chat prompt input"
|
|
915
|
-
autofocus
|
|
916
|
-
@keydown.enter.exact.native.prevent
|
|
917
|
-
@keydown.ctrl.z.exact="handleUndo"
|
|
918
|
-
@keydown.meta.z.exact="handleUndo"
|
|
919
|
-
@keydown.ctrl.shift.z.exact="handleRedo"
|
|
920
|
-
@keydown.meta.shift.z.exact="handleRedo"
|
|
921
|
-
@keydown.ctrl.y.exact="handleRedo"
|
|
922
|
-
@keydown.meta.y.exact="handleRedo"
|
|
923
|
-
@keyup.native="onInputKeyup"
|
|
924
|
-
@compositionend="compositionEnd"
|
|
925
|
-
>
|
|
926
|
-
<template #remaining-character-count-text="{ count }">
|
|
927
|
-
<span
|
|
928
|
-
v-if="count <= promptLengthWarningCount"
|
|
929
|
-
class="gl-absolute gl-bottom-[-25px] gl-right-px gl-pr-3"
|
|
911
|
+
<gl-dropdown-item
|
|
912
|
+
v-for="(command, index) in filteredSlashCommands"
|
|
913
|
+
:key="command.name"
|
|
914
|
+
:class="{ 'active-command': index === activeCommandIndex }"
|
|
915
|
+
@mouseenter.native="activeCommandIndex = index"
|
|
916
|
+
@click="selectSlashCommand(index)"
|
|
930
917
|
>
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
</
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
918
|
+
<span class="gl-flex gl-justify-between">
|
|
919
|
+
<span class="gl-block">{{ command.name }}</span>
|
|
920
|
+
<small class="gl-pl-3 gl-text-right gl-italic gl-text-subtle">{{
|
|
921
|
+
command.description
|
|
922
|
+
}}</small>
|
|
923
|
+
</span>
|
|
924
|
+
</gl-dropdown-item>
|
|
925
|
+
</gl-card>
|
|
926
|
+
|
|
927
|
+
<gl-form-textarea
|
|
928
|
+
ref="prompt"
|
|
929
|
+
v-model="prompt"
|
|
930
|
+
:disabled="!canSubmit"
|
|
931
|
+
data-testid="chat-prompt-input"
|
|
932
|
+
:placeholder="inputPlaceholder"
|
|
933
|
+
:character-count-limit="maxPromptLength"
|
|
934
|
+
:textarea-classes="[
|
|
935
|
+
'gl-absolute',
|
|
936
|
+
'!gl-h-full',
|
|
937
|
+
'gl-rounded-br-none',
|
|
938
|
+
'gl-rounded-tr-none',
|
|
939
|
+
'!gl-bg-transparent',
|
|
940
|
+
'!gl-py-4',
|
|
941
|
+
'!gl-shadow-none',
|
|
942
|
+
'form-control',
|
|
943
|
+
'gl-form-input',
|
|
944
|
+
'gl-form-textarea',
|
|
945
|
+
{ 'gl-truncate': !prompt },
|
|
946
|
+
]"
|
|
947
|
+
aria-label="Chat prompt input"
|
|
948
|
+
autofocus
|
|
949
|
+
@keydown.enter.exact.native.prevent
|
|
950
|
+
@keydown.ctrl.z.exact="handleUndo"
|
|
951
|
+
@keydown.meta.z.exact="handleUndo"
|
|
952
|
+
@keydown.ctrl.shift.z.exact="handleRedo"
|
|
953
|
+
@keydown.meta.shift.z.exact="handleRedo"
|
|
954
|
+
@keydown.ctrl.y.exact="handleRedo"
|
|
955
|
+
@keydown.meta.y.exact="handleRedo"
|
|
956
|
+
@keyup.native="onInputKeyup"
|
|
957
|
+
@compositionend="compositionEnd"
|
|
958
|
+
>
|
|
959
|
+
<template #remaining-character-count-text="{ count }">
|
|
960
|
+
<span
|
|
961
|
+
v-if="count <= promptLengthWarningCount"
|
|
962
|
+
class="gl-absolute gl-bottom-[-25px] gl-right-px gl-pr-3"
|
|
963
|
+
>
|
|
964
|
+
{{ remainingCharacterCountMessage(count) }}
|
|
965
|
+
</span>
|
|
966
|
+
</template>
|
|
967
|
+
<template #character-count-over-limit-text="{ count }">
|
|
968
|
+
<span class="gl-absolute gl-bottom-[-25px] gl-right-px gl-pr-3">{{
|
|
969
|
+
overLimitCharacterCountMessage(count)
|
|
970
|
+
}}</span>
|
|
971
|
+
</template>
|
|
972
|
+
</gl-form-textarea>
|
|
973
|
+
</div>
|
|
974
|
+
<template #append>
|
|
975
|
+
<gl-button
|
|
976
|
+
v-if="canSubmit"
|
|
977
|
+
icon="paper-airplane"
|
|
978
|
+
category="primary"
|
|
979
|
+
variant="confirm"
|
|
980
|
+
class="!gl-absolute gl-bottom-2 gl-right-2 !gl-rounded-full"
|
|
981
|
+
type="submit"
|
|
982
|
+
:disabled="isPromptEmpty || !hasValidPrompt"
|
|
983
|
+
data-testid="chat-prompt-submit-button"
|
|
984
|
+
:aria-label="$options.i18n.CHAT_SUBMIT_LABEL"
|
|
985
|
+
/>
|
|
986
|
+
<gl-button
|
|
987
|
+
v-else
|
|
988
|
+
icon="stop"
|
|
989
|
+
category="primary"
|
|
990
|
+
variant="default"
|
|
991
|
+
class="!gl-absolute gl-bottom-2 gl-right-2 !gl-rounded-full"
|
|
992
|
+
data-testid="chat-prompt-cancel-button"
|
|
993
|
+
:aria-label="$options.i18n.CHAT_CANCEL_LABEL"
|
|
994
|
+
@click="cancelPrompt"
|
|
995
|
+
/>
|
|
996
|
+
</template>
|
|
997
|
+
</gl-form-input-group>
|
|
998
|
+
</gl-form>
|
|
999
|
+
<slot name="footer-controls"></slot>
|
|
1000
|
+
<p
|
|
1001
|
+
class="gl-mb-0 gl-mt-3 gl-px-4 gl-text-sm gl-text-secondary"
|
|
1002
|
+
:class="{ 'gl-mt-6 sm:gl-mt-3 sm:gl-max-w-1/2': prompt.length >= maxPromptLengthWarning }"
|
|
1003
|
+
>
|
|
1004
|
+
{{ $options.i18n.CHAT_DISCLAMER }}
|
|
1005
|
+
</p>
|
|
1006
|
+
</footer>
|
|
1007
|
+
</aside>
|
|
1008
|
+
</component>
|
|
975
1009
|
</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
|
|
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-
|
|
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
|
}
|