diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx
index 75ea3fb2..cead812f 100644
--- a/packages/opencode/src/cli/cmd/tui/app.tsx
+++ b/packages/opencode/src/cli/cmd/tui/app.tsx
@@ -27,6 +27,7 @@ import { ExitProvider } from "./context/exit"
import type { SessionRoute } from "./context/route"
import { Session as SessionApi } from "@/session"
import { TuiEvent } from "./event"
+import { KVProvider, useKV } from "./context/kv"
export function tui(input: {
url: string
@@ -54,27 +55,29 @@ export function tui(input: {
return (
Something went wrong}>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
)
@@ -95,6 +98,7 @@ function App() {
renderer.disableStdoutInterception()
const dialog = useDialog()
const local = useLocal()
+ const kv = useKV()
const command = useCommandDialog()
const { event } = useSDK()
const sync = useSync()
@@ -222,13 +226,13 @@ function App() {
createEffect(() => {
const providerID = local.model.current().providerID
- if (providerID === "openrouter" && !local.kv.data.openrouter_warning) {
+ if (providerID === "openrouter" && !kv.data.openrouter_warning) {
untrack(() => {
DialogAlert.show(
dialog,
"Warning",
"While openrouter is a convenient way to access LLMs your request will often be routed to subpar providers that do not work well in our testing.\n\nFor reliable access to models check out OpenCode Zen\nhttps://opencode.ai/zen",
- ).then(() => local.kv.set("openrouter_warning", true))
+ ).then(() => kv.set("openrouter_warning", true))
})
}
})
diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-theme-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-theme-list.tsx
index 9f7a9203..0135cfd2 100644
--- a/packages/opencode/src/cli/cmd/tui/component/dialog-theme-list.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/dialog-theme-list.tsx
@@ -4,23 +4,23 @@ import { useDialog } from "../ui/dialog"
import { onCleanup, onMount } from "solid-js"
export function DialogThemeList() {
- const { selectedTheme, setSelectedTheme } = useTheme()
+ const theme = useTheme()
const options = Object.keys(THEMES).map((value) => ({
title: value,
value: value as keyof typeof THEMES,
}))
- const initial = selectedTheme()
const dialog = useDialog()
let confirmed = false
let ref: DialogSelectRef
+ const initial = theme.selectedTheme
onMount(() => {
// highlight the first theme in the list when we open it for UX
- setSelectedTheme(Object.keys(THEMES)[0] as keyof typeof THEMES)
+ theme.setSelectedTheme(Object.keys(THEMES)[0] as keyof typeof THEMES)
})
onCleanup(() => {
// if we close the dialog without confirming, reset back to the initial theme
- if (!confirmed) setSelectedTheme(initial)
+ if (!confirmed) theme.setSelectedTheme(initial)
})
return (
@@ -28,10 +28,10 @@ export function DialogThemeList() {
title="Themes"
options={options}
onMove={(opt) => {
- setSelectedTheme(opt.value)
+ theme.setSelectedTheme(opt.value)
}}
onSelect={(opt) => {
- setSelectedTheme(opt.value)
+ theme.setSelectedTheme(opt.value)
confirmed = true
dialog.clear()
}}
@@ -40,12 +40,12 @@ export function DialogThemeList() {
}}
onFilter={(query) => {
if (query.length === 0) {
- setSelectedTheme(initial)
+ theme.setSelectedTheme(initial)
return
}
const first = ref.filtered[0]
- if (first) setSelectedTheme(first.value)
+ if (first) theme.setSelectedTheme(first.value)
}}
/>
)
diff --git a/packages/opencode/src/cli/cmd/tui/context/kv.tsx b/packages/opencode/src/cli/cmd/tui/context/kv.tsx
new file mode 100644
index 00000000..bb9ae847
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/tui/context/kv.tsx
@@ -0,0 +1,45 @@
+import { Global } from "@/global"
+import { createSignal } from "solid-js"
+import { createStore } from "solid-js/store"
+import { createSimpleContext } from "./helper"
+import path from "path"
+
+export const { use: useKV, provider: KVProvider } = createSimpleContext({
+ name: "KV",
+ init: () => {
+ const [ready, setReady] = createSignal(false)
+ const [kvStore, setKvStore] = createStore({
+ openrouter_warning: false,
+ theme: "opencode",
+ })
+ const file = Bun.file(path.join(Global.Path.state, "kv.json"))
+
+ file
+ .json()
+ .then((x) => {
+ setKvStore(x)
+ })
+ .catch(() => {})
+ .finally(() => {
+ setReady(true)
+ })
+
+ return {
+ get data() {
+ return kvStore
+ },
+ get ready() {
+ return ready()
+ },
+ set(key: string, value: any) {
+ setKvStore(key as any, value)
+ Bun.write(
+ file,
+ JSON.stringify({
+ [key]: value,
+ }),
+ )
+ },
+ }
+ },
+})
diff --git a/packages/opencode/src/cli/cmd/tui/context/local.tsx b/packages/opencode/src/cli/cmd/tui/context/local.tsx
index 25ec00b3..e8f11a35 100644
--- a/packages/opencode/src/cli/cmd/tui/context/local.tsx
+++ b/packages/opencode/src/cli/cmd/tui/context/local.tsx
@@ -15,17 +15,18 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const sync = useSync()
const toast = useToast()
- function isModelValid(model: { providerID: string, modelID: string }) {
+ function isModelValid(model: { providerID: string; modelID: string }) {
const provider = sync.data.provider.find((x) => x.id === model.providerID)
return !!provider?.models[model.modelID]
}
- function getFirstValidModel(...modelFns: (() => { providerID: string, modelID: string } | undefined)[]) {
+ function getFirstValidModel(
+ ...modelFns: (() => { providerID: string; modelID: string } | undefined)[]
+ ) {
for (const modelFn of modelFns) {
const model = modelFn()
if (!model) continue
- if (isModelValid(model))
- return model
+ if (isModelValid(model)) return model
}
}
@@ -141,7 +142,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
.then((x) => {
setModelStore("recent", x.recent)
})
- .catch(() => { })
+ .catch(() => {})
.finally(() => {
setModelStore("ready", true)
})
@@ -227,49 +228,9 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
}
})
- const kv = iife(() => {
- const [ready, setReady] = createSignal(false)
- const [kvStore, setKvStore] = createStore({
- openrouter_warning: false,
- })
- const file = Bun.file(path.join(Global.Path.state, "kv.json"))
-
- file
- .json()
- .then((x) => {
- setKvStore(x)
- })
- .catch(() => { })
- .finally(() => {
- setReady(true)
- })
-
- return {
- get data() {
- return kvStore
- },
- get ready() {
- return ready()
- },
- set(key: string, value: any) {
- setKvStore(key as any, value)
- Bun.write(
- file,
- JSON.stringify({
- [key]: value,
- }),
- )
- },
- }
- })
-
const result = {
model,
agent,
- kv,
- get ready() {
- return kv.ready && model.ready
- },
}
return result
},
diff --git a/packages/opencode/src/cli/cmd/tui/context/theme.tsx b/packages/opencode/src/cli/cmd/tui/context/theme.tsx
index 894b87b0..74f403d5 100644
--- a/packages/opencode/src/cli/cmd/tui/context/theme.tsx
+++ b/packages/opencode/src/cli/cmd/tui/context/theme.tsx
@@ -1,5 +1,5 @@
import { SyntaxStyle, RGBA } from "@opentui/core"
-import { createMemo, createSignal, createEffect } from "solid-js"
+import { createMemo } from "solid-js"
import { useSync } from "@tui/context/sync"
import { createSimpleContext } from "./helper"
import aura from "../../../../../../tui/internal/theme/themes/aura.json" with { type: "json" }
@@ -24,8 +24,7 @@ import synthwave84 from "../../../../../../tui/internal/theme/themes/synthwave84
import tokyonight from "../../../../../../tui/internal/theme/themes/tokyonight.json" with { type: "json" }
import vesper from "../../../../../../tui/internal/theme/themes/vesper.json" with { type: "json" }
import zenburn from "../../../../../../tui/internal/theme/themes/zenburn.json" with { type: "json" }
-import { iife } from "@/util/iife"
-import { createStore, reconcile } from "solid-js/store"
+import { useKV } from "./kv"
type Theme = {
primary: RGBA
@@ -628,28 +627,28 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
name: "Theme",
init: () => {
const sync = useSync()
- const [selectedTheme, setSelectedTheme] = createSignal("opencode")
- const [theme, setTheme] = createStore({} as Theme)
- createEffect(() => {
- if (!sync.ready) return
- setSelectedTheme(
- iife(() => {
- if (typeof sync.data.config.theme === "string" && sync.data.config.theme in THEMES) {
- return sync.data.config.theme as keyof typeof THEMES
- }
- return "opencode"
- }),
- )
- })
+ const kv = useKV()
- createEffect(() => {
- setTheme(reconcile(THEMES[selectedTheme()]))
+ const theme = createMemo(() => {
+ console.log(kv.data.theme)
+ return { ...(THEMES[kv.data.theme as keyof typeof THEMES] ?? THEMES.opencode) }
})
return {
- theme,
- selectedTheme,
- setSelectedTheme,
+ get theme() {
+ return new Proxy(theme(), {
+ get(_target, prop) {
+ // @ts-expect-error
+ return theme()[prop]
+ },
+ })
+ },
+ get selectedTheme() {
+ return kv.data.theme
+ },
+ setSelectedTheme(theme: string) {
+ kv.set("theme", theme)
+ },
get ready() {
return sync.ready
},