@gitlab/duo-ui 10.20.0 → 10.22.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 (37) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/components/agentic_chat/agentic_duo_chat.js +18 -37
  3. package/dist/components/chat/components/duo_chat_header/duo_chat_header.js +4 -4
  4. package/dist/components/chat/components/duo_chat_message_tool_approval/components/create_commit_tool_params.js +148 -0
  5. package/dist/components/chat/components/duo_chat_message_tool_approval/components/create_issue_tool_params.js +88 -0
  6. package/dist/components/chat/components/duo_chat_message_tool_approval/components/create_merge_request_tool_params.js +83 -0
  7. package/dist/components/chat/components/duo_chat_message_tool_approval/components/pre_block.js +38 -0
  8. package/dist/components/chat/components/duo_chat_message_tool_approval/components/run_command_tool_params.js +62 -0
  9. package/dist/components/chat/components/duo_chat_message_tool_approval/message_tool_approval.js +46 -8
  10. package/dist/components/chat/duo_chat.js +35 -54
  11. package/dist/components/chat/mock_data.js +85 -1
  12. package/dist/components/ui/duo_layout/duo_layout.js +100 -0
  13. package/dist/components/ui/side_rail/side_rail.js +67 -0
  14. package/dist/components.css +1 -1
  15. package/dist/components.css.map +1 -1
  16. package/dist/index.js +2 -0
  17. package/dist/tailwind.css +1 -1
  18. package/dist/tailwind.css.map +1 -1
  19. package/dist/utils/object.js +9 -0
  20. package/package.json +5 -4
  21. package/src/components/agentic_chat/agentic_duo_chat.vue +210 -244
  22. package/src/components/chat/components/duo_chat_header/duo_chat_header.vue +24 -22
  23. package/src/components/chat/components/duo_chat_message_tool_approval/components/create_commit_tool_params.vue +155 -0
  24. package/src/components/chat/components/duo_chat_message_tool_approval/components/create_issue_tool_params.vue +80 -0
  25. package/src/components/chat/components/duo_chat_message_tool_approval/components/create_merge_request_tool_params.vue +74 -0
  26. package/src/components/chat/components/duo_chat_message_tool_approval/components/pre_block.vue +5 -0
  27. package/src/components/chat/components/duo_chat_message_tool_approval/components/run_command_tool_params.vue +30 -0
  28. package/src/components/chat/components/duo_chat_message_tool_approval/message_tool_approval.vue +143 -88
  29. package/src/components/chat/duo_chat.scss +1 -2
  30. package/src/components/chat/duo_chat.vue +214 -238
  31. package/src/components/chat/mock_data.js +99 -0
  32. package/src/components/ui/duo_layout/duo_layout.md +0 -0
  33. package/src/components/ui/duo_layout/duo_layout.vue +95 -0
  34. package/src/components/ui/side_rail/side_rail.vue +56 -0
  35. package/src/index.js +2 -0
  36. package/src/utils/object.js +4 -0
  37. package/translations.js +29 -6
@@ -1,16 +1,12 @@
1
1
  <script>
2
2
  import throttle from 'lodash/throttle';
3
- import VueResizable from 'vue-resizable';
4
3
 
5
4
  import {
6
5
  GlButton,
7
6
  GlDropdownItem,
8
7
  GlCard,
9
- GlAlert,
10
- GlFormInputGroup,
11
8
  GlFormTextarea,
12
9
  GlForm,
13
- GlExperimentBadge,
14
10
  GlSafeHtmlDirective as SafeHtml,
15
11
  } from '@gitlab/ui';
16
12
 
