mirror of
https://github.com/AstrBotDevs/AstrBot.git
synced 2026-10-16 06:58:51 +08:00
fix(chatui): respect reasoning panel scroll intent (#10243)
* fix(chatui): respect reasoning panel scroll intent * Delete dashboard/tests/reasoningSidebarScroll.test.mjs
This commit is contained in:
@@ -6,7 +6,17 @@
|
||||
<v-btn icon="mdi-close" size="small" variant="text" @click="close" />
|
||||
</div>
|
||||
|
||||
<div ref="sidebarBody" class="reasoning-sidebar-body">
|
||||
<div
|
||||
ref="sidebarBody"
|
||||
class="reasoning-sidebar-body"
|
||||
tabindex="0"
|
||||
@scroll="handleSidebarScroll"
|
||||
@wheel.passive="handleSidebarInteraction"
|
||||
@touchstart.passive="handleSidebarInteraction"
|
||||
@touchmove.passive="handleSidebarInteraction"
|
||||
@pointerdown="handleSidebarInteraction"
|
||||
@keydown="handleSidebarInteraction"
|
||||
>
|
||||
<ReasoningTimeline
|
||||
v-if="parts.length || reasoning"
|
||||
:parts="parts"
|
||||
@@ -40,6 +50,10 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const sidebarBody = ref<HTMLElement | null>(null);
|
||||
const shouldStickToBottom = ref(true);
|
||||
let lastSidebarScrollTop = 0;
|
||||
let touchScrollY = 0;
|
||||
let scrollIntent = 0;
|
||||
|
||||
const emit = defineEmits<{
|
||||
"update:modelValue": [value: boolean];
|
||||
@@ -60,19 +74,90 @@ function close() {
|
||||
}
|
||||
|
||||
function scrollToLatestActivity() {
|
||||
if (!props.modelValue) return;
|
||||
if (!props.modelValue || !shouldStickToBottom.value) return;
|
||||
void nextTick(() => {
|
||||
const body = sidebarBody.value;
|
||||
if (!body) return;
|
||||
if (!body || !shouldStickToBottom.value) return;
|
||||
body.scrollTop = body.scrollHeight;
|
||||
lastSidebarScrollTop = Math.max(0, body.scrollTop);
|
||||
});
|
||||
}
|
||||
|
||||
function handleSidebarInteraction(
|
||||
event: WheelEvent | TouchEvent | PointerEvent | KeyboardEvent,
|
||||
) {
|
||||
if (event instanceof WheelEvent) {
|
||||
if (event.ctrlKey || event.deltaY === 0) return;
|
||||
scrollIntent = Math.sign(event.deltaY);
|
||||
} else if (event.type === "touchstart" || event.type === "touchmove") {
|
||||
const touch = (event as TouchEvent).touches[0];
|
||||
if (!touch) return;
|
||||
if (event.type === "touchstart") {
|
||||
touchScrollY = touch.clientY;
|
||||
return;
|
||||
}
|
||||
scrollIntent = Math.sign(touchScrollY - touch.clientY);
|
||||
touchScrollY = touch.clientY;
|
||||
} else if (event instanceof KeyboardEvent) {
|
||||
const target = event.target as HTMLElement;
|
||||
if (
|
||||
target.closest("input, textarea, select, [contenteditable], button, a")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
["ArrowUp", "PageUp", "Home"].includes(event.key) ||
|
||||
(event.key === " " && event.shiftKey)
|
||||
) {
|
||||
scrollIntent = -1;
|
||||
} else if (["ArrowDown", "PageDown", "End", " "].includes(event.key)) {
|
||||
scrollIntent = 1;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (event.target !== sidebarBody.value) return;
|
||||
scrollIntent = 0;
|
||||
shouldStickToBottom.value = false;
|
||||
}
|
||||
if (scrollIntent < 0) shouldStickToBottom.value = false;
|
||||
}
|
||||
|
||||
function handleSidebarScroll() {
|
||||
const body = sidebarBody.value;
|
||||
if (!body) return;
|
||||
const maxScrollTop = Math.max(0, body.scrollHeight - body.clientHeight);
|
||||
const scrollTop = Math.max(0, body.scrollTop);
|
||||
const previousTop = Math.min(lastSidebarScrollTop, maxScrollTop);
|
||||
const isAwayFromBottom = maxScrollTop - scrollTop > 2;
|
||||
if (scrollTop < previousTop) {
|
||||
shouldStickToBottom.value = false;
|
||||
} else if (
|
||||
scrollTop > previousTop &&
|
||||
!isAwayFromBottom &&
|
||||
scrollIntent >= 0
|
||||
) {
|
||||
shouldStickToBottom.value = true;
|
||||
}
|
||||
lastSidebarScrollTop = scrollTop;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [props.modelValue, props.reasoning, props.parts],
|
||||
scrollToLatestActivity,
|
||||
{ deep: true, flush: "post", immediate: true },
|
||||
() => props.modelValue,
|
||||
(open) => {
|
||||
if (!open) return;
|
||||
shouldStickToBottom.value = true;
|
||||
scrollIntent = 0;
|
||||
lastSidebarScrollTop = 0;
|
||||
scrollToLatestActivity();
|
||||
},
|
||||
{ flush: "post", immediate: true },
|
||||
);
|
||||
|
||||
watch(() => [props.reasoning, props.parts], scrollToLatestActivity, {
|
||||
deep: true,
|
||||
flush: "post",
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user