This commit is contained in:
Dax Raad 2025-11-01 13:17:54 -04:00
parent ef1ef6b9ac
commit e36c80dd4e
5 changed files with 56 additions and 49 deletions

View file

@ -82,7 +82,7 @@ async function getTerminalBackgroundColor(): Promise<"dark" | "light"> {
timeout = setTimeout(() => {
cleanup()
resolve("dark")
}, 5000)
}, 1000)
})
}
@ -97,6 +97,7 @@ export function tui(input: {
// promise to prevent immediate exit
return new Promise<void>(async (resolve) => {
const mode = await getTerminalBackgroundColor()
console.log(mode)
const routeData: Route | undefined = input.sessionID
? {

View file

@ -14,12 +14,14 @@ export function DialogStatus() {
return (
<box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
<box flexDirection="row" justifyContent="space-between">
<text attributes={TextAttributes.BOLD}>Status</text>
<text fg={theme.text} attributes={TextAttributes.BOLD}>
Status
</text>
<text fg={theme.textMuted}>esc</text>
</box>
<Show when={Object.keys(sync.data.mcp).length > 0} fallback={<text>No MCP Servers</text>}>
<box>
<text>{Object.keys(sync.data.mcp).length} MCP Servers</text>
<text fg={theme.text}>{Object.keys(sync.data.mcp).length} MCP Servers</text>
<For each={Object.entries(sync.data.mcp)}>
{([key, item]) => (
<box flexDirection="row" gap={1}>
@ -35,7 +37,7 @@ export function DialogStatus() {
>
</text>
<text wrapMode="word">
<text fg={theme.text} wrapMode="word">
<b>{key}</b>{" "}
<span style={{ fg: theme.textMuted }}>
<Switch>
@ -52,7 +54,7 @@ export function DialogStatus() {
</Show>
{sync.data.lsp.length > 0 && (
<box>
<text>{sync.data.lsp.length} LSP Servers</text>
<text fg={theme.text}>{sync.data.lsp.length} LSP Servers</text>
<For each={sync.data.lsp}>
{(item) => (
<box flexDirection="row" gap={1}>
@ -67,7 +69,7 @@ export function DialogStatus() {
>
</text>
<text wrapMode="word">
<text fg={theme.text} wrapMode="word">
<b>{item.id}</b> <span style={{ fg: theme.textMuted }}>{item.root}</span>
</text>
</box>
@ -75,9 +77,12 @@ export function DialogStatus() {
</For>
</box>
)}
<Show when={enabledFormatters().length > 0} fallback={<text>No Formatters</text>}>
<Show
when={enabledFormatters().length > 0}
fallback={<text fg={theme.text}>No Formatters</text>}
>
<box>
<text>{enabledFormatters().length} Formatters</text>
<text fg={theme.text}>{enabledFormatters().length} Formatters</text>
<For each={enabledFormatters()}>
{(item) => (
<box flexDirection="row" gap={1}>
@ -89,7 +94,7 @@ export function DialogStatus() {
>
</text>
<text wrapMode="word">
<text wrapMode="word" fg={theme.text}>
<b>{item.name}</b>
</text>
</box>

View file

@ -83,37 +83,36 @@ type ThemeJson = {
theme: Record<keyof Theme, ColorValue>
}
export const THEMES: Record<string, Theme> = {
aura: resolveTheme(aura),
ayu: resolveTheme(ayu),
catppuccin: resolveTheme(catppuccin),
cobalt2: resolveTheme(cobalt2),
dracula: resolveTheme(dracula),
everforest: resolveTheme(everforest),
github: resolveTheme(github),
gruvbox: resolveTheme(gruvbox),
kanagawa: resolveTheme(kanagawa),
material: resolveTheme(material),
matrix: resolveTheme(matrix),
monokai: resolveTheme(monokai),
nord: resolveTheme(nord),
["one-dark"]: resolveTheme(onedark),
opencode: resolveTheme(opencode),
palenight: resolveTheme(palenight),
rosepine: resolveTheme(rosepine),
solarized: resolveTheme(solarized),
synthwave84: resolveTheme(synthwave84),
tokyonight: resolveTheme(tokyonight),
vesper: resolveTheme(vesper),
zenburn: resolveTheme(zenburn),
export const THEMES: Record<string, ThemeJson> = {
aura,
ayu,
catppuccin,
cobalt2,
dracula,
everforest,
github,
gruvbox,
kanagawa,
material,
matrix,
monokai,
nord,
["one-dark"]: onedark,
opencode,
palenight,
rosepine,
solarized,
synthwave84,
tokyonight,
vesper,
zenburn,
}
function resolveTheme(theme: ThemeJson) {
function resolveTheme(theme: ThemeJson, mode: "dark" | "light") {
const defs = theme.defs ?? {}
function resolveColor(c: ColorValue): RGBA {
if (typeof c === "string") return c.startsWith("#") ? RGBA.fromHex(c) : resolveColor(defs[c])
// TODO: support light theme when opentui has the equivalent of lipgloss.AdaptiveColor
return resolveColor(c.light)
return resolveColor(c[mode])
}
return Object.fromEntries(
Object.entries(theme.theme).map(([key, value]) => {
@ -632,7 +631,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
const [theme, setTheme] = createSignal(sync.data.config.theme ?? kv.get("theme", "opencode"))
const values = createMemo(() => {
return THEMES[theme()] ?? THEMES.opencode
return resolveTheme(THEMES[theme()] ?? THEMES.opencode, props.mode)
})
return {

View file

@ -641,7 +641,7 @@ function UserMessage(props: {
borderColor={color()}
flexShrink={0}
>
<text>{text()?.text}</text>
<text fg={theme.text}>{text()?.text}</text>
<Show when={files().length}>
<box flexDirection="row" paddingBottom={1} paddingTop={1} gap={1} flexWrap="wrap">
<For each={files()}>
@ -652,7 +652,7 @@ function UserMessage(props: {
return theme.secondary
})
return (
<text>
<text fg={theme.text}>
<span style={{ bg: bg(), fg: theme.background }}>
{" "}
{MIME_BADGE[file.mime] ?? file.mime}{" "}
@ -667,7 +667,7 @@ function UserMessage(props: {
</For>
</box>
</Show>
<text>
<text fg={theme.text}>
{sync.data.config.username ?? "You"}{" "}
<Show
when={queued()}
@ -782,7 +782,7 @@ function ReasoningPart(props: { part: ReasoningPart; message: AssistantMessage }
paddingLeft={2}
backgroundColor={theme.backgroundPanel}
>
<text>{props.part.text.trim()}</text>
<text fg={theme.text}>{props.part.text.trim()}</text>
</box>
</box>
</Show>
@ -1131,6 +1131,7 @@ ToolRegistry.register<typeof EditTool>({
container: "block",
render(props) {
const ctx = use()
const { theme } = useTheme()
const style = createMemo(() => (ctx.width > 120 ? "split" : "stacked"))
@ -1210,7 +1211,7 @@ ToolRegistry.register<typeof EditTool>({
</ToolTitle>
<Switch>
<Match when={props.permission["diff"]}>
<text>{props.permission["diff"]?.trim()}</text>
<text fg={theme.text}>{props.permission["diff"]?.trim()}</text>
</Match>
<Match when={diff() && style() === "split"}>
<box paddingLeft={1} flexDirection="row" gap={2}>
@ -1237,6 +1238,7 @@ ToolRegistry.register<typeof PatchTool>({
name: "patch",
container: "block",
render(props) {
const { theme } = useTheme()
return (
<>
<ToolTitle icon="%" fallback="Preparing patch..." when={true}>
@ -1244,7 +1246,7 @@ ToolRegistry.register<typeof PatchTool>({
</ToolTitle>
<Show when={props.output}>
<box>
<text>{props.output?.trim()}</text>
<text fg={theme.text}>{props.output?.trim()}</text>
</box>
</Show>
</>

View file

@ -42,7 +42,7 @@ export function Sidebar(props: { sessionID: string }) {
<Show when={session()}>
<box flexShrink={0} gap={1} width={40}>
<box>
<text>
<text fg={theme.text}>
<b>{session().title}</b>
</text>
<Show when={session().share?.url}>
@ -50,7 +50,7 @@ export function Sidebar(props: { sessionID: string }) {
</Show>
</box>
<box>
<text>
<text fg={theme.text}>
<b>Context</b>
</text>
<text fg={theme.textMuted}>{context()?.tokens ?? 0} tokens</text>
@ -59,7 +59,7 @@ export function Sidebar(props: { sessionID: string }) {
</box>
<Show when={Object.keys(sync.data.mcp).length > 0}>
<box>
<text>
<text fg={theme.text}>
<b>MCP</b>
</text>
<For each={Object.entries(sync.data.mcp)}>
@ -77,7 +77,7 @@ export function Sidebar(props: { sessionID: string }) {
>
</text>
<text wrapMode="word">
<text fg={theme.text} wrapMode="word">
{key}{" "}
<span style={{ fg: theme.textMuted }}>
<Switch>
@ -96,7 +96,7 @@ export function Sidebar(props: { sessionID: string }) {
</Show>
<Show when={sync.data.lsp.length > 0}>
<box>
<text>
<text fg={theme.text}>
<b>LSP</b>
</text>
<For each={sync.data.lsp}>
@ -123,7 +123,7 @@ export function Sidebar(props: { sessionID: string }) {
</Show>
<Show when={session().summary?.diffs}>
<box>
<text>
<text fg={theme.text}>
<b>Modified Files</b>
</text>
<For each={session().summary?.diffs || []}>
@ -155,7 +155,7 @@ export function Sidebar(props: { sessionID: string }) {
</Show>
<Show when={todo().length > 0}>
<box>
<text>
<text fg={theme.text}>
<b>Todo</b>
</text>
<For each={todo()}>