import { Component, createMemo, type JSX } from "solid-js" import { createStore } from "solid-js/store" import { Button } from "@opencode-ai/ui/button" import { Select } from "@opencode-ai/ui/select" import { Switch } from "@opencode-ai/ui/switch" import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme" import { showToast } from "@opencode-ai/ui/toast" import { useLanguage } from "@/context/language" import { usePlatform } from "@/context/platform" import { useSettings, monoFontFamily } from "@/context/settings" import { playSound, SOUND_OPTIONS } from "@/utils/sound" import { Link } from "./link" let demoSoundState = { cleanup: undefined as (() => void) | undefined, timeout: undefined as NodeJS.Timeout | undefined, } // To prevent audio from overlapping/playing very quickly when navigating the settings menus, // delay the playback by 100ms during quick selection changes and pause existing sounds. const playDemoSound = (src: string) => { if (demoSoundState.cleanup) { demoSoundState.cleanup() } clearTimeout(demoSoundState.timeout) demoSoundState.timeout = setTimeout(() => { demoSoundState.cleanup = playSound(src) }, 100) } export const SettingsGeneral: Component = () => { const theme = useTheme() const language = useLanguage() const platform = usePlatform() const settings = useSettings() const [store, setStore] = createStore({ checking: false, }) const check = () => { if (!platform.checkUpdate) return setStore("checking", true) void platform .checkUpdate() .then((result) => { if (!result.updateAvailable) { showToast({ variant: "success", icon: "circle-check", title: language.t("settings.updates.toast.latest.title"), description: language.t("settings.updates.toast.latest.description", { version: platform.version ?? "" }), }) return } const actions = platform.update && platform.restart ? [ { label: language.t("toast.update.action.installRestart"), onClick: async () => { await platform.update!() await platform.restart!() }, }, { label: language.t("toast.update.action.notYet"), onClick: "dismiss" as const, }, ] : [ { label: language.t("toast.update.action.notYet"), onClick: "dismiss" as const, }, ] showToast({ persistent: true, icon: "download", title: language.t("toast.update.title"), description: language.t("toast.update.description", { version: result.version ?? "" }), actions, }) }) .catch((err: unknown) => { const message = err instanceof Error ? err.message : String(err) showToast({ title: language.t("common.requestFailed"), description: message }) }) .finally(() => setStore("checking", false)) } const themeOptions = createMemo(() => Object.entries(theme.themes()).map(([id, def]) => ({ id, name: def.name ?? id })), ) const colorSchemeOptions = createMemo((): { value: ColorScheme; label: string }[] => [ { value: "system", label: language.t("theme.scheme.system") }, { value: "light", label: language.t("theme.scheme.light") }, { value: "dark", label: language.t("theme.scheme.dark") }, ]) const languageOptions = createMemo(() => language.locales.map((locale) => ({ value: locale, label: language.label(locale), })), ) const fontOptions = [ { value: "ibm-plex-mono", label: "font.option.ibmPlexMono" }, { value: "cascadia-code", label: "font.option.cascadiaCode" }, { value: "fira-code", label: "font.option.firaCode" }, { value: "hack", label: "font.option.hack" }, { value: "inconsolata", label: "font.option.inconsolata" }, { value: "intel-one-mono", label: "font.option.intelOneMono" }, { value: "iosevka", label: "font.option.iosevka" }, { value: "jetbrains-mono", label: "font.option.jetbrainsMono" }, { value: "meslo-lgs", label: "font.option.mesloLgs" }, { value: "roboto-mono", label: "font.option.robotoMono" }, { value: "source-code-pro", label: "font.option.sourceCodePro" }, { value: "ubuntu-mono", label: "font.option.ubuntuMono" }, ] as const const fontOptionsList = [...fontOptions] const soundOptions = [...SOUND_OPTIONS] return (

{language.t("settings.tab.general")}

{/* Appearance Section */}

{language.t("settings.general.section.appearance")}

o.value === theme.colorScheme())} value={(o) => o.value} label={(o) => o.label} onSelect={(option) => option && theme.setColorScheme(option.value)} onHighlight={(option) => { if (!option) return theme.previewColorScheme(option.value) return () => theme.cancelPreview() }} variant="secondary" size="small" triggerVariant="settings" /> {language.t("settings.general.row.theme.description")}{" "} {language.t("common.learnMore")} } > o.value === settings.appearance.font())} value={(o) => o.value} label={(o) => language.t(o.label)} onSelect={(option) => option && settings.appearance.setFont(option.value)} variant="secondary" size="small" triggerVariant="settings" triggerStyle={{ "font-family": monoFontFamily(settings.appearance.font()), "min-width": "180px" }} > {(option) => ( {option ? language.t(option.label) : ""} )}
{/* System notifications Section */}

{language.t("settings.general.section.notifications")}

settings.notifications.setAgent(checked)} />
settings.notifications.setPermissions(checked)} />
settings.notifications.setErrors(checked)} />
{/* Sound effects Section */}

{language.t("settings.general.section.sounds")}

o.id === settings.sounds.permissions())} value={(o) => o.id} label={(o) => language.t(o.label)} onHighlight={(option) => { if (!option) return playDemoSound(option.src) }} onSelect={(option) => { if (!option) return settings.sounds.setPermissions(option.id) playDemoSound(option.src) }} variant="secondary" size="small" triggerVariant="settings" />