@adminforth/agent 2.2.0 → 2.2.2

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.
@@ -24,11 +24,15 @@ const props = withDefaults(defineProps<{
24
24
  wrapperStyle?: Record<string, string>
25
25
  contentStyle?: Record<string, string>
26
26
  scrollBarAutoHide?: boolean
27
+ scrollToBottomOnMount?: boolean
28
+ debugMode?: boolean
27
29
  }>(), {
28
30
  enabled: true,
29
31
  threshold: 50,
30
32
  behavior: 'instant',
31
- scrollBarAutoHide: true
33
+ scrollBarAutoHide: true,
34
+ scrollToBottomOnMount: true,
35
+ debugMode: false
32
36
  })
33
37
 
34
38
  const containerRef = ref<HTMLDivElement | null>(null)
@@ -72,7 +76,7 @@ onMounted(() => {
72
76
  scrollParams.value.scrollTop = containerRef.value.scrollEl.scrollTop
73
77
  scrollParams.value.scrollHeight = containerRef.value.scrollEl.scrollHeight
74
78
  scrollParams.value.clientHeight = containerRef.value.scrollEl.clientHeight
75
- if (props.enabled && !isUserScrolledUp.value) {
79
+ if (!isUserScrolledUp.value && props.scrollToBottomOnMount) {
76
80
  scrollToBottom()
77
81
  }
78
82
  })
@@ -102,10 +106,12 @@ function isNearBottom(customThreshold?: number): boolean {
102
106
  }
103
107
 
104
108
  function scrollToBottom(force = false): void {
109
+ if (props.debugMode) console.log('scrollToBottom called with force:', force)
105
110
  const container = containerRef.value?.scrollEl
106
111
  if (!container) return
107
112
 
108
113
  if (isUserScrolledUp.value && !force) return
114
+ if (props.debugMode) console.log('Scrolling to bottom...:', container.scrollHeight)
109
115
  container.scrollTo({
110
116
  top: container.scrollHeight,
111
117
  behavior: props.behavior
@@ -11,7 +11,7 @@
11
11
  <div ref="chatContainerRef" class="relative flex-1 min-h-0 overflow-hidden" @click="recalculateScroll()">
12
12
  <CustomAutoScrollContainer
13
13
  v-if="showScrollContainer"
14
- :enabled="!showScrollToBottomButton"
14
+ :enabled="false"
15
15
  class="relative h-full flex flex-col overflow-y-auto translate-x-[-50%] left-1/2"
16
16
  ref="scrollContainer"
17
17
  :threshold="THRESHOLD_TO_SHOW_BUTTON"
@@ -53,27 +53,49 @@
53
53
  </div>
54
54
  <div v-if="showBottomSpacer" class="w-full" :style="{ height: spacerHeight + 'px' }"></div>
55
55
  </CustomAutoScrollContainer>
56
- <button @click="scrollContainer.scrollToBottom();">
57
- <IconArrowDownOutline
58
- class="absolute z-10 bottom-8 left-1/2 -translate-x-1/2 bg-lightPrimary/20 dark:bg-darkPrimary/10
59
- border border-lightPrimary dark:border-darkPrimary text-lightPrimary dark:text-darkPrimary p-2 w-10 h-10
60
- rounded-full transition-opacity duration-100 ease-in backdrop-blur-sm"
61
- :class="showScrollToBottomButton ? 'opacity-100' : 'opacity-0 pointer-events-none'"
62
- :disabled="!showScrollToBottomButton"
63
- />
64
- </button>
56
+ <Transition
57
+ enter-active-class="transition-all duration-250 ease-out"
58
+ enter-from-class="opacity-0 translate-y-3 scale-90"
59
+ enter-to-class="opacity-100 translate-y-0 scale-100"
60
+ leave-active-class="transition-all duration-200 ease-in"
61
+ leave-from-class="opacity-100 translate-y-0 scale-100"
62
+ leave-to-class="opacity-0 translate-y-3 scale-90"
63
+ >
64
+ <button
65
+ v-if="showScrollToBottomButton"
66
+ @click="scrollContainer.scrollToBottom();"
67
+ class="absolute z-10 bottom-8 left-1/2 -translate-x-1/2
68
+ bg-lightPrimary/20 dark:bg-darkPrimary/10
69
+ border border-lightPrimary dark:border-darkPrimary
70
+ text-lightPrimary dark:text-darkPrimary
71
+ backdrop-blur-sm rounded-full
72
+ w-10 h-10 flex items-center justify-center gap-0.5"
73
+ :class="{ 'w-12 h-8': showAnimationInScrollToBottomButton }"
74
+ >
75
+ <IconArrowDownOutline
76
+ v-if="!showAnimationInScrollToBottomButton"
77
+ class="p-2 w-10 h-10 transition-opacity duration-100 ease-in "
78
+ :disabled="!showScrollToBottomButton"
79
+ />
80
+ <ThreeDotsAnimation
81
+ v-else
82
+ :disabled="!showScrollToBottomButton"
83
+ />
84
+ </button>
85
+ </Transition>
65
86
  </div>
66
87
  </template>
67
88
 
68
89
 
69
90
  <script setup lang="ts">
70
91
  import type { IMessage } from '../types';
71
- import { useTemplateRef, ref, onMounted, onUnmounted, watch, nextTick, defineAsyncComponent } from 'vue';
92
+ import { useTemplateRef, ref, onMounted, onUnmounted, watch, nextTick, defineAsyncComponent, computed } from 'vue';
72
93
  import { IconArrowDownOutline } from '@iconify-prerendered/vue-flowbite';
73
94
  import SessionsHistory from '../SessionsHistory.vue';
74
95
  import { useAgentStore } from '../composables/useAgentStore';
75
96
  import { useAgentTransitions } from '../composables/useAgentTransitions';
76
97
  import MessageRenderer from './MessageRenderer.vue';
98
+ import ThreeDotsAnimation from './ThreeDotsAnimation.vue';
77
99
 
78
100
  const CustomAutoScrollContainer = defineAsyncComponent(() => import('../CustomAutoScrollContainer.vue'));
79
101
 
@@ -114,6 +136,10 @@ let updateSpacerFrameId: number | null = null;
114
136
  let pendingSpacerUpdate: Promise<void> | null = null;
115
137
  let spacerUpdateQueued = false;
116
138
 
139
+ const showAnimationInScrollToBottomButton = computed(() => {
140
+ return agentStore.isResponseInProgress && showScrollToBottomButton.value;
141
+ });
142
+
117
143
  onMounted(async () => {
118
144
  messageResizeObserver = new ResizeObserver(() => {
119
145
  scheduleSpacerHeightUpdate();
@@ -182,7 +208,17 @@ function getHeightOfLastUserMessage() {
182
208
  }
183
209
 
184
210
  function getHeightOfLastAgentMessage() {
185
- return getLastMessageElement('assistant')?.clientHeight ?? 0;
211
+ const lastUserIndex = props.messages.findLastIndex((message: IMessage) => message.role === 'user');
212
+ const lastAgentIndex = props.messages.findLastIndex((message: IMessage) => message.role === 'assistant');
213
+ // Only reserve spacer space for the current turn's answer, i.e. an assistant message that comes
214
+ // after the last user message. Right after a send the "Thinking" placeholder isn't pushed yet, so
215
+ // findLastIndex would return the previous turn's answer (which sits above the just-sent user
216
+ // message). Subtracting its height shrinks the spacer, making scrollToBottom stop short — and once
217
+ // the real placeholder arrives the spacer grows and the true bottom drops below where we scrolled.
218
+ if (lastAgentIndex <= lastUserIndex) {
219
+ return 0;
220
+ }
221
+ return messagesRefs.value[lastAgentIndex]?.clientHeight ?? 0;
186
222
  }
187
223
 
188
224
  function getScrollClientHeight() {
@@ -195,15 +231,14 @@ async function updateSpacerHeight() {
195
231
  }
196
232
 
197
233
  const clientHeight = getScrollClientHeight();
198
-
199
234
  if (!clientHeight) {
200
235
  return;
201
236
  }
202
237
 
203
238
  const lastUserMessageHeight = getHeightOfLastUserMessage();
204
239
  const lastAgentMessageHeight = getHeightOfLastAgentMessage();
205
-
206
- spacerHeight.value = Math.max(0, clientHeight - (lastUserMessageHeight + MASK_HEIGHT + lastAgentMessageHeight));
240
+ const newSpacerHeight = Math.max(0, clientHeight - (lastUserMessageHeight + MASK_HEIGHT + lastAgentMessageHeight));
241
+ spacerHeight.value = newSpacerHeight;
207
242
  }
208
243
 
209
244
  function scheduleSpacerHeightUpdate() {
@@ -225,11 +260,11 @@ function scheduleSpacerHeightUpdate() {
225
260
  pendingSpacerUpdate = null;
226
261
 
227
262
  // Auto-scroll to bottom if response generation is in progress
228
- if (agentStore.isResponseInProgress) {
229
- nextTick(() => {
230
- scrollContainer.value?.scrollToBottom();
231
- });
232
- }
263
+ // if (agentStore.isResponseInProgress) {
264
+ // nextTick(() => {
265
+ // scrollContainer.value?.scrollToBottom();
266
+ // });
267
+ // }
233
268
 
234
269
  if (spacerUpdateQueued) {
235
270
  scheduleSpacerHeightUpdate();
@@ -284,8 +319,9 @@ async function handleSendMessage() {
284
319
 
285
320
  if (clientHeight) {
286
321
  showBottomSpacer.value = true;
322
+ await nextTick(); // render the just-sent user message + spacer before measuring their heights
287
323
  await updateSpacerHeight();
288
- await nextTick();
324
+ await nextTick(); // apply the computed spacer height before scrolling
289
325
  scrollContainer.value?.scrollToBottom();
290
326
  }
291
327
  }
@@ -12,18 +12,18 @@
12
12
 
13
13
  .bounce-dot2 {
14
14
  animation: bounce 1.5s infinite;
15
- animation-delay: 0.1s;
15
+ animation-delay: 0.2s;
16
16
  }
17
17
 
18
18
  .bounce-dot3 {
19
19
  animation: bounce 1.5s infinite;
20
- animation-delay: 0.2s;
20
+ animation-delay: 0.4s;
21
21
  }
22
22
 
23
23
  @keyframes bounce {
24
24
  0%, 100% {
25
25
  transform: translateY(20%);
26
- opacity: 0.3;
26
+ opacity: 0.4;
27
27
  animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
28
28
  }
29
29
  50% {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/agent",
3
- "version": "2.2.0",
3
+ "version": "2.2.2",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",