@gitlab/duo-ui 15.4.2 → 15.5.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 +14 -0
- package/dist/components/agentic_chat/agentic_duo_chat.js +44 -14
- package/dist/components/chat/components/duo_chat_threads/duo_chat_threads.js +1 -1
- package/package.json +1 -1
- package/src/components/agentic_chat/agentic_duo_chat.vue +42 -8
- package/src/components/chat/components/duo_chat_threads/duo_chat_threads.vue +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [15.5.1](https://gitlab.com/gitlab-org/duo-ui/compare/v15.5.0...v15.5.1) (2026-01-13)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* Fix chat autoscroll ([33b6d85](https://gitlab.com/gitlab-org/duo-ui/commit/33b6d8579a4819983b86d2557b94d599d71d1dea))
|
|
7
|
+
|
|
8
|
+
# [15.5.0](https://gitlab.com/gitlab-org/duo-ui/compare/v15.4.2...v15.5.0) (2026-01-09)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **DuoChat:** Display agent name as subtitle in chat history ([69caff5](https://gitlab.com/gitlab-org/duo-ui/commit/69caff5a584359fea90ba90e0b045eb8da8d4791))
|
|
14
|
+
|
|
1
15
|
## [15.4.2](https://gitlab.com/gitlab-org/duo-ui/compare/v15.4.1...v15.4.2) (2026-01-06)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -41,6 +41,11 @@ const localeValidator = value => {
|
|
|
41
41
|
return false;
|
|
42
42
|
}
|
|
43
43
|
};
|
|
44
|
+
const scrollWithNewContent = element => {
|
|
45
|
+
// Add 20px tolerance for floating point precision and unintentional micro-scrolls
|
|
46
|
+
// otherwise even a tiny scroll up will prevent autoscroll
|
|
47
|
+
return element.scrollTop + element.offsetHeight >= element.scrollHeight - 20;
|
|
48
|
+
};
|
|
44
49
|
var script = {
|
|
45
50
|
name: 'DuoChat',
|
|
46
51
|
components: {
|
|
@@ -320,7 +325,7 @@ var script = {
|
|
|
320
325
|
return {
|
|
321
326
|
isHidden: false,
|
|
322
327
|
prompt: '',
|
|
323
|
-
|
|
328
|
+
scrollWithNewContent: true,
|
|
324
329
|
activeCommandIndex: 0,
|
|
325
330
|
hasValidPrompt: true,
|
|
326
331
|
compositionJustEnded: false,
|
|
@@ -441,10 +446,19 @@ var script = {
|
|
|
441
446
|
this.isHidden = false;
|
|
442
447
|
},
|
|
443
448
|
lastMessage(newMessage) {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
//
|
|
447
|
-
this.
|
|
449
|
+
// Always scroll when user sends a message
|
|
450
|
+
if ((newMessage === null || newMessage === void 0 ? void 0 : newMessage.role.toLowerCase()) === MESSAGE_MODEL_ROLES.user) {
|
|
451
|
+
// Reset scrollWithNewContent so ResizeObserver will scroll during streaming
|
|
452
|
+
this.scrollWithNewContent = true;
|
|
453
|
+
this.scrollToBottomAsync();
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
'lastMessage.content': {
|
|
457
|
+
handler() {
|
|
458
|
+
// Scroll when assistant message content is updated (streaming)
|
|
459
|
+
if (this.scrollWithNewContent) {
|
|
460
|
+
this.scrollToBottomAsync();
|
|
461
|
+
}
|
|
448
462
|
}
|
|
449
463
|
},
|
|
450
464
|
shouldShowSlashCommands(shouldShow) {
|
|
@@ -460,7 +474,22 @@ var script = {
|
|
|
460
474
|
this.handleScrollingThrottled = throttle(this.handleScrolling, 200); // Assume a 200ms throttle for example
|
|
461
475
|
},
|
|
462
476
|
mounted() {
|
|
463
|
-
|
|
477
|
+
var _this$$refs$chatConte;
|
|
478
|
+
const contentElement = (_this$$refs$chatConte = this.$refs.chatContent) === null || _this$$refs$chatConte === void 0 ? void 0 : _this$$refs$chatConte.$el;
|
|
479
|
+
if (contentElement) {
|
|
480
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
481
|
+
// Only scroll if user has not scrolled up (respect scroll position during streaming)
|
|
482
|
+
if (this.scrollWithNewContent) {
|
|
483
|
+
this.scrollToBottomAsync();
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
this.resizeObserver.observe(contentElement);
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
beforeUnmount() {
|
|
490
|
+
var _this$resizeObserver;
|
|
491
|
+
(_this$resizeObserver = this.resizeObserver) === null || _this$resizeObserver === void 0 ? void 0 : _this$resizeObserver.disconnect();
|
|
492
|
+
this.resizeObserver = null;
|
|
464
493
|
},
|
|
465
494
|
methods: {
|
|
466
495
|
onGoBack() {
|
|
@@ -522,17 +551,18 @@ var script = {
|
|
|
522
551
|
this.sendChatPrompt();
|
|
523
552
|
},
|
|
524
553
|
handleScrolling(event) {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
554
|
+
this.scrollWithNewContent = scrollWithNewContent(event.target);
|
|
555
|
+
},
|
|
556
|
+
scrollToBottomAsync() {
|
|
557
|
+
requestAnimationFrame(() => {
|
|
558
|
+
this.scrollToBottom();
|
|
559
|
+
});
|
|
531
560
|
},
|
|
532
561
|
async scrollToBottom() {
|
|
533
562
|
var _this$$refs$anchor, _this$$refs$anchor$sc;
|
|
534
563
|
await this.$nextTick();
|
|
535
564
|
(_this$$refs$anchor = this.$refs.anchor) === null || _this$$refs$anchor === void 0 ? void 0 : (_this$$refs$anchor$sc = _this$$refs$anchor.scrollIntoView) === null || _this$$refs$anchor$sc === void 0 ? void 0 : _this$$refs$anchor$sc.call(_this$$refs$anchor);
|
|
565
|
+
this.scrollWithNewContent = true;
|
|
536
566
|
},
|
|
537
567
|
focusChatInput() {
|
|
538
568
|
var _this$$refs$prompt, _this$$refs$prompt$$e, _this$$refs$prompt$$e2, _this$$refs$prompt$$e3;
|
|
@@ -740,7 +770,7 @@ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=
|
|
|
740
770
|
},attrs:{"width":_vm.shouldRenderResizable ? _vm.dimensions.width : null,"height":_vm.shouldRenderResizable ? _vm.dimensions.height : null,"max-width":_vm.shouldRenderResizable ? _vm.dimensions.maxWidth : null,"max-height":_vm.shouldRenderResizable ? _vm.dimensions.maxHeight : null,"min-width":_vm.shouldRenderResizable ? _vm.dimensions.minWidth : null,"left":_vm.shouldRenderResizable ? _vm.dimensions.left : null,"top":_vm.shouldRenderResizable ? _vm.dimensions.top : null,"fit-parent":true,"min-height":_vm.shouldRenderResizable ? _vm.dimensions.minHeight : null,"active":_vm.shouldRenderResizable ? ['l', 't', 'lt'] : null},on:{"resize:end":_vm.updateSize}},[(!_vm.isHidden)?_c('aside',{staticClass:"duo-chat gl-bottom-0 gl-flex gl-max-h-full gl-flex-col",class:{
|
|
741
771
|
'resizable-content': _vm.shouldRenderResizable,
|
|
742
772
|
'duo-chat-drawer': !_vm.shouldRenderResizable,
|
|
743
|
-
},attrs:{"id":"chat-component","role":"complementary","data-testid":"chat-component"}},[(_vm.showHeader)?_c('duo-chat-header',{ref:"header",attrs:{"active-thread-id":_vm.activeThreadId,"title":_vm.isMultithreaded && _vm.currentView === 'list' ? _vm.$options.i18n.CHAT_HISTORY_TITLE : _vm.title,"subtitle":_vm.activeThreadTitleForView,"error":_vm.error,"info":_vm.hasMessages ? _vm.chatState.reason : '',"is-multithreaded":_vm.isMultithreaded,"current-view":_vm.currentView,"should-render-resizable":_vm.shouldRenderResizable,"badge-type":_vm.isMultithreaded ? null : _vm.badgeType,"session-id":_vm.sessionId,"agents":_vm.agents},on:{"go-back":_vm.onGoBack,"new-chat":_vm.onNewChat,"close":_vm.hideChat},scopedSlots:_vm._u([{key:"subheader",fn:function(){return [_vm._t("subheader")]},proxy:true}],null,true)}):_vm._e(),_vm._v(" "),_c('div',{staticClass:"gl-flex gl-flex-1 gl-flex-grow gl-flex-col gl-overflow-y-auto gl-overscroll-contain gl-bg-inherit",attrs:{"data-testid":"chat-history"},on:{"scroll":_vm.handleScrollingThrottled}},[(_vm.shouldShowThreadList)?_c('duo-chat-threads',{attrs:{"threads":_vm.threadList,"preferred-locale":_vm.preferredLocale},on:{"new-chat":_vm.onNewChat,"select-thread":_vm.onSelectThread,"delete-thread":_vm.onDeleteThread,"close":_vm.hideChat}}):_c('transition-group',{staticClass:"duo-chat-history gl-mt-auto gl-px-4 gl-pb-4 gl-pt-6",attrs:{"mode":"out-in","tag":"section","name":"message"}},[_vm._l((_vm.conversations),function(conversation,index){return _c('duo-chat-conversation',{key:("conversation-" + index),attrs:{"enable-code-insertion":_vm.enableCodeInsertion,"messages":conversation,"show-delimiter":index > 0,"with-feedback":_vm.withFeedback,"is-tool-approval-processing":_vm.isToolApprovalProcessing,"working-directory":_vm.workingDirectory,"trusted-urls":_vm.trustedUrls},on:{"track-feedback":_vm.onTrackFeedback,"insert-code-snippet":_vm.onInsertCodeSnippet,"copy-code-snippet":_vm.onCopyCodeSnippet,"copy-message":_vm.onCopyMessage,"get-context-item-content":_vm.onGetContextItemContent,"approve-tool":_vm.onApproveToolCall,"deny-tool":_vm.onDenyToolCall,"open-file-path":_vm.onOpenFilePath}})}),_vm._v(" "),(!_vm.hasMessages && !_vm.isLoading)?[_c('div',{key:"empty-state-message",staticClass:"duo-chat-message gl-rounded-bl-none gl-leading-20 gl-text-gray-900 gl-break-anywhere",attrs:{"data-testid":"gl-duo-chat-empty-state"}},[(_vm.emptyStateTitle)?_c('p',{staticClass:"gl-m-0",attrs:{"data-testid":"gl-duo-chat-empty-state-title"}},[_vm._v("\n "+_vm._s(_vm.emptyStateTitle)+"\n ")]):_vm._e(),_vm._v(" "),_c('duo-chat-predefined-prompts',{key:"predefined-prompts",attrs:{"prompts":_vm.predefinedPrompts},on:{"click":_vm.sendPredefinedPrompt}})],1)]:_vm._e(),_vm._v(" "),(_vm.isLoading)?_c('duo-chat-loader',{key:"loader",attrs:{"tool-name":_vm.toolName}}):_vm._e(),_vm._v(" "),_c('div',{key:"anchor",ref:"anchor",staticClass:"scroll-anchor"})],2)],1),_vm._v(" "),(!_vm.shouldShowThreadList)?_c('footer',{staticClass:"duo-chat-drawer-footer gl-relative gl-z-2 gl-shrink-0",attrs:{"data-testid":"chat-footer"}},[(_vm.$scopedSlots['footer-panel'])?_c('div',{staticClass:"gl-relative gl-max-w-full",attrs:{"data-testid":"footer-panel-wrapper"}},[_vm._t("footer-panel")],2):_vm._e(),_vm._v(" "),(_vm.$scopedSlots['footer-actions'])?_c('div',{staticClass:"gl-my-4 gl-flex gl-items-center gl-justify-between gl-gap-x-4 gl-px-4",attrs:{"data-testid":"footer-actions-wrapper"}},[_vm._t("footer-actions")],2):_vm._e(),_vm._v(" "),_c('gl-form',{attrs:{"data-testid":"chat-prompt-form"},on:{"submit":function($event){$event.stopPropagation();$event.preventDefault();return _vm.sendChatPrompt.apply(null, arguments)}}},[_c('div',{staticClass:"gl-relative gl-max-w-full"},[_vm._t("context-items-menu",null,{"isOpen":_vm.contextItemsMenuIsOpen,"onClose":_vm.closeContextItemsMenuOpen,"setRef":_vm.setContextItemsMenuRef,"focusPrompt":_vm.focusChatInput})],2),_vm._v(" "),_c('gl-form-input-group',{scopedSlots:_vm._u([{key:"append",fn:function(){return [(_vm.canSubmit)?_c('gl-button',{staticClass:"!gl-absolute gl-bottom-2 gl-right-2 !gl-rounded-full",attrs:{"icon":"paper-airplane","category":"primary","variant":"confirm","type":"submit","disabled":!_vm.isChatAvailable || !_vm.chatState.isEnabled || _vm.isPromptEmpty || !_vm.hasValidPrompt,"data-testid":"chat-prompt-submit-button","aria-label":_vm.$options.i18n.CHAT_SUBMIT_LABEL}}):_c('gl-button',{staticClass:"!gl-absolute gl-bottom-2 gl-right-2 !gl-rounded-full",attrs:{"icon":"stop","category":"primary","variant":"default","data-testid":"chat-prompt-cancel-button","aria-label":_vm.$options.i18n.CHAT_CANCEL_LABEL,"disabled":!_vm.canCancelInternal},on:{"click":_vm.cancelPrompt}})]},proxy:true}],null,false,4037582087)},[_c('div',{staticClass:"duo-chat-input gl-min-h-8 gl-max-w-full gl-grow gl-align-top",attrs:{"data-value":_vm.prompt}},[(_vm.shouldShowSlashCommands)?_c('gl-card',{ref:"commands",staticClass:"slash-commands !gl-absolute gl-w-full -gl-translate-y-full gl-list-none gl-pl-0 gl-shadow-md",attrs:{"body-class":"!gl-p-2"}},_vm._l((_vm.filteredSlashCommands),function(command,index){return _c('gl-dropdown-item',{key:command.name,class:{ 'active-command': index === _vm.activeCommandIndex },on:{"click":function($event){return _vm.selectSlashCommand(index)}},nativeOn:{"mouseenter":function($event){_vm.activeCommandIndex = index;}}},[_c('span',{staticClass:"gl-flex gl-justify-between"},[_c('span',{staticClass:"gl-block"},[_vm._v(_vm._s(command.name))]),_vm._v(" "),_c('small',{staticClass:"gl-pl-3 gl-text-right gl-italic gl-text-subtle"},[_vm._v(_vm._s(command.description))])])])}),1):_vm._e(),_vm._v(" "),_c('gl-form-textarea',{ref:"prompt",attrs:{"disabled":!_vm.canSubmit || !_vm.isChatAvailable || !_vm.chatState.isEnabled,"data-testid":"chat-prompt-input","placeholder":_vm.inputPlaceholder,"character-count-limit":_vm.maxPromptLength,"textarea-classes":[
|
|
773
|
+
},attrs:{"id":"chat-component","role":"complementary","data-testid":"chat-component"}},[(_vm.showHeader)?_c('duo-chat-header',{ref:"header",attrs:{"active-thread-id":_vm.activeThreadId,"title":_vm.isMultithreaded && _vm.currentView === 'list' ? _vm.$options.i18n.CHAT_HISTORY_TITLE : _vm.title,"subtitle":_vm.activeThreadTitleForView,"error":_vm.error,"info":_vm.hasMessages ? _vm.chatState.reason : '',"is-multithreaded":_vm.isMultithreaded,"current-view":_vm.currentView,"should-render-resizable":_vm.shouldRenderResizable,"badge-type":_vm.isMultithreaded ? null : _vm.badgeType,"session-id":_vm.sessionId,"agents":_vm.agents},on:{"go-back":_vm.onGoBack,"new-chat":_vm.onNewChat,"close":_vm.hideChat},scopedSlots:_vm._u([{key:"subheader",fn:function(){return [_vm._t("subheader")]},proxy:true}],null,true)}):_vm._e(),_vm._v(" "),_c('div',{staticClass:"gl-flex gl-flex-1 gl-flex-grow gl-flex-col gl-overflow-y-auto gl-overscroll-contain gl-bg-inherit",attrs:{"data-testid":"chat-history"},on:{"scroll":_vm.handleScrollingThrottled}},[(_vm.shouldShowThreadList)?_c('duo-chat-threads',{attrs:{"threads":_vm.threadList,"preferred-locale":_vm.preferredLocale},on:{"new-chat":_vm.onNewChat,"select-thread":_vm.onSelectThread,"delete-thread":_vm.onDeleteThread,"close":_vm.hideChat}}):_c('transition-group',{ref:"chatContent",staticClass:"duo-chat-history gl-mt-auto gl-px-4 gl-pb-4 gl-pt-6",attrs:{"mode":"out-in","tag":"section","name":"message"}},[_vm._l((_vm.conversations),function(conversation,index){return _c('duo-chat-conversation',{key:("conversation-" + index),attrs:{"enable-code-insertion":_vm.enableCodeInsertion,"messages":conversation,"show-delimiter":index > 0,"with-feedback":_vm.withFeedback,"is-tool-approval-processing":_vm.isToolApprovalProcessing,"working-directory":_vm.workingDirectory,"trusted-urls":_vm.trustedUrls},on:{"track-feedback":_vm.onTrackFeedback,"insert-code-snippet":_vm.onInsertCodeSnippet,"copy-code-snippet":_vm.onCopyCodeSnippet,"copy-message":_vm.onCopyMessage,"get-context-item-content":_vm.onGetContextItemContent,"approve-tool":_vm.onApproveToolCall,"deny-tool":_vm.onDenyToolCall,"open-file-path":_vm.onOpenFilePath}})}),_vm._v(" "),(!_vm.hasMessages && !_vm.isLoading)?[_c('div',{key:"empty-state-message",staticClass:"duo-chat-message gl-rounded-bl-none gl-leading-20 gl-text-gray-900 gl-break-anywhere",attrs:{"data-testid":"gl-duo-chat-empty-state"}},[(_vm.emptyStateTitle)?_c('p',{staticClass:"gl-m-0",attrs:{"data-testid":"gl-duo-chat-empty-state-title"}},[_vm._v("\n "+_vm._s(_vm.emptyStateTitle)+"\n ")]):_vm._e(),_vm._v(" "),_c('duo-chat-predefined-prompts',{key:"predefined-prompts",attrs:{"prompts":_vm.predefinedPrompts},on:{"click":_vm.sendPredefinedPrompt}})],1)]:_vm._e(),_vm._v(" "),(_vm.isLoading)?_c('duo-chat-loader',{key:"loader",attrs:{"tool-name":_vm.toolName}}):_vm._e(),_vm._v(" "),_c('div',{key:"anchor",ref:"anchor",staticClass:"scroll-anchor"})],2)],1),_vm._v(" "),(!_vm.shouldShowThreadList)?_c('footer',{staticClass:"duo-chat-drawer-footer gl-relative gl-z-2 gl-shrink-0",attrs:{"data-testid":"chat-footer"}},[(_vm.$scopedSlots['footer-panel'])?_c('div',{staticClass:"gl-relative gl-max-w-full",attrs:{"data-testid":"footer-panel-wrapper"}},[_vm._t("footer-panel")],2):_vm._e(),_vm._v(" "),(_vm.$scopedSlots['footer-actions'])?_c('div',{staticClass:"gl-my-4 gl-flex gl-items-center gl-justify-between gl-gap-x-4 gl-px-4",attrs:{"data-testid":"footer-actions-wrapper"}},[_vm._t("footer-actions")],2):_vm._e(),_vm._v(" "),_c('gl-form',{attrs:{"data-testid":"chat-prompt-form"},on:{"submit":function($event){$event.stopPropagation();$event.preventDefault();return _vm.sendChatPrompt.apply(null, arguments)}}},[_c('div',{staticClass:"gl-relative gl-max-w-full"},[_vm._t("context-items-menu",null,{"isOpen":_vm.contextItemsMenuIsOpen,"onClose":_vm.closeContextItemsMenuOpen,"setRef":_vm.setContextItemsMenuRef,"focusPrompt":_vm.focusChatInput})],2),_vm._v(" "),_c('gl-form-input-group',{scopedSlots:_vm._u([{key:"append",fn:function(){return [(_vm.canSubmit)?_c('gl-button',{staticClass:"!gl-absolute gl-bottom-2 gl-right-2 !gl-rounded-full",attrs:{"icon":"paper-airplane","category":"primary","variant":"confirm","type":"submit","disabled":!_vm.isChatAvailable || !_vm.chatState.isEnabled || _vm.isPromptEmpty || !_vm.hasValidPrompt,"data-testid":"chat-prompt-submit-button","aria-label":_vm.$options.i18n.CHAT_SUBMIT_LABEL}}):_c('gl-button',{staticClass:"!gl-absolute gl-bottom-2 gl-right-2 !gl-rounded-full",attrs:{"icon":"stop","category":"primary","variant":"default","data-testid":"chat-prompt-cancel-button","aria-label":_vm.$options.i18n.CHAT_CANCEL_LABEL,"disabled":!_vm.canCancelInternal},on:{"click":_vm.cancelPrompt}})]},proxy:true}],null,false,4037582087)},[_c('div',{staticClass:"duo-chat-input gl-min-h-8 gl-max-w-full gl-grow gl-align-top",attrs:{"data-value":_vm.prompt}},[(_vm.shouldShowSlashCommands)?_c('gl-card',{ref:"commands",staticClass:"slash-commands !gl-absolute gl-w-full -gl-translate-y-full gl-list-none gl-pl-0 gl-shadow-md",attrs:{"body-class":"!gl-p-2"}},_vm._l((_vm.filteredSlashCommands),function(command,index){return _c('gl-dropdown-item',{key:command.name,class:{ 'active-command': index === _vm.activeCommandIndex },on:{"click":function($event){return _vm.selectSlashCommand(index)}},nativeOn:{"mouseenter":function($event){_vm.activeCommandIndex = index;}}},[_c('span',{staticClass:"gl-flex gl-justify-between"},[_c('span',{staticClass:"gl-block"},[_vm._v(_vm._s(command.name))]),_vm._v(" "),_c('small',{staticClass:"gl-pl-3 gl-text-right gl-italic gl-text-subtle"},[_vm._v(_vm._s(command.description))])])])}),1):_vm._e(),_vm._v(" "),_c('gl-form-textarea',{ref:"prompt",attrs:{"disabled":!_vm.canSubmit || !_vm.isChatAvailable || !_vm.chatState.isEnabled,"data-testid":"chat-prompt-input","placeholder":_vm.inputPlaceholder,"character-count-limit":_vm.maxPromptLength,"textarea-classes":[
|
|
744
774
|
'gl-absolute',
|
|
745
775
|
'!gl-h-full',
|
|
746
776
|
'gl-rounded-br-none',
|
|
@@ -788,4 +818,4 @@ var __vue_staticRenderFns__ = [];
|
|
|
788
818
|
);
|
|
789
819
|
|
|
790
820
|
export default __vue_component__;
|
|
791
|
-
export { i18n };
|
|
821
|
+
export { i18n, scrollWithNewContent };
|
|
@@ -90,7 +90,7 @@ const __vue_script__ = script;
|
|
|
90
90
|
/* template */
|
|
91
91
|
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"gl-flex gl-flex-col gl-overflow-hidden"},[(_vm.loading)?_c('duo-chat-threads-skeleton'):(_vm.hasThreads)?[_c('div',{staticClass:"gl-grow gl-overflow-y-scroll gl-px-4 gl-py-5"},_vm._l((_vm.groupedThreads),function(threadsForDate,date){return _c('div',{key:date,staticClass:"gl-mb-3"},[_c('div',{staticClass:"gl-mb-3 gl-text-base gl-font-bold gl-text-default",attrs:{"data-testid":"chat-threads-date-header"}},[_vm._v("\n "+_vm._s(_vm.formattedLocalDate(date))+"\n ")]),_vm._v(" "),_vm._l((threadsForDate),function(thread){return _c('div',{key:thread.id,staticClass:"history-item gl-w-full gl-text-base"},[_c('gl-button',{staticClass:"history-item-name gl-w-full gl-text-base",attrs:{"data-testid":"chat-threads-thread-box","aria-label":_vm.sprintf(_vm.$options.i18n.OPEN_CHAT_LABEL, {
|
|
92
92
|
title: _vm.threadLabel(thread),
|
|
93
|
-
})},on:{"click":function($event){return _vm.onSelectThread(thread)}}},[_c('gl-avatar',{staticClass:"gl-shrink-0",attrs:{"size":32,"entity-name":_vm.threadLabel(thread),"shape":"circle"}}),_vm._v(" "),_c('div',{staticClass:"gl-grow gl-whitespace-normal gl-break-all"},[(!thread.title && !thread.goal)?_c('span',{staticClass:"gl-line-clamp-1 gl-text-default"},[_vm._v(_vm._s(_vm.$options.i18n.UNTITLED_CHAT_TITLE))]):[(thread.title)?_c('span',{staticClass:"gl-line-clamp-1 gl-text-default"},[_vm._v(_vm._s(thread.title))]):_vm._e(),_vm._v(" "),(thread.goal)?_c('span',{staticClass:"gl-line-clamp-1 gl-text-subtle"},[_vm._v(_vm._s(thread.goal))]):_vm._e()]],2)],1),_vm._v(" "),_c('gl-button',{staticClass:"history-remove-button",attrs:{"data-testid":"chat-threads-delete-thread-button","icon":"remove","category":"tertiary","title":_vm.$options.i18n.THREAD_DELETE_LABEL,"aria-label":_vm.$options.i18n.THREAD_DELETE_LABEL},on:{"click":function($event){return _vm.$emit('delete-thread', thread.id)}}})],1)})],2)}),0),_vm._v(" "),_c('p',{staticClass:"gl-m-0 gl-p-5 gl-text-center gl-text-base gl-text-subtle",attrs:{"data-testid":"chat-threads-info-banner"}},[_vm._v("\n "+_vm._s(_vm.$options.i18n.CHAT_HISTORY_INFO)+"\n ")])]:_c('duo-chat-threads-empty')],2)};
|
|
93
|
+
})},on:{"click":function($event){return _vm.onSelectThread(thread)}}},[_c('gl-avatar',{staticClass:"gl-shrink-0",attrs:{"size":32,"entity-name":_vm.threadLabel(thread),"shape":"circle"}}),_vm._v(" "),_c('div',{staticClass:"gl-grow gl-whitespace-normal gl-break-all"},[(!thread.title && !thread.goal)?_c('span',{staticClass:"gl-line-clamp-1 gl-text-default"},[_vm._v(_vm._s(_vm.$options.i18n.UNTITLED_CHAT_TITLE))]):[(thread.title)?_c('span',{staticClass:"gl-line-clamp-1 gl-text-default"},[_vm._v(_vm._s(thread.title))]):_vm._e(),_vm._v(" "),(thread.goal)?_c('span',{staticClass:"gl-line-clamp-1 gl-text-subtle"},[_vm._v(_vm._s(thread.goal))]):_vm._e()],_vm._v(" "),(thread.agentName)?_c('span',{staticClass:"gl-line-clamp-1 gl-text-sm gl-text-subtle"},[_vm._v("\n "+_vm._s(thread.agentName)+"\n ")]):_vm._e()],2)],1),_vm._v(" "),_c('gl-button',{staticClass:"history-remove-button",attrs:{"data-testid":"chat-threads-delete-thread-button","icon":"remove","category":"tertiary","title":_vm.$options.i18n.THREAD_DELETE_LABEL,"aria-label":_vm.$options.i18n.THREAD_DELETE_LABEL},on:{"click":function($event){return _vm.$emit('delete-thread', thread.id)}}})],1)})],2)}),0),_vm._v(" "),_c('p',{staticClass:"gl-m-0 gl-p-5 gl-text-center gl-text-base gl-text-subtle",attrs:{"data-testid":"chat-threads-info-banner"}},[_vm._v("\n "+_vm._s(_vm.$options.i18n.CHAT_HISTORY_INFO)+"\n ")])]:_c('duo-chat-threads-empty')],2)};
|
|
94
94
|
var __vue_staticRenderFns__ = [];
|
|
95
95
|
|
|
96
96
|
/* style */
|
package/package.json
CHANGED
|
@@ -98,6 +98,12 @@ const localeValidator = (value) => {
|
|
|
98
98
|
}
|
|
99
99
|
};
|
|
100
100
|
|
|
101
|
+
export const scrollWithNewContent = (element) => {
|
|
102
|
+
// Add 20px tolerance for floating point precision and unintentional micro-scrolls
|
|
103
|
+
// otherwise even a tiny scroll up will prevent autoscroll
|
|
104
|
+
return element.scrollTop + element.offsetHeight >= element.scrollHeight - 20;
|
|
105
|
+
};
|
|
106
|
+
|
|
101
107
|
export default {
|
|
102
108
|
name: 'DuoChat',
|
|
103
109
|
components: {
|
|
@@ -377,7 +383,7 @@ export default {
|
|
|
377
383
|
return {
|
|
378
384
|
isHidden: false,
|
|
379
385
|
prompt: '',
|
|
380
|
-
|
|
386
|
+
scrollWithNewContent: true,
|
|
381
387
|
activeCommandIndex: 0,
|
|
382
388
|
hasValidPrompt: true,
|
|
383
389
|
compositionJustEnded: false,
|
|
@@ -520,12 +526,21 @@ export default {
|
|
|
520
526
|
this.isHidden = false;
|
|
521
527
|
},
|
|
522
528
|
lastMessage(newMessage) {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
//
|
|
526
|
-
this.
|
|
529
|
+
// Always scroll when user sends a message
|
|
530
|
+
if (newMessage?.role.toLowerCase() === MESSAGE_MODEL_ROLES.user) {
|
|
531
|
+
// Reset scrollWithNewContent so ResizeObserver will scroll during streaming
|
|
532
|
+
this.scrollWithNewContent = true;
|
|
533
|
+
this.scrollToBottomAsync();
|
|
527
534
|
}
|
|
528
535
|
},
|
|
536
|
+
'lastMessage.content': {
|
|
537
|
+
handler() {
|
|
538
|
+
// Scroll when assistant message content is updated (streaming)
|
|
539
|
+
if (this.scrollWithNewContent) {
|
|
540
|
+
this.scrollToBottomAsync();
|
|
541
|
+
}
|
|
542
|
+
},
|
|
543
|
+
},
|
|
529
544
|
shouldShowSlashCommands(shouldShow) {
|
|
530
545
|
if (shouldShow) {
|
|
531
546
|
this.onShowSlashCommands();
|
|
@@ -539,7 +554,20 @@ export default {
|
|
|
539
554
|
this.handleScrollingThrottled = throttle(this.handleScrolling, 200); // Assume a 200ms throttle for example
|
|
540
555
|
},
|
|
541
556
|
mounted() {
|
|
542
|
-
this.
|
|
557
|
+
const contentElement = this.$refs.chatContent?.$el;
|
|
558
|
+
if (contentElement) {
|
|
559
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
560
|
+
// Only scroll if user has not scrolled up (respect scroll position during streaming)
|
|
561
|
+
if (this.scrollWithNewContent) {
|
|
562
|
+
this.scrollToBottomAsync();
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
this.resizeObserver.observe(contentElement);
|
|
566
|
+
}
|
|
567
|
+
},
|
|
568
|
+
beforeUnmount() {
|
|
569
|
+
this.resizeObserver?.disconnect();
|
|
570
|
+
this.resizeObserver = null;
|
|
543
571
|
},
|
|
544
572
|
|
|
545
573
|
methods: {
|
|
@@ -605,13 +633,18 @@ export default {
|
|
|
605
633
|
this.sendChatPrompt();
|
|
606
634
|
},
|
|
607
635
|
handleScrolling(event) {
|
|
608
|
-
|
|
609
|
-
|
|
636
|
+
this.scrollWithNewContent = scrollWithNewContent(event.target);
|
|
637
|
+
},
|
|
638
|
+
scrollToBottomAsync() {
|
|
639
|
+
requestAnimationFrame(() => {
|
|
640
|
+
this.scrollToBottom();
|
|
641
|
+
});
|
|
610
642
|
},
|
|
611
643
|
async scrollToBottom() {
|
|
612
644
|
await this.$nextTick();
|
|
613
645
|
|
|
614
646
|
this.$refs.anchor?.scrollIntoView?.();
|
|
647
|
+
this.scrollWithNewContent = true;
|
|
615
648
|
},
|
|
616
649
|
focusChatInput() {
|
|
617
650
|
this.$refs.prompt?.$el?.querySelector?.('textarea')?.focus();
|
|
@@ -885,6 +918,7 @@ export default {
|
|
|
885
918
|
/>
|
|
886
919
|
<transition-group
|
|
887
920
|
v-else
|
|
921
|
+
ref="chatContent"
|
|
888
922
|
mode="out-in"
|
|
889
923
|
tag="section"
|
|
890
924
|
name="message"
|