fix(desktop): expanded states

This commit is contained in:
Adam 2025-12-18 14:31:13 -06:00
parent 15931fa170
commit 0ebcaff927
No known key found for this signature in database
GPG key ID: 9CB48779AF150E75
2 changed files with 58 additions and 39 deletions

View file

@ -1,4 +1,17 @@
import { For, onCleanup, onMount, Show, Match, Switch, createResource, createMemo, createEffect, on } from "solid-js"
import {
For,
onCleanup,
onMount,
Show,
Match,
Switch,
createResource,
createMemo,
createEffect,
on,
createRenderEffect,
batch,
} from "solid-js"
import { Dynamic } from "solid-js/web"
import { useLocal, type LocalFile } from "@/context/local"
import { createStore } from "solid-js/store"
@ -130,7 +143,8 @@ export default function Page() {
clickTimer: undefined as number | undefined,
activeDraggable: undefined as string | undefined,
activeTerminalDraggable: undefined as string | undefined,
stepsExpanded: false,
userInteracted: false,
stepsExpanded: true,
})
let inputRef!: HTMLDivElement
@ -159,7 +173,28 @@ export default function Page() {
),
)
createEffect(() => {
params.id
const status = sync.data.session_status[params.id ?? ""] ?? { type: "idle" }
batch(() => {
setStore("userInteracted", false)
setStore("stepsExpanded", status.type !== "idle")
})
})
const status = createMemo(() => sync.data.session_status[params.id ?? ""] ?? { type: "idle" })
const working = createMemo(() => status().type !== "idle" && activeMessage()?.id === lastUserMessage()?.id)
createRenderEffect((prev) => {
const isWorking = working()
if (!prev && isWorking) {
setStore("stepsExpanded", true)
}
if (prev && !isWorking && !store.userInteracted) {
setStore("stepsExpanded", false)
}
return isWorking
}, working())
command.register(() => [
{
@ -619,7 +654,8 @@ export default function Page() {
sessionID={params.id!}
messageID={activeMessage()!.id}
stepsExpanded={store.stepsExpanded}
onStepsExpandedChange={(expanded) => setStore("stepsExpanded", expanded)}
onStepsExpandedToggle={() => setStore("stepsExpanded", (x) => !x)}
onUserInteracted={() => setStore("userInteracted", true)}
classes={{
root: "pb-20 flex-1 min-w-0",
content: "pb-20",

View file

@ -25,7 +25,8 @@ export function SessionTurn(
sessionID: string
messageID: string
stepsExpanded?: boolean
onStepsExpandedChange?: (expanded: boolean) => void
onStepsExpandedToggle?: () => void
onUserInteracted?: () => void
classes?: {
root?: string
content?: string
@ -171,7 +172,6 @@ export function SessionTurn(
stickyHeaderHeight: 0,
retrySeconds: 0,
status: rawStatus(),
stepsExpanded: props.stepsExpanded ?? working(),
duration: duration(),
})
@ -192,18 +192,26 @@ export function SessionTurn(
function handleScroll() {
if (!scrollRef || store.autoScrolled) return
const { scrollTop } = scrollRef
// only mark as user scrolled if they actively scrolled upward
// content growth increases scrollHeight but never decreases scrollTop
const scrollTop = scrollRef.scrollTop
const reset = scrollTop <= 0 && store.lastScrollTop > 100 && working() && !store.userScrolled
if (reset) {
setStore("lastScrollTop", scrollTop)
requestAnimationFrame(scrollToBottom)
return
}
const scrolledUp = scrollTop < store.lastScrollTop - 10
if (scrolledUp && working()) {
setStore("userScrolled", true)
props.onUserInteracted?.()
}
setStore("lastScrollTop", scrollTop)
}
function handleInteraction() {
if (working()) setStore("userScrolled", true)
if (working()) {
setStore("userScrolled", true)
props.onUserInteracted?.()
}
}
function scrollToBottom() {
@ -242,12 +250,6 @@ export function SessionTurn(
},
)
createEffect(() => {
if (props.stepsExpanded !== undefined) {
setStore("stepsExpanded", props.stepsExpanded)
}
})
createEffect(() => {
const timer = setInterval(() => {
setStore("duration", duration())
@ -262,7 +264,6 @@ export function SessionTurn(
if (newStatus === store.status || !newStatus) return
const timeSinceLastChange = Date.now() - lastStatusChange
if (timeSinceLastChange >= 2500) {
setStore("status", newStatus)
lastStatusChange = Date.now()
@ -280,19 +281,6 @@ export function SessionTurn(
}
})
createEffect((prev) => {
const isWorking = working()
if (!prev && isWorking) {
setStore("stepsExpanded", true)
props.onStepsExpandedChange?.(true)
}
if (prev && !isWorking && !store.userScrolled) {
setStore("stepsExpanded", false)
props.onStepsExpandedChange?.(false)
}
return isWorking
}, working())
return (
<div data-component="session-turn" class={props.classes?.root}>
<div ref={scrollRef} onScroll={handleScroll} data-slot="session-turn-content" class={props.classes?.content}>
@ -336,12 +324,7 @@ export function SessionTurn(
data-slot="session-turn-collapsible-trigger-content"
variant="ghost"
size="small"
onClick={() => {
if (assistantMessages().length === 0) return
const next = !store.stepsExpanded
setStore("stepsExpanded", next)
props.onStepsExpandedChange?.(next)
}}
onClick={props.onStepsExpandedToggle ?? (() => {})}
>
<Show when={working()}>
<Spinner />
@ -361,8 +344,8 @@ export function SessionTurn(
<span data-slot="session-turn-retry-attempt">(#{retry()?.attempt})</span>
</Match>
<Match when={working()}>{store.status ?? "Considering next steps"}</Match>
<Match when={store.stepsExpanded}>Hide steps</Match>
<Match when={!store.stepsExpanded}>Show steps</Match>
<Match when={props.stepsExpanded}>Hide steps</Match>
<Match when={!props.stepsExpanded}>Show steps</Match>
</Switch>
<span>·</span>
<span>{store.duration}</span>
@ -373,7 +356,7 @@ export function SessionTurn(
</div>
</Show>
{/* Response */}
<Show when={store.stepsExpanded && assistantMessages().length > 0}>
<Show when={props.stepsExpanded && assistantMessages().length > 0}>
<div data-slot="session-turn-collapsible-content-inner">
<For each={assistantMessages()}>
{(assistantMessage) => {
@ -472,7 +455,7 @@ export function SessionTurn(
</Accordion>
</div>
</Show>
<Show when={error() && !store.stepsExpanded}>
<Show when={error() && !props.stepsExpanded}>
<Card variant="error" class="error-card">
{error()?.data?.message as string}
</Card>