perf(app): better session stream rendering
This commit is contained in:
@@ -316,12 +316,22 @@ export default function Page() {
|
|||||||
return sync.session.history.loading(id)
|
return sync.session.history.loading(id)
|
||||||
})
|
})
|
||||||
const emptyUserMessages: UserMessage[] = []
|
const emptyUserMessages: UserMessage[] = []
|
||||||
const userMessages = createMemo(() => messages().filter((m) => m.role === "user") as UserMessage[], emptyUserMessages)
|
const userMessages = createMemo(
|
||||||
const visibleUserMessages = createMemo(() => {
|
() => messages().filter((m) => m.role === "user") as UserMessage[],
|
||||||
const revert = revertMessageID()
|
emptyUserMessages,
|
||||||
if (!revert) return userMessages()
|
{ equals: same },
|
||||||
return userMessages().filter((m) => m.id < revert)
|
)
|
||||||
}, emptyUserMessages)
|
const visibleUserMessages = createMemo(
|
||||||
|
() => {
|
||||||
|
const revert = revertMessageID()
|
||||||
|
if (!revert) return userMessages()
|
||||||
|
return userMessages().filter((m) => m.id < revert)
|
||||||
|
},
|
||||||
|
emptyUserMessages,
|
||||||
|
{
|
||||||
|
equals: same,
|
||||||
|
},
|
||||||
|
)
|
||||||
const lastUserMessage = createMemo(() => visibleUserMessages().at(-1))
|
const lastUserMessage = createMemo(() => visibleUserMessages().at(-1))
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
@@ -347,13 +357,19 @@ export default function Page() {
|
|||||||
promptHeight: 0,
|
promptHeight: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
const renderedUserMessages = createMemo(() => {
|
const renderedUserMessages = createMemo(
|
||||||
const msgs = visibleUserMessages()
|
() => {
|
||||||
const start = store.turnStart
|
const msgs = visibleUserMessages()
|
||||||
if (start <= 0) return msgs
|
const start = store.turnStart
|
||||||
if (start >= msgs.length) return emptyUserMessages
|
if (start <= 0) return msgs
|
||||||
return msgs.slice(start)
|
if (start >= msgs.length) return emptyUserMessages
|
||||||
}, emptyUserMessages)
|
return msgs.slice(start)
|
||||||
|
},
|
||||||
|
emptyUserMessages,
|
||||||
|
{
|
||||||
|
equals: same,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
const newSessionWorktree = createMemo(() => {
|
const newSessionWorktree = createMemo(() => {
|
||||||
if (store.newSessionWorktree === "create") return "create"
|
if (store.newSessionWorktree === "create") return "create"
|
||||||
|
|||||||
@@ -457,9 +457,16 @@ export function SessionTurn(
|
|||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const timer = setInterval(() => {
|
const update = () => {
|
||||||
setStore("duration", duration())
|
setStore("duration", duration())
|
||||||
}, 1000)
|
}
|
||||||
|
|
||||||
|
update()
|
||||||
|
|
||||||
|
// Only keep ticking while the active (in-progress) turn is running.
|
||||||
|
if (!working()) return
|
||||||
|
|
||||||
|
const timer = setInterval(update, 1000)
|
||||||
onCleanup(() => clearInterval(timer))
|
onCleanup(() => clearInterval(timer))
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -495,6 +502,11 @@ export function SessionTurn(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
if (!statusTimeout) return
|
||||||
|
clearTimeout(statusTimeout)
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-component="session-turn" class={props.classes?.root} ref={setRootRef}>
|
<div data-component="session-turn" class={props.classes?.root} ref={setRootRef}>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|||||||
let settleTimer: ReturnType<typeof setTimeout> | undefined
|
let settleTimer: ReturnType<typeof setTimeout> | undefined
|
||||||
let autoTimer: ReturnType<typeof setTimeout> | undefined
|
let autoTimer: ReturnType<typeof setTimeout> | undefined
|
||||||
let cleanup: (() => void) | undefined
|
let cleanup: (() => void) | undefined
|
||||||
let resizeFrame: number | undefined
|
|
||||||
let auto: { top: number; time: number } | undefined
|
let auto: { top: number; time: number } | undefined
|
||||||
|
|
||||||
const threshold = () => options.bottomThreshold ?? 10
|
const threshold = () => options.bottomThreshold ?? 10
|
||||||
@@ -152,11 +151,10 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|||||||
() => {
|
() => {
|
||||||
if (!active()) return
|
if (!active()) return
|
||||||
if (store.userScrolled) return
|
if (store.userScrolled) return
|
||||||
if (resizeFrame !== undefined) return
|
// ResizeObserver fires after layout, before paint.
|
||||||
resizeFrame = requestAnimationFrame(() => {
|
// Keep the bottom locked in the same frame to avoid visible
|
||||||
resizeFrame = undefined
|
// "jump up then catch up" artifacts while streaming content.
|
||||||
scrollToBottom(false)
|
scrollToBottom(false)
|
||||||
})
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -190,7 +188,6 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
if (settleTimer) clearTimeout(settleTimer)
|
if (settleTimer) clearTimeout(settleTimer)
|
||||||
if (autoTimer) clearTimeout(autoTimer)
|
if (autoTimer) clearTimeout(autoTimer)
|
||||||
if (resizeFrame !== undefined) cancelAnimationFrame(resizeFrame)
|
|
||||||
if (cleanup) cleanup()
|
if (cleanup) cleanup()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user