@@ -38,7 +34,7 @@ export const i18n = {
38
34
  CHAT_HISTORY_TITLE: translate('AgenticDuoChat.chatHistoryTitle', 'Chat history'),
39
35
  CHAT_DISCLAMER: translate(
40
36
  'AgenticDuoChat.chatDisclamer',
41
- 'Chat can autonomously change code. Responses and changes can be inaccurate. Review carefully.'
37
+ 'Responses may be inaccurate. Verify before use.'
42
38
  ),
43
39
  CHAT_EMPTY_STATE_TITLE: translate(
44
40
  'AgenticDuoChat.chatEmptyStateTitle',
@@ -46,6 +42,10 @@ export const i18n = {
46
42
  ),
47
43
  CHAT_PROMPT_PLACEHOLDER_DEFAULT: translate(
48
44
  '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,11 +102,8 @@ export default {
102
102
  name: 'DuoChat',
103
103
  components: {
104
104
  GlButton,
105
- GlAlert,
106
- GlFormInputGroup,
107
105
  GlFormTextarea,
108
106
  GlForm,
109
- GlExperimentBadge,
110
107
  DuoChatLoader,
111
108
  DuoChatPredefinedPrompts,
112
109
  DuoChatConversation,
@@ -114,7 +111,6 @@ export default {
114
111
  DuoChatThreads,
115
112
  GlCard,
116
113
  GlDropdownItem,
117
- VueResizable,
118
114
  },
119
115
  directives: {
120
116
  SafeHtml,
@@ -343,7 +339,6 @@ export default {
343
339
  },
344
340
  data() {
345
341
  return {
346
- isHidden: false,
347
342
  prompt: '',
348
343
  scrolledToBottom: true,
349
344
  activeCommandIndex: 0,
@@ -461,7 +456,6 @@ export default {
461
456
  if (!loading && !this.isStreaming) {
462
457
  this.canSubmit = true; // Re-enable submit button when loading stops
463
458
  }
464
- this.isHidden = false;
465
459
  },
466
460
  isStreaming(streaming) {
467
461
  if (!streaming && !this.isLoading) {
@@ -502,14 +496,10 @@ export default {
502
496
  this.focusChatInput();
503
497
  });
504
498
  },
505
- updateSize(e) {
506
- this.$emit('chat-resize', e);
507
- },
508
499
  compositionEnd() {
509
500
  this.compositionJustEnded = true;
510
501
  },
511
502
  hideChat() {
512
- this.isHidden = true;
513
503
  /**
514
504
  * Emitted when clicking the cross in the title and the chat gets closed.
515
505
  */
@@ -765,245 +755,221 @@ export default {
765
755
  };
766
756
  </script>
767
757
  <template>
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"
758
+ <div
759
+ id="chat-component"
760
+ class="markdown-code-block duo-chat gl-bottom-0 gl-flex gl-max-h-full gl-flex-col"
761
+ role="complementary"
762
+ data-testid="chat-component"
785
763
  >
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"
764
+ <duo-chat-header
765
+ v-if="showHeader"
766
+ ref="header"
767
+ :active-thread-id="activeThreadId"
768
+ :title="isMultithreaded && currentView === 'list' ? $options.i18n.CHAT_HISTORY_TITLE : title"
769
+ :subtitle="activeThreadTitleForView"
770
+ :error="error"
771
+ :is-multithreaded="isMultithreaded"
772
+ :current-view="currentView"
773
+ :should-render-resizable="shouldRenderResizable"
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"
796
790
  >
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"
791
+ <duo-chat-threads
792
+ v-if="shouldShowThreadList"
793
+ :threads="threadList"
794
+ :preferred-locale="preferredLocale"
813
795
  @new-chat="onNewChat"
796
+ @select-thread="onSelectThread"
797
+ @delete-thread="onDeleteThread"
814
798
  @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"
815
806
  >
816
- <template #subheader>
817
- <slot name="subheader"></slot>
818
- </template>
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"
807
+ <duo-chat-conversation
808
+ v-for="(conversation, index) in conversations"
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"
834
824
  />
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
- <!--
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>
840
+ </template>
841
+ <duo-chat-loader v-if="isLoading" key="loader" :tool-name="toolName" />
842
+ <div key="anchor" ref="anchor" class="scroll-anchor"></div>
843
+ </transition-group>
844
+ </div>
845
+ <footer
846
+ v-if="isChatAvailable && !shouldShowThreadList"
847
+ data-testid="chat-footer"
848
+ class="duo-chat-drawer-footer gl-relative gl-z-2 gl-shrink-0 gl-border-0 gl-bg-default gl-pb-3"
849
+ :class="{ 'duo-chat-drawer-body-scrim-on-footer': !scrolledToBottom }"
850
+ >
851
+ <gl-form data-testid="chat-prompt-form" @submit.stop.prevent="sendChatPrompt">
852
+ <div class="gl-relative gl-max-w-full">
853
+ <!--
889
854
  @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" ...`
890
855
  -->
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>
856
+ <slot
857
+ name="context-items-menu"
858
+ :is-open="contextItemsMenuIsOpen"
859
+ :on-close="closeContextItemsMenuOpen"
860
+ :set-ref="setContextItemsMenuRef"
861
+ :focus-prompt="focusChatInput"
862
+ ></slot>
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>
898
873
  </div>
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"
874
+ <div :data-value="prompt" class="gl-h-[40px] gl-grow">
875
+ <gl-card
876
+ v-if="shouldShowSlashCommands"
877
+ ref="commands"
878
+ class="slash-commands !gl-absolute gl-w-full -gl-translate-y-full gl-list-none gl-pl-0 gl-shadow-md"
879
+ body-class="!gl-p-2"
904
880
  >
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"
881
+ <gl-dropdown-item
882
+ v-for="(command, index) in filteredSlashCommands"
883
+ :key="command.name"
884
+ :class="{ 'active-command': index === activeCommandIndex }"
885
+ @mouseenter.native="activeCommandIndex = index"
886
+ @click="selectSlashCommand(index)"
910
887
  >
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)"
888
+ <span class="gl-flex gl-justify-between">
889
+ <span class="gl-block">{{ command.name }}</span>
890
+ <small class="gl-pl-3 gl-text-right gl-italic gl-text-subtle">{{
891
+ command.description
892
+ }}</small>
893
+ </span>
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"
917
930
  >
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>
931
+ {{ remainingCharacterCountMessage(count) }}
932
+ </span>
933
+ </template>
934
+ <template #character-count-over-limit-text="{ count }">
935
+ <span class="gl-absolute gl-bottom-[-25px] gl-right-px gl-pr-3">{{
936
+ overLimitCharacterCountMessage(count)
937
+ }}</span>
938
+ </template>
939
+ </gl-form-textarea>
940
+ </div>
941
+ <div class="gl-flex gl-justify-end gl-px-3 gl-pb-3">
942
+ <gl-button
943
+ v-if="canSubmit"
944
+ icon="paper-airplane"
945
+ category="primary"
946
+ variant="confirm"
947
+ class="gl-bottom-2 gl-right-2 gl-ml-auto !gl-rounded-full"
948
+ type="submit"
949
+ :disabled="isPromptEmpty || !hasValidPrompt"
950
+ data-testid="chat-prompt-submit-button"
951
+ :aria-label="$options.i18n.CHAT_SUBMIT_LABEL"
952
+ />
953
+ <gl-button
954
+ v-else
955
+ icon="stop"
956
+ category="primary"
957
+ variant="default"
958
+ class="gl-bottom-2 gl-right-2 !gl-rounded-full"
959
+ data-testid="chat-prompt-cancel-button"
960
+ :aria-label="$options.i18n.CHAT_CANCEL_LABEL"
961
+ @click="cancelPrompt"
962
+ />
963
+ </div>
964
+ </div>
965
+ </gl-form>
966
+ <slot name="footer-controls"></slot>
967
+ <p
968
+ class="gl-mb-0 gl-mt-3 gl-px-4 gl-text-sm gl-text-secondary"
969
+ :class="{ 'gl-mt-6 sm:gl-mt-3 sm:gl-max-w-1/2': prompt.length >= maxPromptLengthWarning }"
970
+ >
971
+ {{ $options.i18n.CHAT_DISCLAMER }}
972
+ </p>
973
+ </footer>
974
+ </div>
1009
975
  </template>
@@ -3,10 +3,10 @@ import Vue from 'vue';
3
3
  import {
4
4
  GlAlert,
5
5
  GlBadge,
6
+ GlAvatar,
6
7
  GlButton,
7
8
  GlDropdown,
8
9
  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,
46
47
  GlButton,
47
48
  GlDropdown,
48
49
  GlDropdownItem,
49
- GlExperimentBadge,
50
50
  GlDisclosureDropdown,
51
51
  },
52
52
  directives: {
@@ -140,19 +140,31 @@ 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>
143
162
  <div class="drawer-title gl-flex gl-items-center gl-justify-start gl-p-5">
144
- <div class="gl-flex-1 gl-overflow-hidden">
163
+ <div class="gl-flex gl-flex-1 gl-overflow-hidden">
164
+ <gl-avatar :size="32" :entity-name="title" shape="circle" class="gl-mr-3" />
145
165
  <div class="gl-flex gl-items-center">
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" />
166
+ <h3 class="gl-my-0 gl-text-[0.875rem]">{{ title }}</h3>
148
167
  </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>
156
168
  </div>
157
169
 
158
170
  <div class="gl-flex gl-gap-3">
@@ -242,16 +254,6 @@ export default {
242
254
  </span>
243
255
  </gl-dropdown-item>
244
256
  </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
- />
255
257
  </div>
256
258
  </div>
257
259