wip(app): settings
This commit is contained in:
@@ -3,6 +3,7 @@ import { Select } from "@opencode-ai/ui/select"
|
|||||||
import { Switch } from "@opencode-ai/ui/switch"
|
import { Switch } from "@opencode-ai/ui/switch"
|
||||||
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
|
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
|
||||||
import { useSettings } from "@/context/settings"
|
import { useSettings } from "@/context/settings"
|
||||||
|
import { playSound, SOUND_OPTIONS } from "@/utils/sound"
|
||||||
|
|
||||||
export const SettingsGeneral: Component = () => {
|
export const SettingsGeneral: Component = () => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
@@ -20,11 +21,20 @@ export const SettingsGeneral: Component = () => {
|
|||||||
|
|
||||||
const fontOptions = [
|
const fontOptions = [
|
||||||
{ value: "ibm-plex-mono", label: "IBM Plex Mono" },
|
{ value: "ibm-plex-mono", label: "IBM Plex Mono" },
|
||||||
|
{ value: "cascadia-code", label: "Cascadia Code" },
|
||||||
{ value: "fira-code", label: "Fira Code" },
|
{ value: "fira-code", label: "Fira Code" },
|
||||||
|
{ value: "hack", label: "Hack" },
|
||||||
|
{ value: "inconsolata", label: "Inconsolata" },
|
||||||
|
{ value: "intel-one-mono", label: "Intel One Mono" },
|
||||||
{ value: "jetbrains-mono", label: "JetBrains Mono" },
|
{ value: "jetbrains-mono", label: "JetBrains Mono" },
|
||||||
|
{ value: "meslo-lgs", label: "Meslo LGS" },
|
||||||
|
{ value: "roboto-mono", label: "Roboto Mono" },
|
||||||
{ value: "source-code-pro", label: "Source Code Pro" },
|
{ value: "source-code-pro", label: "Source Code Pro" },
|
||||||
|
{ value: "ubuntu-mono", label: "Ubuntu Mono" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const soundOptions = [...SOUND_OPTIONS]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="flex flex-col h-full overflow-y-auto no-scrollbar">
|
<div class="flex flex-col h-full overflow-y-auto no-scrollbar">
|
||||||
<div class="flex flex-col gap-8 p-8 max-w-[720px]">
|
<div class="flex flex-col gap-8 p-8 max-w-[720px]">
|
||||||
@@ -110,6 +120,59 @@ export const SettingsGeneral: Component = () => {
|
|||||||
/>
|
/>
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Sound effects Section */}
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<h3 class="text-14-medium text-text-strong pb-2">Sound effects</h3>
|
||||||
|
|
||||||
|
<SettingsRow title="Agent" description="Play sound when the agent is complete or needs attention">
|
||||||
|
<Select
|
||||||
|
options={soundOptions}
|
||||||
|
current={soundOptions.find((o) => o.id === settings.sounds.agent())}
|
||||||
|
value={(o) => o.id}
|
||||||
|
label={(o) => o.label}
|
||||||
|
onSelect={(option) => {
|
||||||
|
if (!option) return
|
||||||
|
settings.sounds.setAgent(option.id)
|
||||||
|
playSound(option.src)
|
||||||
|
}}
|
||||||
|
variant="secondary"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</SettingsRow>
|
||||||
|
|
||||||
|
<SettingsRow title="Permissions" description="Play sound when a permission is required">
|
||||||
|
<Select
|
||||||
|
options={soundOptions}
|
||||||
|
current={soundOptions.find((o) => o.id === settings.sounds.permissions())}
|
||||||
|
value={(o) => o.id}
|
||||||
|
label={(o) => o.label}
|
||||||
|
onSelect={(option) => {
|
||||||
|
if (!option) return
|
||||||
|
settings.sounds.setPermissions(option.id)
|
||||||
|
playSound(option.src)
|
||||||
|
}}
|
||||||
|
variant="secondary"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</SettingsRow>
|
||||||
|
|
||||||
|
<SettingsRow title="Errors" description="Play sound when an error occurs">
|
||||||
|
<Select
|
||||||
|
options={soundOptions}
|
||||||
|
current={soundOptions.find((o) => o.id === settings.sounds.errors())}
|
||||||
|
value={(o) => o.id}
|
||||||
|
label={(o) => o.label}
|
||||||
|
onSelect={(option) => {
|
||||||
|
if (!option) return
|
||||||
|
settings.sounds.setErrors(option.id)
|
||||||
|
playSound(option.src)
|
||||||
|
}}
|
||||||
|
variant="secondary"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</SettingsRow>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { Ghostty, Terminal as Term, FitAddon } from "ghostty-web"
|
import type { Ghostty, Terminal as Term, FitAddon } from "ghostty-web"
|
||||||
import { ComponentProps, createEffect, createSignal, onCleanup, onMount, splitProps } from "solid-js"
|
import { ComponentProps, createEffect, createSignal, onCleanup, onMount, splitProps } from "solid-js"
|
||||||
import { useSDK } from "@/context/sdk"
|
import { useSDK } from "@/context/sdk"
|
||||||
|
import { monoFontFamily, useSettings } from "@/context/settings"
|
||||||
import { SerializeAddon } from "@/addons/serialize"
|
import { SerializeAddon } from "@/addons/serialize"
|
||||||
import { LocalPTY } from "@/context/terminal"
|
import { LocalPTY } from "@/context/terminal"
|
||||||
import { resolveThemeVariant, useTheme, withAlpha, type HexColor } from "@opencode-ai/ui/theme"
|
import { resolveThemeVariant, useTheme, withAlpha, type HexColor } from "@opencode-ai/ui/theme"
|
||||||
@@ -36,6 +37,7 @@ const DEFAULT_TERMINAL_COLORS: Record<"light" | "dark", TerminalColors> = {
|
|||||||
|
|
||||||
export const Terminal = (props: TerminalProps) => {
|
export const Terminal = (props: TerminalProps) => {
|
||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
|
const settings = useSettings()
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
let container!: HTMLDivElement
|
let container!: HTMLDivElement
|
||||||
const [local, others] = splitProps(props, ["pty", "class", "classList", "onConnectError"])
|
const [local, others] = splitProps(props, ["pty", "class", "classList", "onConnectError"])
|
||||||
@@ -82,6 +84,14 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
setOption("theme", colors)
|
setOption("theme", colors)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const font = monoFontFamily(settings.appearance.font())
|
||||||
|
if (!term) return
|
||||||
|
const setOption = (term as unknown as { setOption?: (key: string, value: string) => void }).setOption
|
||||||
|
if (!setOption) return
|
||||||
|
setOption("fontFamily", font)
|
||||||
|
})
|
||||||
|
|
||||||
const focusTerminal = () => {
|
const focusTerminal = () => {
|
||||||
const t = term
|
const t = term
|
||||||
if (!t) return
|
if (!t) return
|
||||||
@@ -112,7 +122,7 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
cursorStyle: "bar",
|
cursorStyle: "bar",
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontFamily: "IBM Plex Mono, monospace",
|
fontFamily: monoFontFamily(settings.appearance.font()),
|
||||||
allowTransparency: true,
|
allowTransparency: true,
|
||||||
theme: terminalColors(),
|
theme: terminalColors(),
|
||||||
scrollback: 10_000,
|
scrollback: 10_000,
|
||||||
|
|||||||
@@ -4,13 +4,12 @@ import { createSimpleContext } from "@opencode-ai/ui/context"
|
|||||||
import { useGlobalSDK } from "./global-sdk"
|
import { useGlobalSDK } from "./global-sdk"
|
||||||
import { useGlobalSync } from "./global-sync"
|
import { useGlobalSync } from "./global-sync"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
|
import { useSettings } from "@/context/settings"
|
||||||
import { Binary } from "@opencode-ai/util/binary"
|
import { Binary } from "@opencode-ai/util/binary"
|
||||||
import { base64Encode } from "@opencode-ai/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { EventSessionError } from "@opencode-ai/sdk/v2"
|
import { EventSessionError } from "@opencode-ai/sdk/v2"
|
||||||
import { makeAudioPlayer } from "@solid-primitives/audio"
|
|
||||||
import idleSound from "@opencode-ai/ui/audio/staplebops-01.aac"
|
|
||||||
import errorSound from "@opencode-ai/ui/audio/nope-03.aac"
|
|
||||||
import { Persist, persisted } from "@/utils/persist"
|
import { Persist, persisted } from "@/utils/persist"
|
||||||
|
import { playSound, soundSrc } from "@/utils/sound"
|
||||||
|
|
||||||
type NotificationBase = {
|
type NotificationBase = {
|
||||||
directory?: string
|
directory?: string
|
||||||
@@ -44,19 +43,10 @@ function pruneNotifications(list: Notification[]) {
|
|||||||
export const { use: useNotification, provider: NotificationProvider } = createSimpleContext({
|
export const { use: useNotification, provider: NotificationProvider } = createSimpleContext({
|
||||||
name: "Notification",
|
name: "Notification",
|
||||||
init: () => {
|
init: () => {
|
||||||
let idlePlayer: ReturnType<typeof makeAudioPlayer> | undefined
|
|
||||||
let errorPlayer: ReturnType<typeof makeAudioPlayer> | undefined
|
|
||||||
|
|
||||||
try {
|
|
||||||
idlePlayer = makeAudioPlayer(idleSound)
|
|
||||||
errorPlayer = makeAudioPlayer(errorSound)
|
|
||||||
} catch (err) {
|
|
||||||
console.log("Failed to load audio", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
const globalSDK = useGlobalSDK()
|
const globalSDK = useGlobalSDK()
|
||||||
const globalSync = useGlobalSync()
|
const globalSync = useGlobalSync()
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
|
const settings = useSettings()
|
||||||
|
|
||||||
const [store, setStore, _, ready] = persisted(
|
const [store, setStore, _, ready] = persisted(
|
||||||
Persist.global("notification", ["notification.v1"]),
|
Persist.global("notification", ["notification.v1"]),
|
||||||
@@ -93,16 +83,20 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
|||||||
const match = Binary.search(syncStore.session, sessionID, (s) => s.id)
|
const match = Binary.search(syncStore.session, sessionID, (s) => s.id)
|
||||||
const session = match.found ? syncStore.session[match.index] : undefined
|
const session = match.found ? syncStore.session[match.index] : undefined
|
||||||
if (session?.parentID) break
|
if (session?.parentID) break
|
||||||
try {
|
|
||||||
idlePlayer?.play()
|
playSound(soundSrc(settings.sounds.agent()))
|
||||||
} catch {}
|
|
||||||
append({
|
append({
|
||||||
...base,
|
...base,
|
||||||
type: "turn-complete",
|
type: "turn-complete",
|
||||||
session: sessionID,
|
session: sessionID,
|
||||||
})
|
})
|
||||||
|
|
||||||
const href = `/${base64Encode(directory)}/session/${sessionID}`
|
const href = `/${base64Encode(directory)}/session/${sessionID}`
|
||||||
void platform.notify("Response ready", session?.title ?? sessionID, href)
|
if (settings.notifications.agent()) {
|
||||||
|
void platform.notify("Response ready", session?.title ?? sessionID, href)
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case "session.error": {
|
case "session.error": {
|
||||||
@@ -111,9 +105,9 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
|||||||
const match = sessionID ? Binary.search(syncStore.session, sessionID, (s) => s.id) : undefined
|
const match = sessionID ? Binary.search(syncStore.session, sessionID, (s) => s.id) : undefined
|
||||||
const session = sessionID && match?.found ? syncStore.session[match.index] : undefined
|
const session = sessionID && match?.found ? syncStore.session[match.index] : undefined
|
||||||
if (session?.parentID) break
|
if (session?.parentID) break
|
||||||
try {
|
|
||||||
errorPlayer?.play()
|
playSound(soundSrc(settings.sounds.errors()))
|
||||||
} catch {}
|
|
||||||
const error = "error" in event.properties ? event.properties.error : undefined
|
const error = "error" in event.properties ? event.properties.error : undefined
|
||||||
append({
|
append({
|
||||||
...base,
|
...base,
|
||||||
@@ -121,9 +115,13 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
|||||||
session: sessionID ?? "global",
|
session: sessionID ?? "global",
|
||||||
error,
|
error,
|
||||||
})
|
})
|
||||||
|
|
||||||
const description = session?.title ?? (typeof error === "string" ? error : "An error occurred")
|
const description = session?.title ?? (typeof error === "string" ? error : "An error occurred")
|
||||||
const href = sessionID ? `/${base64Encode(directory)}/session/${sessionID}` : `/${base64Encode(directory)}`
|
const href = sessionID ? `/${base64Encode(directory)}/session/${sessionID}` : `/${base64Encode(directory)}`
|
||||||
void platform.notify("Session error", description, href)
|
if (settings.notifications.errors()) {
|
||||||
|
void platform.notify("Session error", description, href)
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { createMemo } from "solid-js"
|
import { createEffect, createMemo } from "solid-js"
|
||||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||||
import { persisted } from "@/utils/persist"
|
import { persisted } from "@/utils/persist"
|
||||||
|
|
||||||
@@ -9,6 +9,12 @@ export interface NotificationSettings {
|
|||||||
errors: boolean
|
errors: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SoundSettings {
|
||||||
|
agent: string
|
||||||
|
permissions: string
|
||||||
|
errors: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface Settings {
|
export interface Settings {
|
||||||
general: {
|
general: {
|
||||||
autoSave: boolean
|
autoSave: boolean
|
||||||
@@ -22,6 +28,7 @@ export interface Settings {
|
|||||||
autoApprove: boolean
|
autoApprove: boolean
|
||||||
}
|
}
|
||||||
notifications: NotificationSettings
|
notifications: NotificationSettings
|
||||||
|
sounds: SoundSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultSettings: Settings = {
|
const defaultSettings: Settings = {
|
||||||
@@ -37,16 +44,47 @@ const defaultSettings: Settings = {
|
|||||||
autoApprove: false,
|
autoApprove: false,
|
||||||
},
|
},
|
||||||
notifications: {
|
notifications: {
|
||||||
agent: false,
|
agent: true,
|
||||||
permissions: false,
|
permissions: true,
|
||||||
errors: false,
|
errors: false,
|
||||||
},
|
},
|
||||||
|
sounds: {
|
||||||
|
agent: "staplebops-01",
|
||||||
|
permissions: "staplebops-02",
|
||||||
|
errors: "nope-03",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const monoFallback =
|
||||||
|
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
|
||||||
|
|
||||||
|
const monoFonts: Record<string, string> = {
|
||||||
|
"ibm-plex-mono": `"IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||||
|
"cascadia-code": `"Cascadia Code Nerd Font", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||||
|
"fira-code": `"Fira Code Nerd Font", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||||
|
hack: `"Hack Nerd Font", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||||
|
inconsolata: `"Inconsolata Nerd Font", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||||
|
"intel-one-mono": `"Intel One Mono Nerd Font", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||||
|
"jetbrains-mono": `"JetBrains Mono Nerd Font", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||||
|
"meslo-lgs": `"Meslo LGS Nerd Font", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||||
|
"roboto-mono": `"Roboto Mono Nerd Font", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||||
|
"source-code-pro": `"Source Code Pro Nerd Font", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||||
|
"ubuntu-mono": `"Ubuntu Mono Nerd Font", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function monoFontFamily(font: string | undefined) {
|
||||||
|
return monoFonts[font ?? defaultSettings.appearance.font] ?? monoFonts[defaultSettings.appearance.font]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const { use: useSettings, provider: SettingsProvider } = createSimpleContext({
|
export const { use: useSettings, provider: SettingsProvider } = createSimpleContext({
|
||||||
name: "Settings",
|
name: "Settings",
|
||||||
init: () => {
|
init: () => {
|
||||||
const [store, setStore, _, ready] = persisted("settings.v1", createStore<Settings>(defaultSettings))
|
const [store, setStore, _, ready] = persisted("settings.v3", createStore<Settings>(defaultSettings))
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (typeof document === "undefined") return
|
||||||
|
document.documentElement.style.setProperty("--font-family-mono", monoFontFamily(store.appearance?.font))
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ready,
|
ready,
|
||||||
@@ -98,6 +136,20 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
|
|||||||
setStore("notifications", "errors", value)
|
setStore("notifications", "errors", value)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
sounds: {
|
||||||
|
agent: createMemo(() => store.sounds?.agent ?? defaultSettings.sounds.agent),
|
||||||
|
setAgent(value: string) {
|
||||||
|
setStore("sounds", "agent", value)
|
||||||
|
},
|
||||||
|
permissions: createMemo(() => store.sounds?.permissions ?? defaultSettings.sounds.permissions),
|
||||||
|
setPermissions(value: string) {
|
||||||
|
setStore("sounds", "permissions", value)
|
||||||
|
},
|
||||||
|
errors: createMemo(() => store.sounds?.errors ?? defaultSettings.sounds.errors),
|
||||||
|
setErrors(value: string) {
|
||||||
|
setStore("sounds", "errors", value)
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import { Dialog } from "@opencode-ai/ui/dialog"
|
|||||||
import { getFilename } from "@opencode-ai/util/path"
|
import { getFilename } from "@opencode-ai/util/path"
|
||||||
import { Session, type Message, type TextPart } from "@opencode-ai/sdk/v2/client"
|
import { Session, type Message, type TextPart } from "@opencode-ai/sdk/v2/client"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
|
import { useSettings } from "@/context/settings"
|
||||||
import { createStore, produce, reconcile } from "solid-js/store"
|
import { createStore, produce, reconcile } from "solid-js/store"
|
||||||
import {
|
import {
|
||||||
DragDropProvider,
|
DragDropProvider,
|
||||||
@@ -54,6 +55,7 @@ import { useNotification } from "@/context/notification"
|
|||||||
import { usePermission } from "@/context/permission"
|
import { usePermission } from "@/context/permission"
|
||||||
import { Binary } from "@opencode-ai/util/binary"
|
import { Binary } from "@opencode-ai/util/binary"
|
||||||
import { retry } from "@opencode-ai/util/retry"
|
import { retry } from "@opencode-ai/util/retry"
|
||||||
|
import { playSound, soundSrc } from "@/utils/sound"
|
||||||
|
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
|
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
|
||||||
@@ -98,6 +100,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
const layout = useLayout()
|
const layout = useLayout()
|
||||||
const layoutReady = createMemo(() => layout.ready())
|
const layoutReady = createMemo(() => layout.ready())
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
|
const settings = useSettings()
|
||||||
const server = useServer()
|
const server = useServer()
|
||||||
const notification = useNotification()
|
const notification = useNotification()
|
||||||
const permission = usePermission()
|
const permission = usePermission()
|
||||||
@@ -329,7 +332,18 @@ export default function Layout(props: ParentProps) {
|
|||||||
if (now - lastAlerted < cooldownMs) return
|
if (now - lastAlerted < cooldownMs) return
|
||||||
alertedAtBySession.set(sessionKey, now)
|
alertedAtBySession.set(sessionKey, now)
|
||||||
|
|
||||||
void platform.notify(config.title, description, href)
|
if (e.details.type === "permission.asked") {
|
||||||
|
playSound(soundSrc(settings.sounds.permissions()))
|
||||||
|
if (settings.notifications.permissions()) {
|
||||||
|
void platform.notify(config.title, description, href)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.details.type === "question.asked") {
|
||||||
|
if (settings.notifications.agent()) {
|
||||||
|
void platform.notify(config.title, description, href)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const currentDir = params.dir ? base64Decode(params.dir) : undefined
|
const currentDir = params.dir ? base64Decode(params.dir) : undefined
|
||||||
const currentSession = params.id
|
const currentSession = params.id
|
||||||
|
|||||||
44
packages/app/src/utils/sound.ts
Normal file
44
packages/app/src/utils/sound.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import nope01 from "@opencode-ai/ui/audio/nope-01.aac"
|
||||||
|
import nope02 from "@opencode-ai/ui/audio/nope-02.aac"
|
||||||
|
import nope03 from "@opencode-ai/ui/audio/nope-03.aac"
|
||||||
|
import nope04 from "@opencode-ai/ui/audio/nope-04.aac"
|
||||||
|
import nope05 from "@opencode-ai/ui/audio/nope-05.aac"
|
||||||
|
import staplebops01 from "@opencode-ai/ui/audio/staplebops-01.aac"
|
||||||
|
import staplebops02 from "@opencode-ai/ui/audio/staplebops-02.aac"
|
||||||
|
import staplebops03 from "@opencode-ai/ui/audio/staplebops-03.aac"
|
||||||
|
import staplebops04 from "@opencode-ai/ui/audio/staplebops-04.aac"
|
||||||
|
import staplebops05 from "@opencode-ai/ui/audio/staplebops-05.aac"
|
||||||
|
import staplebops06 from "@opencode-ai/ui/audio/staplebops-06.aac"
|
||||||
|
import staplebops07 from "@opencode-ai/ui/audio/staplebops-07.aac"
|
||||||
|
|
||||||
|
export const SOUND_OPTIONS = [
|
||||||
|
{ id: "staplebops-01", label: "Boopy", src: staplebops01 },
|
||||||
|
{ id: "staplebops-02", label: "Beepy", src: staplebops02 },
|
||||||
|
{ id: "staplebops-03", label: "Staplebops 03", src: staplebops03 },
|
||||||
|
{ id: "staplebops-04", label: "Staplebops 04", src: staplebops04 },
|
||||||
|
{ id: "staplebops-05", label: "Staplebops 05", src: staplebops05 },
|
||||||
|
{ id: "staplebops-06", label: "Staplebops 06", src: staplebops06 },
|
||||||
|
{ id: "staplebops-07", label: "Staplebops 07", src: staplebops07 },
|
||||||
|
{ id: "nope-01", label: "Nope 01", src: nope01 },
|
||||||
|
{ id: "nope-02", label: "Nope 02", src: nope02 },
|
||||||
|
{ id: "nope-03", label: "Oopsie", src: nope03 },
|
||||||
|
{ id: "nope-04", label: "Nope 04", src: nope04 },
|
||||||
|
{ id: "nope-05", label: "Nope 05", src: nope05 },
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export type SoundOption = (typeof SOUND_OPTIONS)[number]
|
||||||
|
export type SoundID = SoundOption["id"]
|
||||||
|
|
||||||
|
const soundById = Object.fromEntries(SOUND_OPTIONS.map((s) => [s.id, s.src])) as Record<SoundID, string>
|
||||||
|
|
||||||
|
export function soundSrc(id: string | undefined) {
|
||||||
|
if (!id) return
|
||||||
|
if (!(id in soundById)) return
|
||||||
|
return soundById[id as SoundID]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function playSound(src: string | undefined) {
|
||||||
|
if (typeof Audio === "undefined") return
|
||||||
|
if (!src) return
|
||||||
|
void new Audio(src).play().catch(() => undefined)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user