Просмотр исходного кода

fix(desktop): "load more" button behavior in desktop sidebar (#8430)

Shane Bishop 2 месяцев назад
Родитель
Сommit
077ca4454f

+ 9 - 3
packages/app/src/context/global-sync.tsx

@@ -38,6 +38,7 @@ type State = {
   config: Config
   config: Config
   path: Path
   path: Path
   session: Session[]
   session: Session[]
+  sessionTotal: number
   session_status: {
   session_status: {
     [sessionID: string]: SessionStatus
     [sessionID: string]: SessionStatus
   }
   }
@@ -98,6 +99,7 @@ function createGlobalSync() {
         agent: [],
         agent: [],
         command: [],
         command: [],
         session: [],
         session: [],
+        sessionTotal: 0,
         session_status: {},
         session_status: {},
         session_diff: {},
         session_diff: {},
         todo: {},
         todo: {},
@@ -117,8 +119,10 @@ function createGlobalSync() {
 
 
   async function loadSessions(directory: string) {
   async function loadSessions(directory: string) {
     const [store, setStore] = child(directory)
     const [store, setStore] = child(directory)
-    globalSDK.client.session
-      .list({ directory })
+    const limit = store.limit
+
+    return globalSDK.client.session
+      .list({ directory, roots: true })
       .then((x) => {
       .then((x) => {
         const fourHoursAgo = Date.now() - 4 * 60 * 60 * 1000
         const fourHoursAgo = Date.now() - 4 * 60 * 60 * 1000
         const nonArchived = (x.data ?? [])
         const nonArchived = (x.data ?? [])
@@ -128,10 +132,12 @@ function createGlobalSync() {
           .sort((a, b) => a.id.localeCompare(b.id))
           .sort((a, b) => a.id.localeCompare(b.id))
         // Include up to the limit, plus any updated in the last 4 hours
         // Include up to the limit, plus any updated in the last 4 hours
         const sessions = nonArchived.filter((s, i) => {
         const sessions = nonArchived.filter((s, i) => {
-          if (i < store.limit) return true
+          if (i < limit) return true
           const updated = new Date(s.time?.updated ?? s.time?.created).getTime()
           const updated = new Date(s.time?.updated ?? s.time?.created).getTime()
           return updated > fourHoursAgo
           return updated > fourHoursAgo
         })
         })
+        // Store total session count (used for "load more" pagination)
+        setStore("sessionTotal", nonArchived.length)
         setStore("session", reconcile(sessions, { key: "id" }))
         setStore("session", reconcile(sessions, { key: "id" }))
       })
       })
       .catch((err) => {
       .catch((err) => {

+ 1 - 1
packages/app/src/pages/layout.tsx

@@ -944,7 +944,7 @@ export default function Layout(props: ParentProps) {
         .toSorted(sortSessions),
         .toSorted(sortSessions),
     )
     )
     const rootSessions = createMemo(() => sessions().filter((s) => !s.parentID))
     const rootSessions = createMemo(() => sessions().filter((s) => !s.parentID))
-    const hasMoreSessions = createMemo(() => store.session.length >= store.limit)
+    const hasMoreSessions = createMemo(() => store.sessionTotal > store.session.length)
     const loadMoreSessions = async () => {
     const loadMoreSessions = async () => {
       setProjectStore("limit", (limit) => limit + 5)
       setProjectStore("limit", (limit) => limit + 5)
       await globalSync.project.loadSessions(props.project.worktree)
       await globalSync.project.loadSessions(props.project.worktree)

+ 4 - 0
packages/opencode/src/server/server.ts

@@ -724,6 +724,8 @@ export namespace Server {
           validator(
           validator(
             "query",
             "query",
             z.object({
             z.object({
+              directory: z.string().optional().meta({ description: "Filter sessions by project directory" }),
+              roots: z.coerce.boolean().optional().meta({ description: "Only return root sessions (no parentID)" }),
               start: z.coerce
               start: z.coerce
                 .number()
                 .number()
                 .optional()
                 .optional()
@@ -737,6 +739,8 @@ export namespace Server {
             const term = query.search?.toLowerCase()
             const term = query.search?.toLowerCase()
             const sessions: Session.Info[] = []
             const sessions: Session.Info[] = []
             for await (const session of Session.list()) {
             for await (const session of Session.list()) {
+              if (query.directory !== undefined && session.directory !== query.directory) continue
+              if (query.roots && session.parentID) continue
               if (query.start !== undefined && session.time.updated < query.start) continue
               if (query.start !== undefined && session.time.updated < query.start) continue
               if (term !== undefined && !session.title.toLowerCase().includes(term)) continue
               if (term !== undefined && !session.title.toLowerCase().includes(term)) continue
               sessions.push(session)
               sessions.push(session)

+ 39 - 0
packages/opencode/test/server/session-list.test.ts

@@ -0,0 +1,39 @@
+import { describe, expect, test } from "bun:test"
+import path from "path"
+import { Instance } from "../../src/project/instance"
+import { Server } from "../../src/server/server"
+import { Session } from "../../src/session"
+import { Log } from "../../src/util/log"
+
+const projectRoot = path.join(__dirname, "../..")
+Log.init({ print: false })
+
+describe("session.list", () => {
+  test("filters by directory", async () => {
+    await Instance.provide({
+      directory: projectRoot,
+      fn: async () => {
+        const app = Server.App()
+
+        const first = await Session.create({})
+
+        const otherDir = path.join(projectRoot, "..", "__session_list_other")
+        const second = await Instance.provide({
+          directory: otherDir,
+          fn: async () => Session.create({}),
+        })
+
+        const response = await app.request(`/session?directory=${encodeURIComponent(projectRoot)}`)
+        expect(response.status).toBe(200)
+
+        const body = (await response.json()) as unknown[]
+        const ids = body
+          .map((s) => (typeof s === "object" && s && "id" in s ? (s as { id: string }).id : undefined))
+          .filter((x): x is string => typeof x === "string")
+
+        expect(ids).toContain(first.id)
+        expect(ids).not.toContain(second.id)
+      },
+    })
+  })
+})

+ 2 - 0
packages/sdk/js/src/v2/gen/sdk.gen.ts

@@ -781,6 +781,7 @@ export class Session extends HeyApiClient {
   public list<ThrowOnError extends boolean = false>(
   public list<ThrowOnError extends boolean = false>(
     parameters?: {
     parameters?: {
       directory?: string
       directory?: string
+      roots?: boolean
       start?: number
       start?: number
       search?: string
       search?: string
       limit?: number
       limit?: number
@@ -793,6 +794,7 @@ export class Session extends HeyApiClient {
         {
         {
           args: [
           args: [
             { in: "query", key: "directory" },
             { in: "query", key: "directory" },
+            { in: "query", key: "roots" },
             { in: "query", key: "start" },
             { in: "query", key: "start" },
             { in: "query", key: "search" },
             { in: "query", key: "search" },
             { in: "query", key: "limit" },
             { in: "query", key: "limit" },

+ 7 - 0
packages/sdk/js/src/v2/gen/types.gen.ts

@@ -2589,7 +2589,14 @@ export type SessionListData = {
   body?: never
   body?: never
   path?: never
   path?: never
   query?: {
   query?: {
+    /**
+     * Filter sessions by project directory
+     */
     directory?: string
     directory?: string
+    /**
+     * Only return root sessions (no parentID)
+     */
+    roots?: boolean
     /**
     /**
      * Filter sessions updated on or after this timestamp (milliseconds since epoch)
      * Filter sessions updated on or after this timestamp (milliseconds since epoch)
      */
      */