@gitlab/duo-ui 10.23.0 → 11.0.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 +23 -0
- package/dist/components/agentic_chat/agentic_duo_chat.js +37 -18
- package/dist/components/agentic_chat/web_agentic_duo_chat.js +725 -0
- package/dist/components/chat/components/duo_chat_header/duo_chat_header.js +4 -4
- package/dist/components/chat/components/duo_chat_header/web_duo_chat_header.js +161 -0
- package/dist/components/chat/duo_chat.js +54 -35
- package/dist/components/chat/web_duo_chat.js +658 -0
- package/dist/components/ui/duo_layout/duo_layout.js +1 -1
- package/dist/components.css +1 -1
- package/dist/components.css.map +1 -1
- package/dist/index.js +3 -1
- package/dist/tailwind.css +1 -1
- package/dist/tailwind.css.map +1 -1
- package/package.json +1 -1
- package/src/components/agentic_chat/agentic_duo_chat.vue +244 -210
- package/src/components/agentic_chat/web_agentic_duo_chat.vue +978 -0
- package/src/components/chat/components/duo_chat_header/duo_chat_header.vue +22 -24
- package/src/components/chat/components/duo_chat_header/web_duo_chat_header.vue +274 -0
- package/src/components/chat/duo_chat.scss +2 -1
- package/src/components/chat/duo_chat.vue +238 -214
- package/src/components/chat/web_duo_chat.vue +891 -0
- package/src/components/ui/duo_layout/duo_layout.vue +2 -2
- package/src/index.js +5 -2
- package/translations.js +46 -5
|
@@ -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>
|