diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx
index b5e8ffa721d5..115aaab4b777 100644
--- a/packages/app/src/components/settings-general.tsx
+++ b/packages/app/src/components/settings-general.tsx
@@ -7,14 +7,11 @@ import { Switch } from "@opencode-ai/ui/switch"
import { TextField } from "@opencode-ai/ui/text-field"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme/context"
-import { useDialog } from "@opencode-ai/ui/context/dialog"
import { showToast } from "@/utils/toast"
-import { useParams } from "@solidjs/router"
import { useLanguage } from "@/context/language"
import { usePermission } from "@/context/permission"
import { usePlatform, type DisplayBackend } from "@/context/platform"
-import { useServerSync } from "@/context/server-sync"
-import { useServerSDK } from "@/context/server-sdk"
+import { useServerSDK, useServerSync } from "@/context/server-context"
import {
monoDefault,
monoFontFamily,
@@ -27,16 +24,10 @@ import {
terminalInput,
useSettings,
} from "@/context/settings"
-import { decode64 } from "@/utils/base64"
-import { playSoundById, SOUND_OPTIONS } from "@/utils/sound"
+import { SOUND_OPTIONS } from "@/utils/sound"
import { Link } from "./link"
import { SettingsList } from "./settings-list"
-
-let demoSoundState = {
- cleanup: undefined as (() => void) | undefined,
- timeout: undefined as NodeJS.Timeout | undefined,
- run: 0,
-}
+import { playDemoSound, stopDemoSound } from "./settings-sound"
type ThemeOption = {
id: string
@@ -55,40 +46,11 @@ type ShellSelectOption = {
label: string
}
-// 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 stopDemoSound = () => {
- demoSoundState.run += 1
- if (demoSoundState.cleanup) {
- demoSoundState.cleanup()
- }
- clearTimeout(demoSoundState.timeout)
- demoSoundState.cleanup = undefined
-}
-
-const playDemoSound = (id: string | undefined) => {
- stopDemoSound()
- if (!id) return
-
- const run = ++demoSoundState.run
- demoSoundState.timeout = setTimeout(() => {
- void playSoundById(id).then((cleanup) => {
- if (demoSoundState.run !== run) {
- cleanup?.()
- return
- }
- demoSoundState.cleanup = cleanup
- })
- }, 100)
-}
-
-export const SettingsGeneral: Component = () => {
+export const SettingsGeneral: Component<{ directory?: string; sessionID?: string }> = (props) => {
const theme = useTheme()
const language = useLanguage()
const permission = usePermission()
const platform = usePlatform()
- const dialog = useDialog()
- const params = useParams()
const settings = useSettings()
const [store, setStore] = createStore({
@@ -96,30 +58,29 @@ export const SettingsGeneral: Component = () => {
})
const linux = createMemo(() => platform.platform === "desktop" && platform.os === "linux")
- const dir = createMemo(() => decode64(params.dir))
const accepting = createMemo(() => {
- const value = dir()
+ const value = props.directory
if (!value) return false
- if (!params.id) return permission.isAutoAcceptingDirectory(value)
- return permission.isAutoAccepting(params.id, value)
+ if (!props.sessionID) return permission.isAutoAcceptingDirectory(value)
+ return permission.isAutoAccepting(props.sessionID, value)
})
const toggleAccept = (checked: boolean) => {
- const value = dir()
+ const value = props.directory
if (!value) return
- if (!params.id) {
+ if (!props.sessionID) {
if (permission.isAutoAcceptingDirectory(value) === checked) return
permission.toggleAutoAcceptDirectory(value)
return
}
if (checked) {
- permission.enableAutoAccept(params.id, value)
+ permission.enableAutoAccept(props.sessionID, value)
return
}
- permission.disableAutoAccept(params.id, value)
+ permission.disableAutoAccept(props.sessionID, value)
}
const desktop = createMemo(() => platform.platform === "desktop")
@@ -182,7 +143,7 @@ export const SettingsGeneral: Component = () => {
const [shells] = createResource(
() =>
- serverSdk.client.pty
+ serverSdk().client.pty
.shells()
.then((res) => res.data ?? [])
.catch(() => [] as ShellOption[]),
@@ -206,11 +167,11 @@ export const SettingsGeneral: Component = () => {
})
const autoOption = { id: "auto", value: "", label: language.t("settings.general.row.shell.autoDefault") }
- const currentShell = createMemo(() => serverSync.data.config.shell ?? "")
+ const currentShell = createMemo(() => serverSync().data.config.shell ?? "")
const shellOptions = createMemo
(() => {
const list = shells.latest
- const current = serverSync.data.config.shell
+ const current = serverSync().data.config.shell
const nameCounts = new Map()
for (const s of list) {
@@ -328,7 +289,7 @@ export const SettingsGeneral: Component = () => {
description={language.t("toast.permissions.autoaccept.on.description")}
>
-
+
@@ -345,7 +306,7 @@ export const SettingsGeneral: Component = () => {
onSelect={(option) => {
if (!option) return
if (option.value === currentShell()) return
- serverSync.updateConfig({ shell: option.value })
+ serverSync().updateConfig({ shell: option.value })
}}
variant="secondary"
size="small"
@@ -409,13 +370,7 @@ export const SettingsGeneral: Component = () => {
{
- settings.general.setNewLayoutDesigns(checked)
- if (!checked) return
- void import("@/components/settings-v2").then((module) => {
- dialog.show(() => )
- })
- }}
+ onChange={settings.general.setNewLayoutDesigns}
/>
diff --git a/packages/app/src/components/settings-models.tsx b/packages/app/src/components/settings-models.tsx
index f3d9e1522ff4..5b2d7740f424 100644
--- a/packages/app/src/components/settings-models.tsx
+++ b/packages/app/src/components/settings-models.tsx
@@ -32,9 +32,9 @@ const ListEmptyState: Component<{ message: string; filter: string }> = (props) =
)
}
-export const SettingsModels: Component = () => {
+export const SettingsModels: Component<{ directory?: string }> = (props) => {
return (
-
+ props.directory}>
)
diff --git a/packages/app/src/components/settings-providers.tsx b/packages/app/src/components/settings-providers.tsx
index ef8d03aedd0a..c356054d2267 100644
--- a/packages/app/src/components/settings-providers.tsx
+++ b/packages/app/src/components/settings-providers.tsx
@@ -6,8 +6,7 @@ import { showToast } from "@/utils/toast"
import { popularProviders, useProviders } from "@/hooks/use-providers"
import { createMemo, type Component, For, Show } from "solid-js"
import { useLanguage } from "@/context/language"
-import { useServerSDK } from "@/context/server-sdk"
-import { useServerSync } from "@/context/server-sync"
+import { useServerSDK, useServerSync } from "@/context/server-context"
import { DialogConnectProvider } from "./dialog-connect-provider"
import { DialogSelectProvider } from "./dialog-select-provider"
import { DialogCustomProvider } from "./dialog-custom-provider"
@@ -28,20 +27,20 @@ const PROVIDER_NOTES = [
{ match: (id: string) => id === "vercel", key: "dialog.provider.vercel.note" },
] as const
-export const SettingsProviders: Component = () => {
+export const SettingsProviders: Component<{ directory?: string }> = (props) => {
return (
-
-
+ props.directory}>
+
)
}
-const SettingsProvidersContent: Component = () => {
+const SettingsProvidersContent: Component<{ directory?: string }> = (props) => {
const dialog = useDialog()
const language = useLanguage()
const serverSDK = useServerSDK()
const serverSync = useServerSync()
- const providers = useProviders()
+ const providers = useProviders(() => props.directory)
const connected = createMemo(() => {
return providers
@@ -83,7 +82,7 @@ const SettingsProvidersContent: Component = () => {
const note = (id: string) => PROVIDER_NOTES.find((item) => item.match(id))?.key
const isConfigCustom = (providerID: string) => {
- const provider = serverSync.data.config.provider?.[providerID]
+ const provider = serverSync().data.config.provider?.[providerID]
if (!provider) return false
if (provider.npm !== "@ai-sdk/openai-compatible") return false
if (!provider.models || Object.keys(provider.models).length === 0) return false
@@ -91,11 +90,12 @@ const SettingsProvidersContent: Component = () => {
}
const disableProvider = async (providerID: string, name: string) => {
- const before = serverSync.data.config.disabled_providers ?? []
+ const sync = serverSync()
+ const before = sync.data.config.disabled_providers ?? []
const next = before.includes(providerID) ? before : [...before, providerID]
- serverSync.set("config", "disabled_providers", next)
+ sync.set("config", "disabled_providers", next)
- await serverSync
+ await sync
.updateConfig({ disabled_providers: next })
.then(() => {
showToast({
@@ -106,22 +106,23 @@ const SettingsProvidersContent: Component = () => {
})
})
.catch((err: unknown) => {
- serverSync.set("config", "disabled_providers", before)
+ sync.set("config", "disabled_providers", before)
const message = err instanceof Error ? err.message : String(err)
showToast({ title: language.t("common.requestFailed"), description: message })
})
}
const disconnect = async (providerID: string, name: string) => {
+ const sdk = serverSDK()
if (isConfigCustom(providerID)) {
- await serverSDK.client.auth.remove({ providerID }).catch(() => undefined)
+ await sdk.client.auth.remove({ providerID }).catch(() => undefined)
await disableProvider(providerID, name)
return
}
- await serverSDK.client.auth
+ await sdk.client.auth
.remove({ providerID })
.then(async () => {
- await serverSDK.client.global.dispose()
+ await sdk.client.global.dispose()
showToast({
variant: "success",
icon: "circle-check",
@@ -209,7 +210,7 @@ const SettingsProvidersContent: Component = () => {
variant="secondary"
icon="plus-small"
onClick={() => {
- dialog.show(() => )
+ dialog.show(() => )
}}
>
{language.t("common.connect")}
@@ -237,7 +238,7 @@ const SettingsProvidersContent: Component = () => {
variant="secondary"
icon="plus-small"
onClick={() => {
- dialog.show(() => )
+ dialog.show(() => )
}}
>
{language.t("common.connect")}
@@ -249,7 +250,7 @@ const SettingsProvidersContent: Component = () => {
variant="ghost"
class="px-0 py-0 mt-5 text-14-medium text-text-interactive-base text-left justify-start hover:bg-transparent active:bg-transparent"
onClick={() => {
- dialog.show(() => )
+ dialog.show(() => )
}}
>
{language.t("dialog.provider.viewAll")}
diff --git a/packages/app/src/components/settings-server-picker.tsx b/packages/app/src/components/settings-server-picker.tsx
index 3f679753f053..4bf15211834d 100644
--- a/packages/app/src/components/settings-server-picker.tsx
+++ b/packages/app/src/components/settings-server-picker.tsx
@@ -1,41 +1,41 @@
import { Button } from "@opencode-ai/ui/button"
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
import { Icon } from "@opencode-ai/ui/icon"
-import { QueryClientProvider } from "@tanstack/solid-query"
-import { createMemo, For, type ParentProps, Show } from "solid-js"
+import { createMemo, For, type Accessor, type ParentProps, Show } from "solid-js"
import { ServerHealthIndicator, ServerRow } from "@/components/server/server-row"
import { ModelsProvider } from "@/context/models"
import { ServerConnection } from "@/context/server"
-import { ServerSDKProvider } from "@/context/server-sdk"
-import { ServerSyncProvider } from "@/context/server-sync"
+import { ServerContextProvider } from "@/context/server-context"
import { useGlobal } from "@/context/global"
import { useSettings } from "@/context/settings"
-export function SettingsServerScope(props: ParentProps) {
+export function SettingsServerScope(props: ParentProps<{ directory: Accessor }>) {
const global = useGlobal()
const settings = useSettings()
return (
- {(server) => {props.children}}
+ {(server) => (
+
+ {props.children}
+
+ )}
)
}
-function SettingsServerDataProviders(props: ParentProps<{ server: ServerConnection.Any }>) {
+function SettingsServerDataProviders(
+ props: ParentProps<{ server: ServerConnection.Any; directory: Accessor }>,
+) {
const global = useGlobal()
const serverCtx = () => global.createServerCtx(props.server)
return (
-
-
-
- {props.children}
-
-
-
+
+ {props.children}
+
)
}
diff --git a/packages/app/src/components/settings-sound.ts b/packages/app/src/components/settings-sound.ts
new file mode 100644
index 000000000000..8dacaf5b5881
--- /dev/null
+++ b/packages/app/src/components/settings-sound.ts
@@ -0,0 +1,30 @@
+import { playSoundById } from "@/utils/sound"
+
+const state = {
+ cleanup: undefined as (() => void) | undefined,
+ timeout: undefined as NodeJS.Timeout | undefined,
+ run: 0,
+}
+
+export function stopDemoSound() {
+ state.run += 1
+ state.cleanup?.()
+ clearTimeout(state.timeout)
+ state.cleanup = undefined
+}
+
+export function playDemoSound(id: string | undefined) {
+ stopDemoSound()
+ if (!id) return
+
+ const run = ++state.run
+ state.timeout = setTimeout(() => {
+ void playSoundById(id).then((cleanup) => {
+ if (state.run !== run) {
+ cleanup?.()
+ return
+ }
+ state.cleanup = cleanup
+ })
+ }, 100)
+}
diff --git a/packages/app/src/components/settings-v2/dialog-settings-v2.tsx b/packages/app/src/components/settings-v2/dialog-settings-v2.tsx
index 932f2dedd468..abc9f3ae94d2 100644
--- a/packages/app/src/components/settings-v2/dialog-settings-v2.tsx
+++ b/packages/app/src/components/settings-v2/dialog-settings-v2.tsx
@@ -10,8 +10,9 @@ import { SettingsProvidersV2 } from "./providers"
import { SettingsModelsV2 } from "./models"
import "./settings-v2.css"
import { SettingsServers } from "../settings-servers"
+import { SettingsServerScope } from "../settings-server-picker"
-export const DialogSettings: Component = () => {
+export const DialogSettings: Component<{ directory?: string; sessionID?: string }> = (props) => {
const language = useLanguage()
const platform = usePlatform()
@@ -62,7 +63,7 @@ export const DialogSettings: Component = () => {
diff --git a/packages/app/src/components/settings-v2/general.tsx b/packages/app/src/components/settings-v2/general.tsx
index 9bc5141ba602..eb005829d24f 100644
--- a/packages/app/src/components/settings-v2/general.tsx
+++ b/packages/app/src/components/settings-v2/general.tsx
@@ -7,14 +7,11 @@ import { Switch } from "@opencode-ai/ui/v2/switch-v2"
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme/context"
-import { useDialog } from "@opencode-ai/ui/context/dialog"
import { showToast } from "@/utils/toast"
-import { useParams } from "@solidjs/router"
import { useLanguage } from "@/context/language"
import { usePermission } from "@/context/permission"
import { usePlatform, type DisplayBackend } from "@/context/platform"
-import { useServerSync } from "@/context/server-sync"
-import { useServerSDK } from "@/context/server-sdk"
+import { useServerSDK, useServerSync } from "@/context/server-context"
import {
monoDefault,
monoFontFamily,
@@ -27,19 +24,13 @@ import {
terminalInput,
useSettings,
} from "@/context/settings"
-import { decode64 } from "@/utils/base64"
-import { playSoundById, SOUND_OPTIONS } from "@/utils/sound"
+import { SOUND_OPTIONS } from "@/utils/sound"
import { Link } from "../link"
+import { playDemoSound, stopDemoSound } from "../settings-sound"
import { SettingsListV2 } from "./parts/list"
import { SettingsRowV2 } from "./parts/row"
import "./settings-v2.css"
-let demoSoundState = {
- cleanup: undefined as (() => void) | undefined,
- timeout: undefined as NodeJS.Timeout | undefined,
- run: 0,
-}
-
type ThemeOption = {
id: string
name: string
@@ -57,40 +48,11 @@ type ShellSelectOption = {
label: string
}
-// 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 stopDemoSound = () => {
- demoSoundState.run += 1
- if (demoSoundState.cleanup) {
- demoSoundState.cleanup()
- }
- clearTimeout(demoSoundState.timeout)
- demoSoundState.cleanup = undefined
-}
-
-const playDemoSound = (id: string | undefined) => {
- stopDemoSound()
- if (!id) return
-
- const run = ++demoSoundState.run
- demoSoundState.timeout = setTimeout(() => {
- void playSoundById(id).then((cleanup) => {
- if (demoSoundState.run !== run) {
- cleanup?.()
- return
- }
- demoSoundState.cleanup = cleanup
- })
- }, 100)
-}
-
-export const SettingsGeneralV2: Component = () => {
+export const SettingsGeneralV2: Component<{ directory?: string; sessionID?: string }> = (props) => {
const theme = useTheme()
const language = useLanguage()
const permission = usePermission()
const platform = usePlatform()
- const dialog = useDialog()
- const params = useParams()
const settings = useSettings()
const [store, setStore] = createStore({
@@ -98,30 +60,29 @@ export const SettingsGeneralV2: Component = () => {
})
const linux = createMemo(() => platform.platform === "desktop" && platform.os === "linux")
- const dir = createMemo(() => decode64(params.dir))
const accepting = createMemo(() => {
- const value = dir()
+ const value = props.directory
if (!value) return false
- if (!params.id) return permission.isAutoAcceptingDirectory(value)
- return permission.isAutoAccepting(params.id, value)
+ if (!props.sessionID) return permission.isAutoAcceptingDirectory(value)
+ return permission.isAutoAccepting(props.sessionID, value)
})
const toggleAccept = (checked: boolean) => {
- const value = dir()
+ const value = props.directory
if (!value) return
- if (!params.id) {
+ if (!props.sessionID) {
if (permission.isAutoAcceptingDirectory(value) === checked) return
permission.toggleAutoAcceptDirectory(value)
return
}
if (checked) {
- permission.enableAutoAccept(params.id, value)
+ permission.enableAutoAccept(props.sessionID, value)
return
}
- permission.disableAutoAccept(params.id, value)
+ permission.disableAutoAccept(props.sessionID, value)
}
const desktop = createMemo(() => platform.platform === "desktop")
@@ -184,7 +145,7 @@ export const SettingsGeneralV2: Component = () => {
const [shells] = createResource(
() =>
- serverSdk.client.pty
+ serverSdk().client.pty
.shells()
.then((res) => res.data ?? [])
.catch(() => [] as ShellOption[]),
@@ -208,11 +169,11 @@ export const SettingsGeneralV2: Component = () => {
})
const autoOption = { id: "auto", value: "", label: language.t("settings.general.row.shell.autoDefault") }
- const currentShell = createMemo(() => serverSync.data.config.shell ?? "")
+ const currentShell = createMemo(() => serverSync().data.config.shell ?? "")
const shellOptions = createMemo