Skip to content

Commit 33c31b0

Browse files
authored
perf: batch reasoning renderer updates (#387)
1 parent f139c30 commit 33c31b0

1 file changed

Lines changed: 54 additions & 23 deletions

File tree

packages/components/src/bubble/renderers/Reasoning.vue

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script setup lang="ts">
22
import { IconArrowDown, IconAtom } from '@opentiny/tiny-robot-svgs'
3-
import { nextTick, ref, watch, watchEffect } from 'vue'
3+
import { nextTick, onBeforeUnmount, ref, watch } from 'vue'
44
import { useBubbleContentRenderer, useBubbleEventFn, useOmitMessageFields } from '../composables'
55
import { BubbleContentRendererProps, ChatMessageContent } from '../index.type'
66
@@ -23,16 +23,58 @@ const { restMessage, restProps } = useOmitMessageFields(props, ['reasoning_conte
2323
const renderer = useBubbleContentRenderer(restMessage, props.contentIndex)
2424
2525
const open = ref(true)
26+
const displayedReasoningContent = ref(props.message.reasoning_content ?? '')
27+
const detailRef = ref<HTMLParagraphElement | null>(null)
2628
27-
watchEffect(() => {
28-
// 思考过程默认展开
29-
open.value = props.message.state?.open ?? true
30-
})
29+
let renderFrame: number | null = null
30+
31+
const scrollDetailToBottom = () => {
32+
if (!open.value || !detailRef.value) {
33+
return
34+
}
35+
36+
detailRef.value.scrollTop = detailRef.value.scrollHeight
37+
}
38+
39+
const scheduleDetailUpdate = () => {
40+
if (typeof window === 'undefined' || typeof window.requestAnimationFrame !== 'function') {
41+
displayedReasoningContent.value = props.message.reasoning_content ?? ''
42+
return
43+
}
44+
45+
if (renderFrame !== null) {
46+
return
47+
}
48+
49+
renderFrame = window.requestAnimationFrame(() => {
50+
renderFrame = null
51+
displayedReasoningContent.value = props.message.reasoning_content ?? ''
52+
nextTick(scrollDetailToBottom)
53+
})
54+
}
55+
56+
watch(
57+
() => props.message.state?.open,
58+
(value) => {
59+
// 思考过程默认展开
60+
open.value = value ?? true
61+
if (open.value) {
62+
scheduleDetailUpdate()
63+
}
64+
},
65+
{ immediate: true },
66+
)
67+
68+
watch(() => props.message.reasoning_content, scheduleDetailUpdate, { immediate: true })
3169
3270
const handleBubbleEvent = useBubbleEventFn()
3371
3472
const handleClick = () => {
3573
open.value = !open.value
74+
if (open.value) {
75+
scheduleDetailUpdate()
76+
}
77+
3678
handleBubbleEvent({
3779
name: 'state:update',
3880
payload: {
@@ -42,23 +84,12 @@ const handleClick = () => {
4284
})
4385
}
4486
45-
const detailRef = ref<HTMLParagraphElement | null>(null)
46-
47-
watch(
48-
() => props.message.reasoning_content,
49-
() => {
50-
nextTick(() => {
51-
if (!detailRef.value) {
52-
return
53-
}
54-
55-
detailRef.value.scrollTo({
56-
top: detailRef.value.scrollHeight,
57-
behavior: 'smooth',
58-
})
59-
})
60-
},
61-
)
87+
onBeforeUnmount(() => {
88+
if (renderFrame !== null && typeof window !== 'undefined') {
89+
window.cancelAnimationFrame(renderFrame)
90+
renderFrame = null
91+
}
92+
})
6293
</script>
6394

6495
<template>
@@ -77,7 +108,7 @@ watch(
77108
</div>
78109
<div class="border-line"></div>
79110
</div>
80-
<p class="detail-content" ref="detailRef">{{ props.message.reasoning_content }}</p>
111+
<p class="detail-content" ref="detailRef">{{ displayedReasoningContent }}</p>
81112
</div>
82113
</div>
83114
<component :is="renderer.renderer" v-bind="{ ...renderer.attributes, ...restProps }" />

0 commit comments

Comments
 (0)