diff options
| author | Adam <[email protected]> | 2025-12-29 20:54:33 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2025-12-30 04:57:35 -0600 |
| commit | fa1ac7bc957f3b8b13b97e85eac40729f16b510b (patch) | |
| tree | 0e65db62401b8057019222a8d05f6b9de452bffb /packages/app/src/context | |
| parent | c82ab649e2307237b480a94dbb7df6d77a8bf71a (diff) | |
| download | opencode-fa1ac7bc957f3b8b13b97e85eac40729f16b510b.tar.gz opencode-fa1ac7bc957f3b8b13b97e85eac40729f16b510b.zip | |
feat(desktop): system notifications
Diffstat (limited to 'packages/app/src/context')
| -rw-r--r-- | packages/app/src/context/notification.tsx | 25 | ||||
| -rw-r--r-- | packages/app/src/context/platform.tsx | 3 |
2 files changed, 19 insertions, 9 deletions
diff --git a/packages/app/src/context/notification.tsx b/packages/app/src/context/notification.tsx index 2b258ebd6..33d72d4f2 100644 --- a/packages/app/src/context/notification.tsx +++ b/packages/app/src/context/notification.tsx @@ -2,7 +2,9 @@ import { createStore } from "solid-js/store" import { createSimpleContext } from "@opencode-ai/ui/context" import { useGlobalSDK } from "./global-sdk" import { useGlobalSync } from "./global-sync" +import { usePlatform } from "@/context/platform" import { Binary } from "@opencode-ai/util/binary" +import { base64Encode } from "@opencode-ai/util/encode" import { EventSessionError } from "@opencode-ai/sdk/v2" import { makeAudioPlayer } from "@solid-primitives/audio" import idleSound from "@opencode-ai/ui/audio/staplebops-01.aac" @@ -43,6 +45,7 @@ export const { use: useNotification, provider: NotificationProvider } = createSi const globalSDK = useGlobalSDK() const globalSync = useGlobalSync() + const platform = usePlatform() const [store, setStore, _, ready] = persisted( "notification.v1", @@ -64,8 +67,8 @@ export const { use: useNotification, provider: NotificationProvider } = createSi 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 + const session = match.found ? syncStore.session[match.index] : undefined + if (session?.parentID) break try { idlePlayer?.play() } catch {} @@ -74,25 +77,29 @@ export const { use: useNotification, provider: NotificationProvider } = createSi type: "turn-complete", session: sessionID, }) + const href = `/${base64Encode(directory)}/session/${sessionID}` + void platform.notify("Response ready", session?.title ?? sessionID, href) 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 - } + const [syncStore] = globalSync.child(directory) + const match = sessionID ? Binary.search(syncStore.session, sessionID, (s) => s.id) : undefined + const session = sessionID && match?.found ? syncStore.session[match.index] : undefined + if (session?.parentID) break try { errorPlayer?.play() } catch {} + const error = "error" in event.properties ? event.properties.error : undefined setStore("list", store.list.length, { ...base, type: "error", session: sessionID ?? "global", - error: "error" in event.properties ? event.properties.error : undefined, + error, }) + const description = session?.title ?? (typeof error === "string" ? error : "An error occurred") + const href = sessionID ? `/${base64Encode(directory)}/session/${sessionID}` : `/${base64Encode(directory)}` + void platform.notify("Session error", description, href) break } } diff --git a/packages/app/src/context/platform.tsx b/packages/app/src/context/platform.tsx index 2b710e6f2..85afd1e1f 100644 --- a/packages/app/src/context/platform.tsx +++ b/packages/app/src/context/platform.tsx @@ -14,6 +14,9 @@ export type Platform = { /** Restart the app */ restart(): Promise<void> + /** Send a system notification (optional deep link) */ + notify(title: string, description?: string, href?: string): Promise<void> + /** Open native directory picker dialog (Tauri only) */ openDirectoryPickerDialog?(opts?: { title?: string; multiple?: boolean }): Promise<string | string[] | null> |
