feat(tui): preserve scroll position on submit
This commit is contained in:
parent
db45026c6c
commit
6cc7ccd3c1
1 changed files with 78 additions and 43 deletions
|
|
@ -295,6 +295,7 @@ export function Session() {
|
||||||
let seeded = false
|
let seeded = false
|
||||||
let sent = false
|
let sent = false
|
||||||
let scroll: ScrollBoxRenderable
|
let scroll: ScrollBoxRenderable
|
||||||
|
const [showJumpToBottom, setShowJumpToBottom] = createSignal(false)
|
||||||
const [prompt, setPrompt] = createSignal<PromptRef>()
|
const [prompt, setPrompt] = createSignal<PromptRef>()
|
||||||
const bind = (r: PromptRef | undefined) => {
|
const bind = (r: PromptRef | undefined) => {
|
||||||
setPrompt(r)
|
setPrompt(r)
|
||||||
|
|
@ -314,6 +315,11 @@ export function Session() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateJumpToBottom = () => {
|
||||||
|
if (!scroll || scroll.isDestroyed) return
|
||||||
|
setShowJumpToBottom(scroll.scrollTop + scroll.viewport.height < scroll.scrollHeight - 1)
|
||||||
|
}
|
||||||
|
|
||||||
// Tail-first transcript mounting: only the newest rows mount when the session opens, and the
|
// Tail-first transcript mounting: only the newest rows mount when the session opens, and the
|
||||||
// rest backfill in chunks shortly after, so switching to a long session costs the visible tail
|
// rest backfill in chunks shortly after, so switching to a long session costs the visible tail
|
||||||
// instead of the whole transcript. Until backfill pins the count, the hidden span derives from
|
// instead of the whole transcript. Until backfill pins the count, the hidden span derives from
|
||||||
|
|
@ -322,6 +328,10 @@ export function Session() {
|
||||||
const [hiddenRows, setHiddenRows] = createSignal<number>()
|
const [hiddenRows, setHiddenRows] = createSignal<number>()
|
||||||
const hidden = createMemo(() => Math.max(0, Math.min(hiddenRows() ?? Infinity, rows.length - TRANSCRIPT_TAIL_ROWS)))
|
const hidden = createMemo(() => Math.max(0, Math.min(hiddenRows() ?? Infinity, rows.length - TRANSCRIPT_TAIL_ROWS)))
|
||||||
const visibleRows = createMemo(() => (hidden() === 0 ? rows : rows.slice(hidden())))
|
const visibleRows = createMemo(() => (hidden() === 0 ? rows : rows.slice(hidden())))
|
||||||
|
createEffect(() => {
|
||||||
|
visibleRows().length
|
||||||
|
afterLayout(updateJumpToBottom)
|
||||||
|
})
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const current = hidden()
|
const current = hidden()
|
||||||
if (current === 0) return
|
if (current === 0) return
|
||||||
|
|
@ -409,6 +419,7 @@ export function Session() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!scroll || scroll.isDestroyed) return
|
if (!scroll || scroll.isDestroyed) return
|
||||||
scroll.scrollTo(scroll.scrollHeight)
|
scroll.scrollTo(scroll.scrollHeight)
|
||||||
|
updateJumpToBottom()
|
||||||
}, 50)
|
}, 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -421,6 +432,7 @@ export function Session() {
|
||||||
run: () => {
|
run: () => {
|
||||||
clearMessageNavigation()
|
clearMessageNavigation()
|
||||||
scroll.scrollBy(-scroll.height / 2)
|
scroll.scrollBy(-scroll.height / 2)
|
||||||
|
updateJumpToBottom()
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -432,6 +444,7 @@ export function Session() {
|
||||||
run: () => {
|
run: () => {
|
||||||
clearMessageNavigation()
|
clearMessageNavigation()
|
||||||
scroll.scrollBy(scroll.height / 2)
|
scroll.scrollBy(scroll.height / 2)
|
||||||
|
updateJumpToBottom()
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -443,6 +456,7 @@ export function Session() {
|
||||||
run: () => {
|
run: () => {
|
||||||
clearMessageNavigation()
|
clearMessageNavigation()
|
||||||
scroll.scrollBy(-1)
|
scroll.scrollBy(-1)
|
||||||
|
updateJumpToBottom()
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -454,6 +468,7 @@ export function Session() {
|
||||||
run: () => {
|
run: () => {
|
||||||
clearMessageNavigation()
|
clearMessageNavigation()
|
||||||
scroll.scrollBy(1)
|
scroll.scrollBy(1)
|
||||||
|
updateJumpToBottom()
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -465,6 +480,7 @@ export function Session() {
|
||||||
run: () => {
|
run: () => {
|
||||||
clearMessageNavigation()
|
clearMessageNavigation()
|
||||||
scroll.scrollBy(-scroll.height / 4)
|
scroll.scrollBy(-scroll.height / 4)
|
||||||
|
updateJumpToBottom()
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -476,6 +492,7 @@ export function Session() {
|
||||||
run: () => {
|
run: () => {
|
||||||
clearMessageNavigation()
|
clearMessageNavigation()
|
||||||
scroll.scrollBy(scroll.height / 4)
|
scroll.scrollBy(scroll.height / 4)
|
||||||
|
updateJumpToBottom()
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -490,6 +507,7 @@ export function Session() {
|
||||||
run: () => {
|
run: () => {
|
||||||
clearMessageNavigation()
|
clearMessageNavigation()
|
||||||
scroll.scrollTo(0)
|
scroll.scrollTo(0)
|
||||||
|
updateJumpToBottom()
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -501,6 +519,7 @@ export function Session() {
|
||||||
run: () => {
|
run: () => {
|
||||||
clearMessageNavigation()
|
clearMessageNavigation()
|
||||||
scroll.scrollTo(scroll.scrollHeight)
|
scroll.scrollTo(scroll.scrollHeight)
|
||||||
|
updateJumpToBottom()
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -960,48 +979,67 @@ export function Session() {
|
||||||
<box flexDirection="row" flexGrow={1} minHeight={0}>
|
<box flexDirection="row" flexGrow={1} minHeight={0}>
|
||||||
<box flexGrow={1} minHeight={0} paddingBottom={1} paddingLeft={2} paddingRight={2} gap={1}>
|
<box flexGrow={1} minHeight={0} paddingBottom={1} paddingLeft={2} paddingRight={2} gap={1}>
|
||||||
<Show when={session()}>
|
<Show when={session()}>
|
||||||
<scrollbox
|
<box flexGrow={1} minHeight={0}>
|
||||||
ref={(r) => (scroll = r)}
|
<scrollbox
|
||||||
viewportOptions={{
|
id="session-transcript"
|
||||||
paddingRight: showScrollbar() ? 1 : 0,
|
ref={(r) => (scroll = r)}
|
||||||
}}
|
onMouseScroll={() => setTimeout(updateJumpToBottom, 0)}
|
||||||
verticalScrollbarOptions={{
|
viewportOptions={{
|
||||||
paddingLeft: 1,
|
paddingRight: showScrollbar() ? 1 : 0,
|
||||||
visible: showScrollbar(),
|
}}
|
||||||
trackOptions: {
|
verticalScrollbarOptions={{
|
||||||
backgroundColor: theme.raise(theme.background.surface.offset),
|
paddingLeft: 1,
|
||||||
foregroundColor: theme.border.default,
|
visible: showScrollbar(),
|
||||||
},
|
trackOptions: {
|
||||||
}}
|
backgroundColor: theme.raise(theme.background.surface.offset),
|
||||||
stickyScroll={!navigationMessage()}
|
foregroundColor: theme.border.default,
|
||||||
stickyStart="bottom"
|
},
|
||||||
flexGrow={1}
|
}}
|
||||||
scrollAcceleration={scrollAcceleration()}
|
stickyScroll={!navigationMessage()}
|
||||||
>
|
stickyStart="bottom"
|
||||||
<For each={visibleRows()}>
|
flexGrow={1}
|
||||||
{(row, index) => (
|
scrollAcceleration={scrollAcceleration()}
|
||||||
<SessionRowView
|
>
|
||||||
row={row}
|
<For each={visibleRows()}>
|
||||||
message={(messageID) => data.session.message.get(route.sessionID, messageID)}
|
{(row, index) => (
|
||||||
boundaryID={boundaries()[index() + hidden()]}
|
<SessionRowView
|
||||||
|
row={row}
|
||||||
|
message={(messageID) => data.session.message.get(route.sessionID, messageID)}
|
||||||
|
boundaryID={boundaries()[index() + hidden()]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
<BackgroundToolHint messages={messages()} />
|
||||||
|
<Show when={session()?.revert?.messageID}>
|
||||||
|
<RevertMessage
|
||||||
|
count={
|
||||||
|
messages().filter(
|
||||||
|
(message) => message.id >= session()!.revert!.messageID && message.type === "user",
|
||||||
|
).length
|
||||||
|
}
|
||||||
|
files={session()!.revert!.files ?? []}
|
||||||
/>
|
/>
|
||||||
)}
|
</Show>
|
||||||
</For>
|
<Show when={navigationSlack()}>
|
||||||
<BackgroundToolHint messages={messages()} />
|
{(height) => <box id={NAVIGATION_SLACK_ID} height={height()} flexShrink={0} />}
|
||||||
<Show when={session()?.revert?.messageID}>
|
</Show>
|
||||||
<RevertMessage
|
</scrollbox>
|
||||||
count={
|
<Show when={showJumpToBottom()}>
|
||||||
messages().filter(
|
<box
|
||||||
(message) => message.id >= session()!.revert!.messageID && message.type === "user",
|
id="session-jump-to-bottom"
|
||||||
).length
|
position="absolute"
|
||||||
}
|
zIndex={1000}
|
||||||
files={session()!.revert!.files ?? []}
|
right={showScrollbar() ? 2 : 0}
|
||||||
/>
|
bottom={0}
|
||||||
|
backgroundColor={theme.raise(theme.background.default)}
|
||||||
|
paddingLeft={1}
|
||||||
|
paddingRight={1}
|
||||||
|
onMouseUp={toBottom}
|
||||||
|
>
|
||||||
|
<text fg={theme.text.default}>↓ jump to bottom</text>
|
||||||
|
</box>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={navigationSlack()}>
|
</box>
|
||||||
{(height) => <box id={NAVIGATION_SLACK_ID} height={height()} flexShrink={0} />}
|
|
||||||
</Show>
|
|
||||||
</scrollbox>
|
|
||||||
<box flexShrink={0}>
|
<box flexShrink={0}>
|
||||||
<Composer
|
<Composer
|
||||||
sessionID={route.sessionID}
|
sessionID={route.sessionID}
|
||||||
|
|
@ -1034,9 +1072,6 @@ export function Session() {
|
||||||
visible={true}
|
visible={true}
|
||||||
ref={bind}
|
ref={bind}
|
||||||
disabled={false}
|
disabled={false}
|
||||||
onSubmit={() => {
|
|
||||||
toBottom()
|
|
||||||
}}
|
|
||||||
sessionID={route.sessionID}
|
sessionID={route.sessionID}
|
||||||
/>
|
/>
|
||||||
</Match>
|
</Match>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue