Kaynağa Gözat

chore: permissions ux

Adam 1 ay önce
ebeveyn
işleme
1b5bf32ce5

+ 32 - 6
packages/app/src/pages/layout.tsx

@@ -41,7 +41,7 @@ import {
 } from "@thisbeyond/solid-dnd"
 import type { DragEvent } from "@thisbeyond/solid-dnd"
 import { useProviders } from "@/hooks/use-providers"
-import { showToast, Toast } from "@opencode-ai/ui/toast"
+import { showToast, Toast, toaster } from "@opencode-ai/ui/toast"
 import { useGlobalSDK } from "@/context/global-sdk"
 import { useNotification } from "@/context/notification"
 import { Binary } from "@opencode-ai/util/binary"
@@ -118,13 +118,15 @@ export default function Layout(props: ParentProps) {
   })
 
   onMount(() => {
-    const seenPermissions = new Set<string>()
+    const seenSessions = new Set<string>()
+    const toastBySession = new Map<string, number>()
     const unsub = globalSDK.event.listen((e) => {
       if (e.details?.type !== "permission.updated") return
       const directory = e.name
       const permission = e.details.properties
-      if (seenPermissions.has(permission.id)) return
-      seenPermissions.add(permission.id)
+      const sessionKey = `${directory}:${permission.sessionID}`
+      if (seenSessions.has(sessionKey)) return
+      seenSessions.add(sessionKey)
       const currentDir = params.dir ? base64Decode(params.dir) : undefined
       const currentSession = params.id
       if (directory === currentDir && permission.sessionID === currentSession) return
@@ -133,7 +135,7 @@ export default function Layout(props: ParentProps) {
       if (directory === currentDir && session?.parentID === currentSession) return
       const sessionTitle = session?.title ?? "New session"
       const projectName = getFilename(directory)
-      showToast({
+      const toastId = showToast({
         persistent: true,
         icon: "checklist",
         title: "Permission required",
@@ -144,7 +146,6 @@ export default function Layout(props: ParentProps) {
             onClick: () => {
               navigate(`/${base64Encode(directory)}/session/${permission.sessionID}`)
             },
-            dismissAfter: true,
           },
           {
             label: "Dismiss",
@@ -152,8 +153,33 @@ export default function Layout(props: ParentProps) {
           },
         ],
       })
+      toastBySession.set(sessionKey, toastId)
     })
     onCleanup(unsub)
+
+    createEffect(() => {
+      const currentDir = params.dir ? base64Decode(params.dir) : undefined
+      const currentSession = params.id
+      if (!currentDir || !currentSession) return
+      const sessionKey = `${currentDir}:${currentSession}`
+      const toastId = toastBySession.get(sessionKey)
+      if (toastId !== undefined) {
+        toaster.dismiss(toastId)
+        toastBySession.delete(sessionKey)
+        seenSessions.delete(sessionKey)
+      }
+      const [store] = globalSync.child(currentDir)
+      const childSessions = store.session.filter((s) => s.parentID === currentSession)
+      for (const child of childSessions) {
+        const childKey = `${currentDir}:${child.id}`
+        const childToastId = toastBySession.get(childKey)
+        if (childToastId !== undefined) {
+          toaster.dismiss(childToastId)
+          toastBySession.delete(childKey)
+          seenSessions.delete(childKey)
+        }
+      }
+    })
   })
 
   function sortSessions(a: Session, b: Session) {

+ 6 - 0
packages/ui/src/components/session-turn.tsx

@@ -328,6 +328,12 @@ export function SessionTurn(
     }
   })
 
+  createEffect(() => {
+    if (permissionParts().length > 0) {
+      autoScroll.forceScrollToBottom()
+    }
+  })
+
   createEffect(() => {
     if (working() || !isLastUserMessage()) return
 

+ 1 - 4
packages/ui/src/components/toast.tsx

@@ -92,7 +92,6 @@ export type ToastVariant = "default" | "success" | "error" | "loading"
 export interface ToastAction {
   label: string
   onClick: "dismiss" | (() => void)
-  dismissAfter?: boolean
 }
 
 export interface ToastOptions {
@@ -132,10 +131,8 @@ export function showToast(options: ToastOptions | string) {
                 onClick={() => {
                   if (typeof action.onClick === "function") {
                     action.onClick()
-                    if (action.dismissAfter) toaster.dismiss(props.toastId)
-                  } else {
-                    toaster.dismiss(props.toastId)
                   }
+                  toaster.dismiss(props.toastId)
                 }}
               >
                 {action.label}

+ 17 - 0
packages/ui/src/hooks/create-auto-scroll.tsx

@@ -35,6 +35,22 @@ export function createAutoScroll(options: AutoScrollOptions) {
     })
   }
 
+  function forceScrollToBottom() {
+    if (!scrollRef) return
+
+    setStore("userScrolled", false)
+    isAutoScrolling = true
+    if (autoScrollTimeout) clearTimeout(autoScrollTimeout)
+    autoScrollTimeout = setTimeout(() => {
+      isAutoScrolling = false
+    }, 1000)
+
+    scrollRef.scrollTo({
+      top: scrollRef.scrollHeight,
+      behavior: "smooth",
+    })
+  }
+
   function handleScroll() {
     if (!scrollRef) return
 
@@ -161,6 +177,7 @@ export function createAutoScroll(options: AutoScrollOptions) {
     handleScroll,
     handleInteraction,
     scrollToBottom,
+    forceScrollToBottom,
     userScrolled: () => store.userScrolled,
   }
 }