@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 (
|
|
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="
|
|
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
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
230
|
-
|
|
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.
|
|
15
|
+
animation-delay: 0.2s;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
.bounce-dot3 {
|
|
19
19
|
animation: bounce 1.5s infinite;
|
|
20
|
-
animation-delay: 0.
|
|
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.
|
|
26
|
+
opacity: 0.4;
|
|
27
27
|
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
|
|
28
28
|
}
|
|
29
29
|
50% {
|