Explorar el Código

wip(desktop): progress

Adam hace 2 meses
padre
commit
8ce0966987

+ 2 - 2
packages/desktop/src/context/global-sync.tsx

@@ -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)

+ 17 - 4
packages/desktop/src/context/notification.tsx

@@ -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

+ 19 - 2
packages/desktop/src/pages/layout.tsx

@@ -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>