wip(desktop): progress

This commit is contained in:
Adam 2025-12-15 10:13:29 -06:00
parent 8cb26b6066
commit 8ce0966987
No known key found for this signature in database
GPG key ID: 9CB48779AF150E75
3 changed files with 38 additions and 8 deletions

View file

@ -100,7 +100,7 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
async function loadSessions(directory: string) {
globalSDK.client.session.list({ directory }).then((x) => {
const oneHourAgo = Date.now() - 60 * 60 * 1000
const fourHoursAgo = Date.now() - 4 * 60 * 60 * 1000
const nonArchived = (x.data ?? [])
.slice()
.filter((s) => !s.time.archived)
@ -109,7 +109,7 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
const sessions = nonArchived.filter((s, i) => {
if (i < 5) return true
const updated = new Date(s.time.updated).getTime()
return updated > oneHourAgo
return updated > fourHoursAgo
})
const [, setStore] = child(directory)
setStore("session", sessions)

View file

@ -2,6 +2,8 @@ import { createStore } from "solid-js/store"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { makePersisted } from "@solid-primitives/storage"
import { useGlobalSDK } from "./global-sdk"
import { useGlobalSync } from "./global-sync"
import { Binary } from "@opencode-ai/util/binary"
import { EventSessionError } from "@opencode-ai/sdk/v2"
import { makeAudioPlayer } from "@solid-primitives/audio"
import idleSound from "@opencode-ai/ui/audio/staplebops-01.aac"
@ -32,6 +34,7 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
const idlePlayer = makeAudioPlayer(idleSound)
const errorPlayer = makeAudioPlayer(errorSound)
const globalSDK = useGlobalSDK()
const globalSync = useGlobalSync()
const [store, setStore] = makePersisted(
createStore({
@ -57,22 +60,32 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
}
switch (event.type) {
case "session.idle": {
const sessionID = event.properties.sessionID
const [syncStore] = globalSync.child(directory)
const match = Binary.search(syncStore.session, sessionID, (s) => s.id)
const isChild = match.found && syncStore.session[match.index].parentID
if (isChild) break
idlePlayer.play()
const session = event.properties.sessionID
setStore("list", store.list.length, {
...base,
type: "turn-complete",
session,
session: sessionID,
})
break
}
case "session.error": {
const sessionID = event.properties.sessionID
if (sessionID) {
const [syncStore] = globalSync.child(directory)
const match = Binary.search(syncStore.session, sessionID, (s) => s.id)
const isChild = match.found && syncStore.session[match.index].parentID
if (isChild) break
}
errorPlayer.play()
const session = event.properties.sessionID ?? "global"
setStore("list", store.list.length, {
...base,
type: "error",
session,
session: sessionID ?? "global",
error: "error" in event.properties ? event.properties.error : undefined,
})
break

View file

@ -44,6 +44,16 @@ export default function Layout(props: ParentProps) {
activeDraggable: undefined as string | undefined,
})
let scrollContainerRef: HTMLDivElement | undefined
function scrollToSession(sessionId: string) {
if (!scrollContainerRef) return
const element = scrollContainerRef.querySelector(`[data-session-id="${sessionId}"]`)
if (element) {
element.scrollIntoView({ block: "center", behavior: "smooth" })
}
}
const params = useParams()
const globalSDK = useGlobalSDK()
const globalSync = useGlobalSync()
@ -111,7 +121,9 @@ export default function Layout(props: ParentProps) {
// If target is within bounds, navigate to that session
if (targetIndex >= 0 && targetIndex < sessions.length) {
navigateToSession(sessions[targetIndex])
const session = sessions[targetIndex]
navigateToSession(session)
queueMicrotask(() => scrollToSession(session.id))
return
}
@ -130,6 +142,7 @@ export default function Layout(props: ParentProps) {
// If going down (offset > 0), go to first session; if going up (offset < 0), go to last session
const targetSession = offset > 0 ? nextProjectSessions[0] : nextProjectSessions[nextProjectSessions.length - 1]
navigate(`/${base64Encode(nextProject.worktree)}/session/${targetSession.id}`)
queueMicrotask(() => scrollToSession(targetSession.id))
}
async function archiveSession(session: Session) {
@ -418,6 +431,7 @@ export default function Layout(props: ParentProps) {
return (
<>
<div
data-session-id={props.session.id}
class="group/session relative w-full pr-2 py-1 rounded-md cursor-default transition-colors
hover:bg-surface-raised-base-hover focus-within:bg-surface-raised-base-hover has-[.active]:bg-surface-raised-base-hover"
style={{ "padding-left": `${16 + depth * 12}px` }}
@ -670,7 +684,10 @@ export default function Layout(props: ParentProps) {
>
<DragDropSensors />
<ConstrainDragXAxis />
<div class="w-full min-w-8 flex flex-col gap-2 min-h-0 overflow-y-auto no-scrollbar">
<div
ref={scrollContainerRef}
class="w-full min-w-8 flex flex-col gap-2 min-h-0 overflow-y-auto no-scrollbar"
>
<SortableProvider ids={layout.projects.list().map((p) => p.worktree)}>
<For each={layout.projects.list()}>{(project) => <SortableProject project={project} />}</For>
</SortableProvider>