mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-03 06:23:34 +00:00
refactor(tui): split theme hooks (#39395)
This commit is contained in:
parent
f6fb1a7cdd
commit
08b80da931
50 changed files with 833 additions and 814 deletions
|
|
@ -66,7 +66,7 @@ import { DialogHelp } from "./ui/dialog-help"
|
|||
import { DialogAgent } from "./component/dialog-agent"
|
||||
import { DialogSessionList } from "./component/dialog-session-list"
|
||||
import { ThemeErrorToast } from "./component/theme-error-toast"
|
||||
import { ThemeProvider, useTheme } from "./context/theme"
|
||||
import { ThemeProvider, useTheme, useThemes } from "./context/theme"
|
||||
import { Home } from "./routes/home"
|
||||
import { Session } from "./routes/session"
|
||||
import { PromptHistoryProvider } from "./component/prompt/history"
|
||||
|
|
@ -423,8 +423,8 @@ function App(props: { pair?: DialogPairCredentials }) {
|
|||
const event = useEvent()
|
||||
const client = useClient()
|
||||
const toast = useToast()
|
||||
const themeState = useTheme()
|
||||
const { themeV2, mode, supports, setMode, locked, lock, unlock } = themeState
|
||||
const theme = useTheme()
|
||||
const { mode, supports, setMode, locked, lock, unlock } = useThemes()
|
||||
const data = useData()
|
||||
const location = useLocation()
|
||||
const exit = useExit()
|
||||
|
|
@ -1090,7 +1090,7 @@ function App(props: { pair?: DialogPairCredentials }) {
|
|||
width={dimensions().width}
|
||||
height={dimensions().height}
|
||||
flexDirection="column"
|
||||
backgroundColor={themeV2.background.default}
|
||||
backgroundColor={theme.background.default}
|
||||
onMouseDown={(evt) => {
|
||||
if (copyOnSelectEnabled()) return
|
||||
if (evt.button !== MouseButton.RIGHT) return
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
} from "@opentui/core"
|
||||
import { extend, useRenderer } from "@opentui/solid"
|
||||
import { onCleanup, onMount } from "solid-js"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { tint } from "../theme/color"
|
||||
import { GoUpsellArtPainter } from "./bg-pulse-render"
|
||||
|
||||
|
|
@ -70,7 +70,9 @@ declare module "@opentui/solid" {
|
|||
extend({ go_upsell_art: GoUpsellArtRenderable })
|
||||
|
||||
export function BgPulse() {
|
||||
const { themeV2, mode } = useTheme().contextual("elevated")
|
||||
const themes = useThemes()
|
||||
const theme = themes.contextual("elevated")
|
||||
const mode = themes.mode
|
||||
const renderer = useRenderer()
|
||||
let targetFps = renderer.targetFps
|
||||
let maxFps = renderer.maxFps
|
||||
|
|
@ -91,9 +93,9 @@ export function BgPulse() {
|
|||
<go_upsell_art
|
||||
width="100%"
|
||||
height="100%"
|
||||
backgroundPanel={themeV2.background.default}
|
||||
primary={themeV2.hue.interactive[mode() === "light" ? 800 : 200]}
|
||||
logoBase={tint(themeV2.background.default, themeV2.text.default, 0.62)}
|
||||
backgroundPanel={theme.background.default}
|
||||
primary={theme.hue.interactive[mode() === "light" ? 800 : 200]}
|
||||
logoBase={tint(theme.background.default, theme.text.default, 0.62)}
|
||||
live
|
||||
/>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { useData } from "../context/data"
|
|||
import { useLocation } from "../context/location"
|
||||
import { useRoute } from "../context/route"
|
||||
import { Keymap } from "../context/keymap"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useTheme, useThemes } from "../context/theme"
|
||||
import { DevTools } from "../devtools"
|
||||
import { usePlugin } from "../plugin/context"
|
||||
import { errorMessage } from "../util/error"
|
||||
|
|
@ -31,12 +31,12 @@ export function DevToolsBar() {
|
|||
const location = useLocation()
|
||||
const route = useRoute()
|
||||
const plugins = usePlugin()
|
||||
const theme = useTheme()
|
||||
const themes = useThemes()
|
||||
const keymap = Keymap.use()
|
||||
const renderer = useRenderer()
|
||||
const dimensions = useTerminalDimensions()
|
||||
const { themeV2, mode, supports, setMode } = theme
|
||||
const elevatedTheme = theme.contextual("elevated").themeV2
|
||||
const { current: theme, mode, supports, setMode } = themes
|
||||
const elevatedTheme = themes.contextual("elevated")
|
||||
const [panel, setPanel] = createSignal<Panel>()
|
||||
const [dumping, setDumping] = createSignal(false)
|
||||
const [dumpPath, setDumpPath] = createSignal<string>()
|
||||
|
|
@ -196,8 +196,8 @@ export function DevToolsBar() {
|
|||
})),
|
||||
},
|
||||
theme: {
|
||||
name: theme.selected,
|
||||
mode: theme.mode(),
|
||||
name: themes.selected,
|
||||
mode: themes.mode(),
|
||||
},
|
||||
},
|
||||
null,
|
||||
|
|
@ -213,7 +213,7 @@ export function DevToolsBar() {
|
|||
}
|
||||
|
||||
return (
|
||||
<box height={1} flexShrink={0} flexDirection="row" backgroundColor={themeV2.raise(themeV2.background.default)}>
|
||||
<box height={1} flexShrink={0} flexDirection="row" backgroundColor={theme.raise(theme.background.default)}>
|
||||
<Show when={panel()}>
|
||||
<box
|
||||
position="absolute"
|
||||
|
|
@ -230,12 +230,12 @@ export function DevToolsBar() {
|
|||
<text
|
||||
fg={
|
||||
panel() === "server"
|
||||
? themeV2.text.action.primary.focused
|
||||
? theme.text.action.primary.focused
|
||||
: serverIndicator().state === "connected"
|
||||
? themeV2.text.feedback.success.default
|
||||
? theme.text.feedback.success.default
|
||||
: serverIndicator().state === "disconnected"
|
||||
? themeV2.text.feedback.error.default
|
||||
: themeV2.text.default
|
||||
? theme.text.feedback.error.default
|
||||
: theme.text.default
|
||||
}
|
||||
>
|
||||
{serverIndicator().icon}
|
||||
|
|
@ -243,10 +243,10 @@ export function DevToolsBar() {
|
|||
<text
|
||||
fg={
|
||||
panel() === "server"
|
||||
? themeV2.text.action.primary.focused
|
||||
? theme.text.action.primary.focused
|
||||
: serverIndicator().state === "disconnected"
|
||||
? themeV2.text.feedback.error.default
|
||||
: themeV2.text.subdued
|
||||
? theme.text.feedback.error.default
|
||||
: theme.text.subdued
|
||||
}
|
||||
>
|
||||
{" "}
|
||||
|
|
@ -279,10 +279,10 @@ export function DevToolsBar() {
|
|||
<text
|
||||
fg={
|
||||
panel() === "ui"
|
||||
? themeV2.text.action.primary.focused
|
||||
? theme.text.action.primary.focused
|
||||
: runtime() === "high"
|
||||
? themeV2.text.feedback.error.default
|
||||
: themeV2.text.subdued
|
||||
? theme.text.feedback.error.default
|
||||
: theme.text.subdued
|
||||
}
|
||||
>
|
||||
{statusIcon(runtime())}
|
||||
|
|
@ -290,10 +290,10 @@ export function DevToolsBar() {
|
|||
<text
|
||||
fg={
|
||||
panel() === "ui"
|
||||
? themeV2.text.action.primary.focused
|
||||
? theme.text.action.primary.focused
|
||||
: runtime() === "high"
|
||||
? themeV2.text.feedback.error.default
|
||||
: themeV2.text.subdued
|
||||
? theme.text.feedback.error.default
|
||||
: theme.text.subdued
|
||||
}
|
||||
>
|
||||
{" "}
|
||||
|
|
@ -320,11 +320,11 @@ export function DevToolsBar() {
|
|||
</Show>
|
||||
</BarItem>
|
||||
<BarItem active={panel() === "theme"} onClick={() => toggle("theme")}>
|
||||
<text fg={panel() === "theme" ? themeV2.text.action.primary.focused : themeV2.text.subdued}>Theme</text>
|
||||
<text fg={panel() === "theme" ? theme.text.action.primary.focused : theme.text.subdued}>Theme</text>
|
||||
<Show when={panel() === "theme"}>
|
||||
<PanelBox>
|
||||
<PanelTitle>Theme</PanelTitle>
|
||||
<Row label="Name" value={theme.selected} />
|
||||
<Row label="Name" value={themes.selected} />
|
||||
<Row label="Mode" value={mode()} />
|
||||
<For each={themePerformance()}>{(entry) => <Row label={entry.key} value={String(entry.value)} />}</For>
|
||||
<Show when={canSwitchMode()}>
|
||||
|
|
@ -336,7 +336,7 @@ export function DevToolsBar() {
|
|||
</Show>
|
||||
</BarItem>
|
||||
<BarItem active={panel() === "tools"} onClick={() => toggle("tools")}>
|
||||
<text fg={panel() === "tools" ? themeV2.text.action.primary.focused : themeV2.text.subdued}>Tools</text>
|
||||
<text fg={panel() === "tools" ? theme.text.action.primary.focused : theme.text.subdued}>Tools</text>
|
||||
<Show when={panel() === "tools"}>
|
||||
<PanelBox>
|
||||
<PanelTitle>Tools</PanelTitle>
|
||||
|
|
@ -406,14 +406,14 @@ export function DevToolsBar() {
|
|||
</Show>
|
||||
</BarItem>
|
||||
<box flexGrow={1} minWidth={0}>
|
||||
<TimeToFirstDraw visible={timing()} width="100%" fg={themeV2.text.subdued} label="Time to first draw" />
|
||||
<TimeToFirstDraw visible={timing()} width="100%" fg={theme.text.subdued} label="Time to first draw" />
|
||||
</box>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
||||
function BarItem(props: ParentProps<{ active: boolean; onClick: () => void }>) {
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
const renderer = useRenderer()
|
||||
return (
|
||||
<box
|
||||
|
|
@ -423,7 +423,7 @@ function BarItem(props: ParentProps<{ active: boolean; onClick: () => void }>) {
|
|||
flexDirection="row"
|
||||
paddingLeft={1}
|
||||
paddingRight={1}
|
||||
backgroundColor={props.active ? themeV2.background.action.primary.focused : undefined}
|
||||
backgroundColor={props.active ? theme.background.action.primary.focused : undefined}
|
||||
onMouseUp={() => {
|
||||
if (renderer.getSelection()?.getSelectedText()) return
|
||||
props.onClick()
|
||||
|
|
@ -435,7 +435,7 @@ function BarItem(props: ParentProps<{ active: boolean; onClick: () => void }>) {
|
|||
}
|
||||
|
||||
function PanelBox(props: ParentProps) {
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const renderer = useRenderer()
|
||||
return (
|
||||
<box
|
||||
|
|
@ -448,7 +448,7 @@ function PanelBox(props: ParentProps) {
|
|||
paddingRight={2}
|
||||
paddingTop={1}
|
||||
paddingBottom={1}
|
||||
backgroundColor={themeV2.background.default}
|
||||
backgroundColor={theme.background.default}
|
||||
flexDirection="column"
|
||||
onMouseUp={(event) => {
|
||||
if (renderer.getSelection()?.getSelectedText()) return
|
||||
|
|
@ -461,32 +461,32 @@ function PanelBox(props: ParentProps) {
|
|||
}
|
||||
|
||||
function PanelTitle(props: ParentProps) {
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
return (
|
||||
<text fg={themeV2.text.default} attributes={TextAttributes.BOLD} marginBottom={1}>
|
||||
<text fg={theme.text.default} attributes={TextAttributes.BOLD} marginBottom={1}>
|
||||
{props.children}
|
||||
</text>
|
||||
)
|
||||
}
|
||||
|
||||
function Row(props: { label: string; value: string }) {
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
return (
|
||||
<box flexDirection="row">
|
||||
<text fg={themeV2.text.subdued}>{props.label}</text>
|
||||
<text fg={theme.text.subdued}>{props.label}</text>
|
||||
<box flexGrow={1} />
|
||||
<text fg={themeV2.text.default}>{props.value}</text>
|
||||
<text fg={theme.text.default}>{props.value}</text>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
||||
function Action(props: ParentProps<{ onClick: () => void; disabled?: boolean; hoverBackground?: boolean }>) {
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const [hovered, setHovered] = createSignal(false)
|
||||
return (
|
||||
<box
|
||||
backgroundColor={
|
||||
props.hoverBackground && hovered() && !props.disabled ? themeV2.background.action.primary.hovered : undefined
|
||||
props.hoverBackground && hovered() && !props.disabled ? theme.background.action.primary.hovered : undefined
|
||||
}
|
||||
onMouseOver={() => setHovered(true)}
|
||||
onMouseOut={() => setHovered(false)}
|
||||
|
|
@ -495,7 +495,7 @@ function Action(props: ParentProps<{ onClick: () => void; disabled?: boolean; ho
|
|||
if (!props.disabled) props.onClick()
|
||||
}}
|
||||
>
|
||||
<text fg={props.disabled ? themeV2.text.subdued : themeV2.text.action.primary.default}>{props.children}</text>
|
||||
<text fg={props.disabled ? theme.text.subdued : theme.text.action.primary.default}>{props.children}</text>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
|
@ -506,7 +506,7 @@ function cpuPercent(microseconds: number, milliseconds: number) {
|
|||
}
|
||||
|
||||
function ProcessStat(props: { label: string; values: readonly number[]; unit: string; decimals?: number }) {
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const value = () => {
|
||||
const value = props.values.at(-1)
|
||||
if (value === undefined) return "--"
|
||||
|
|
@ -515,13 +515,13 @@ function ProcessStat(props: { label: string; values: readonly number[]; unit: st
|
|||
return (
|
||||
<box flexDirection="row">
|
||||
<box width={7}>
|
||||
<text fg={themeV2.text.subdued}>{props.label}</text>
|
||||
<text fg={theme.text.subdued}>{props.label}</text>
|
||||
</box>
|
||||
<box flexGrow={1}>
|
||||
<text fg={props.values.length ? themeV2.text.default : themeV2.text.subdued}>{brailleGraph(props.values)}</text>
|
||||
<text fg={props.values.length ? theme.text.default : theme.text.subdued}>{brailleGraph(props.values)}</text>
|
||||
</box>
|
||||
<box width={8} alignItems="flex-end">
|
||||
<text fg={props.values.length ? themeV2.text.default : themeV2.text.subdued}>{value()}</text>
|
||||
<text fg={props.values.length ? theme.text.default : theme.text.subdued}>{value()}</text>
|
||||
</box>
|
||||
</box>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { createMemo, createSignal } from "solid-js"
|
||||
import { useConfig } from "../config"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { DialogSelect } from "../ui/dialog-select"
|
||||
import { useToast } from "../ui/toast"
|
||||
|
||||
|
|
@ -267,7 +267,7 @@ export function settingID(setting: Setting) {
|
|||
export function DialogConfig(props: { current?: string }) {
|
||||
const config = useConfig()
|
||||
const toast = useToast()
|
||||
const themeState = useTheme()
|
||||
const themes = useThemes()
|
||||
const current = Math.max(
|
||||
0,
|
||||
settings.findIndex((setting) => settingID(setting) === props.current),
|
||||
|
|
@ -280,12 +280,12 @@ export function DialogConfig(props: { current?: string }) {
|
|||
if (!result || typeof result !== "object") return undefined
|
||||
return (result as Record<string, unknown>)[key]
|
||||
}, config.data)
|
||||
if (setting.path.join(".") === "theme.name") return current ?? themeState.selected
|
||||
if (setting.path.join(".") === "theme.name") return current ?? themes.selected
|
||||
return current ?? setting.default
|
||||
}
|
||||
const values = (setting: Setting) =>
|
||||
setting.path.join(".") === "theme.name"
|
||||
? Object.keys(themeState.all()).sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }))
|
||||
? Object.keys(themes.all()).sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }))
|
||||
: setting.values
|
||||
const display = (setting: Setting) => {
|
||||
const current = value(setting)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { describeOS, describeTerminal } from "../util/system"
|
|||
import { useTuiApp } from "../context/runtime"
|
||||
|
||||
export function DialogDebug() {
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
const dialog = useDialog()
|
||||
const route = useRoute()
|
||||
const local = useLocal()
|
||||
|
|
@ -55,10 +55,10 @@ export function DialogDebug() {
|
|||
return (
|
||||
<box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={themeV2.text.default} attributes={TextAttributes.BOLD}>
|
||||
<text fg={theme.text.default} attributes={TextAttributes.BOLD}>
|
||||
Debug
|
||||
</text>
|
||||
<text fg={themeV2.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={theme.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
|
|
@ -68,10 +68,10 @@ export function DialogDebug() {
|
|||
<For each={entries()}>
|
||||
{(entry) => (
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text flexShrink={0} fg={themeV2.text.subdued}>
|
||||
<text flexShrink={0} fg={theme.text.subdued}>
|
||||
{entry.label.padEnd(10)}
|
||||
</text>
|
||||
<text fg={themeV2.text.default} wrapMode="word">
|
||||
<text fg={theme.text.default} wrapMode="word">
|
||||
{entry.value}
|
||||
</text>
|
||||
</box>
|
||||
|
|
@ -79,12 +79,12 @@ export function DialogDebug() {
|
|||
</For>
|
||||
</box>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={themeV2.text.subdued}>Share this when reporting an issue.</text>
|
||||
<text fg={theme.text.subdued}>Share this when reporting an issue.</text>
|
||||
<text onMouseUp={copy}>
|
||||
<span style={{ fg: copied() ? themeV2.text.feedback.success.default : themeV2.text.default }}>
|
||||
<span style={{ fg: copied() ? theme.text.feedback.success.default : theme.text.default }}>
|
||||
<b>{copied() ? "✓ copied" : "copy"}</b>{" "}
|
||||
</span>
|
||||
<span style={{ fg: themeV2.text.subdued }}>enter</span>
|
||||
<span style={{ fg: theme.text.subdued }}>enter</span>
|
||||
</text>
|
||||
</box>
|
||||
</box>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { useClipboard } from "../context/clipboard"
|
|||
import { useData } from "../context/data"
|
||||
import { useClient } from "../context/client"
|
||||
import { Keymap } from "../context/keymap"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { useDialog } from "../ui/dialog"
|
||||
import { DialogPrompt } from "../ui/dialog-prompt"
|
||||
import { DialogSelect } from "../ui/dialog-select"
|
||||
|
|
@ -64,7 +64,7 @@ export function DialogIntegration(
|
|||
) {
|
||||
const data = useData()
|
||||
const dialog = useDialog()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const options = createMemo(() => {
|
||||
const providers = data.location.websearch.list() ?? []
|
||||
const providersByID = new Map(providers.map((provider) => [provider.id, provider]))
|
||||
|
|
@ -87,7 +87,7 @@ export function DialogIntegration(
|
|||
disabled: methods.length === 0 && credentials.length === 0,
|
||||
gutter:
|
||||
integration.connections.length > 0
|
||||
? () => <text fg={themeV2.text.feedback.success.default}>✓</text>
|
||||
? () => <text fg={theme.text.feedback.success.default}>✓</text>
|
||||
: undefined,
|
||||
onSelect: () => {
|
||||
if (credentials.length) return manageConnections(integration, methods, dialog, props.onConnected)
|
||||
|
|
@ -103,12 +103,12 @@ export function DialogIntegration(
|
|||
options={options()}
|
||||
emptyView={
|
||||
<box paddingLeft={4} paddingRight={4} paddingTop={1}>
|
||||
<text fg={themeV2.text.subdued}>No integrations available</text>
|
||||
<text fg={theme.text.subdued}>No integrations available</text>
|
||||
</box>
|
||||
}
|
||||
noMatchView={
|
||||
<box paddingLeft={4} paddingRight={4} paddingTop={1}>
|
||||
<text fg={themeV2.text.subdued}>No integrations found</text>
|
||||
<text fg={theme.text.subdued}>No integrations found</text>
|
||||
</box>
|
||||
}
|
||||
/>
|
||||
|
|
@ -303,16 +303,16 @@ function CommandPending(props: {
|
|||
|
||||
function CommandView(props: { title: string; output: string; message: string }) {
|
||||
const dialog = useDialog()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const { themeV2: overlayTheme } = useTheme().contextual("overlay")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const overlayTheme = useThemes().contextual("overlay")
|
||||
onMount(() => dialog.setSize("large"))
|
||||
return (
|
||||
<box gap={1} paddingBottom={1}>
|
||||
<box flexDirection="row" justifyContent="space-between" paddingLeft={2} paddingRight={2}>
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text.default}>
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text.default}>
|
||||
{props.title}
|
||||
</text>
|
||||
<text fg={themeV2.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={theme.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
esc close
|
||||
</text>
|
||||
</box>
|
||||
|
|
@ -326,7 +326,7 @@ function CommandView(props: { title: string; output: string; message: string })
|
|||
<text fg={overlayTheme.text.default}>{props.output.trim()}</text>
|
||||
</box>
|
||||
<box paddingLeft={2} paddingRight={2}>
|
||||
<text fg={themeV2.text.subdued}>{props.message}</text>
|
||||
<text fg={theme.text.subdued}>{props.message}</text>
|
||||
</box>
|
||||
</box>
|
||||
)
|
||||
|
|
@ -341,7 +341,7 @@ function KeyMethod(props: {
|
|||
const dialog = useDialog()
|
||||
const client = useClient()
|
||||
const toast = useToast()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const [error, setError] = createSignal<string>()
|
||||
|
||||
return (
|
||||
|
|
@ -360,7 +360,7 @@ function KeyMethod(props: {
|
|||
.catch((cause) => setError(message(cause)))
|
||||
}}
|
||||
description={() => (
|
||||
<Show when={error()}>{(value) => <text fg={themeV2.text.feedback.error.default}>{value()}</text>}</Show>
|
||||
<Show when={error()}>{(value) => <text fg={theme.text.feedback.error.default}>{value()}</text>}</Show>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
|
|
@ -516,7 +516,7 @@ function OAuthCode(props: {
|
|||
const dialog = useDialog()
|
||||
const client = useClient()
|
||||
const toast = useToast()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const [error, setError] = createSignal<string>()
|
||||
let settled = false
|
||||
|
||||
|
|
@ -550,9 +550,9 @@ function OAuthCode(props: {
|
|||
}}
|
||||
description={() => (
|
||||
<box gap={1}>
|
||||
<text fg={themeV2.text.subdued}>{props.attempt.instructions}</text>
|
||||
<Link href={props.attempt.url} fg={themeV2.markdown.link} />
|
||||
<Show when={error()}>{(value) => <text fg={themeV2.text.feedback.error.default}>{value()}</text>}</Show>
|
||||
<text fg={theme.text.subdued}>{props.attempt.instructions}</text>
|
||||
<Link href={props.attempt.url} fg={theme.markdown.link} />
|
||||
<Show when={error()}>{(value) => <text fg={theme.text.feedback.error.default}>{value()}</text>}</Show>
|
||||
</box>
|
||||
)}
|
||||
/>
|
||||
|
|
@ -561,31 +561,31 @@ function OAuthCode(props: {
|
|||
|
||||
function OAuthView(props: { title: string; url?: string; instructions?: string; message: string; copy?: boolean }) {
|
||||
const dialog = useDialog()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
return (
|
||||
<box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text.default}>
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text.default}>
|
||||
{props.title}
|
||||
</text>
|
||||
<text fg={themeV2.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={theme.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
<Show when={props.url}>
|
||||
{(url) => (
|
||||
<box gap={1}>
|
||||
<Link href={url()} fg={themeV2.markdown.link} />
|
||||
<Link href={url()} fg={theme.markdown.link} />
|
||||
<Show when={props.instructions}>
|
||||
{(instructions) => <text fg={themeV2.text.subdued}>{instructions()}</text>}
|
||||
{(instructions) => <text fg={theme.text.subdued}>{instructions()}</text>}
|
||||
</Show>
|
||||
</box>
|
||||
)}
|
||||
</Show>
|
||||
<text fg={themeV2.text.subdued}>{props.message}</text>
|
||||
<text fg={theme.text.subdued}>{props.message}</text>
|
||||
<Show when={props.copy}>
|
||||
<text fg={themeV2.text.default}>
|
||||
c <span style={{ fg: themeV2.text.subdued }}>copy</span>
|
||||
<text fg={theme.text.default}>
|
||||
c <span style={{ fg: theme.text.subdued }}>copy</span>
|
||||
</text>
|
||||
</Show>
|
||||
</box>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { Keymap } from "../context/keymap"
|
|||
import { pipe, sortBy } from "remeda"
|
||||
import { DialogSelect } from "../ui/dialog-select"
|
||||
import { useDialog } from "../ui/dialog"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { TextAttributes, type ScrollBoxRenderable } from "@opentui/core"
|
||||
import type { McpServer } from "@opencode-ai/client"
|
||||
import { useClipboard } from "../context/clipboard"
|
||||
|
|
@ -20,12 +20,12 @@ function statusError(status: McpServer["status"]) {
|
|||
}
|
||||
|
||||
function Status(props: { enabled: boolean; loading: boolean }) {
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
if (props.loading) return <span style={{ fg: themeV2.text.subdued }}>⋯ Loading</span>
|
||||
const theme = useThemes().contextual("elevated")
|
||||
if (props.loading) return <span style={{ fg: theme.text.subdued }}>⋯ Loading</span>
|
||||
if (props.enabled) {
|
||||
return <span style={{ fg: themeV2.text.feedback.success.default, attributes: TextAttributes.BOLD }}>✓ Enabled</span>
|
||||
return <span style={{ fg: theme.text.feedback.success.default, attributes: TextAttributes.BOLD }}>✓ Enabled</span>
|
||||
}
|
||||
return <span style={{ fg: themeV2.text.subdued }}>○ Disabled</span>
|
||||
return <span style={{ fg: theme.text.subdued }}>○ Disabled</span>
|
||||
}
|
||||
|
||||
export function DialogMcp() {
|
||||
|
|
@ -33,7 +33,7 @@ export function DialogMcp() {
|
|||
const dialog = useDialog()
|
||||
const client = useClient()
|
||||
const toast = useToast()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const [focused, setFocused] = createSignal<string>()
|
||||
const [detail, setDetail] = createSignal<McpServer>()
|
||||
const [loading, setLoading] = createSignal<string | null>(null)
|
||||
|
|
@ -110,7 +110,7 @@ export function DialogMcp() {
|
|||
]}
|
||||
footer={
|
||||
<Show when={focusedError()}>
|
||||
<text fg={themeV2.text.subdued}>enter to view error</text>
|
||||
<text fg={theme.text.subdued}>enter to view error</text>
|
||||
</Show>
|
||||
}
|
||||
/>
|
||||
|
|
@ -134,8 +134,8 @@ function DialogMcpError(props: { server: McpServer; onBack: () => void }) {
|
|||
const dialog = useDialog()
|
||||
const clipboard = useClipboard()
|
||||
const toast = useToast()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const { themeV2: overlayTheme } = useTheme().contextual("overlay")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const overlayTheme = useThemes().contextual("overlay")
|
||||
const dimensions = useTerminalDimensions()
|
||||
const config = useConfig().data
|
||||
const [copied, setCopied] = createSignal(false)
|
||||
|
|
@ -171,14 +171,14 @@ function DialogMcpError(props: { server: McpServer; onBack: () => void }) {
|
|||
return (
|
||||
<box paddingLeft={4} paddingRight={4} paddingBottom={1} gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text.default}>
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text.default}>
|
||||
MCP server: {props.server.name}
|
||||
</text>
|
||||
<text fg={themeV2.text.subdued} onMouseUp={props.onBack}>
|
||||
<text fg={theme.text.subdued} onMouseUp={props.onBack}>
|
||||
esc back
|
||||
</text>
|
||||
</box>
|
||||
<text fg={themeV2.text.feedback.error.default}>✗ Failed</text>
|
||||
<text fg={theme.text.feedback.error.default}>✗ Failed</text>
|
||||
<box
|
||||
backgroundColor={overlayTheme.background.default}
|
||||
paddingLeft={2}
|
||||
|
|
@ -198,8 +198,8 @@ function DialogMcpError(props: { server: McpServer; onBack: () => void }) {
|
|||
</scrollbox>
|
||||
</box>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={themeV2.text.subdued}>↑↓ scroll</text>
|
||||
<text fg={themeV2.text.subdued} onMouseUp={copy}>
|
||||
<text fg={theme.text.subdued}>↑↓ scroll</text>
|
||||
<text fg={theme.text.subdued} onMouseUp={copy}>
|
||||
{copied() ? "✓ copied" : "c copy details"}
|
||||
</text>
|
||||
</box>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { DialogSelect, type DialogSelectOption } from "../ui/dialog-select"
|
|||
import { useDialog } from "../ui/dialog"
|
||||
import { useClient } from "../context/client"
|
||||
import { Keymap } from "../context/keymap"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { useData } from "../context/data"
|
||||
import { abbreviateHome } from "../runtime"
|
||||
import { useTuiPaths } from "../context/runtime"
|
||||
|
|
@ -38,7 +38,7 @@ export function DialogMoveSession(props: DialogMoveSessionProps) {
|
|||
const dialog = useDialog()
|
||||
const client = useClient()
|
||||
const dimensions = useTerminalDimensions()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const sessionData = useData()
|
||||
const route = useRoute()
|
||||
const toast = useToast()
|
||||
|
|
@ -172,18 +172,18 @@ export function DialogMoveSession(props: DialogMoveSessionProps) {
|
|||
return {
|
||||
title,
|
||||
titleView: isRemoving ? (
|
||||
<span style={{ fg: themeV2.text.feedback.error.default }}>Deleting {item.location}</span>
|
||||
<span style={{ fg: theme.text.feedback.error.default }}>Deleting {item.location}</span>
|
||||
) : deleting ? (
|
||||
<span style={{ fg: themeV2.text.action.destructive.default }}>
|
||||
<span style={{ fg: theme.text.action.destructive.default }}>
|
||||
Press {shortcuts.get("dialog.move_session.delete")} again to confirm
|
||||
</span>
|
||||
) : suffix ? (
|
||||
<>
|
||||
{visible.slice(0, split)}
|
||||
<span style={{ fg: themeV2.text.subdued }}>{visible.slice(split)}</span>
|
||||
<span style={{ fg: theme.text.subdued }}>{visible.slice(split)}</span>
|
||||
</>
|
||||
) : undefined,
|
||||
bg: deleting ? themeV2.background.action.destructive.default : undefined,
|
||||
bg: deleting ? theme.background.action.destructive.default : undefined,
|
||||
value: {
|
||||
type: "directory",
|
||||
directory: item.location,
|
||||
|
|
@ -316,7 +316,7 @@ export function DialogMoveSession(props: DialogMoveSessionProps) {
|
|||
title="Move session"
|
||||
titleView={
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text fg={themeV2.text.default} attributes={TextAttributes.BOLD}>
|
||||
<text fg={theme.text.default} attributes={TextAttributes.BOLD}>
|
||||
Move session
|
||||
</text>
|
||||
<Show when={working() || directories.loading || loadedProject.loading}>
|
||||
|
|
@ -329,25 +329,25 @@ export function DialogMoveSession(props: DialogMoveSessionProps) {
|
|||
emptyView={
|
||||
showError() ? (
|
||||
<box paddingLeft={4} paddingRight={4} paddingTop={1}>
|
||||
<text fg={themeV2.text.feedback.error.default} attributes={TextAttributes.BOLD}>
|
||||
<text fg={theme.text.feedback.error.default} attributes={TextAttributes.BOLD}>
|
||||
Could not load project directories
|
||||
</text>
|
||||
<text fg={themeV2.text.subdued}>{errorMessage(loadError())}</text>
|
||||
<text fg={themeV2.text.subdued}>Close and reopen Move session to try again.</text>
|
||||
<text fg={theme.text.subdued}>{errorMessage(loadError())}</text>
|
||||
<text fg={theme.text.subdued}>Close and reopen Move session to try again.</text>
|
||||
</box>
|
||||
) : directories.loading || loadedProject.loading ? (
|
||||
<box paddingLeft={4} paddingRight={4} paddingTop={1}>
|
||||
<text fg={themeV2.text.subdued}>Loading project directories…</text>
|
||||
<text fg={theme.text.subdued}>Loading project directories…</text>
|
||||
</box>
|
||||
) : (
|
||||
<box paddingLeft={4} paddingRight={4} paddingTop={1}>
|
||||
<text fg={themeV2.text.subdued}>No project directories available</text>
|
||||
<text fg={theme.text.subdued}>No project directories available</text>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
noMatchView={
|
||||
<box paddingLeft={4} paddingRight={4} paddingTop={1}>
|
||||
<text fg={themeV2.text.subdued}>No project directories found</text>
|
||||
<text fg={theme.text.subdued}>No project directories found</text>
|
||||
</box>
|
||||
}
|
||||
locked={showError() || directories.loading || loadedProject.loading || Boolean(removing())}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { useTerminalDimensions } from "@opentui/solid"
|
|||
import { createMemo, createResource, createSignal, For, Show } from "solid-js"
|
||||
import { renderUnicodeCompact } from "uqr"
|
||||
import { useClient } from "../context/client"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { useDialog } from "../ui/dialog"
|
||||
import { errorMessage } from "../util/error"
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ export function DialogPair(props: { credentials?: DialogPairCredentials }) {
|
|||
const client = useClient()
|
||||
const dialog = useDialog()
|
||||
const dimensions = useTerminalDimensions()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const [loadError, setLoadError] = createSignal<unknown>()
|
||||
const [showPassword, setShowPassword] = createSignal(false)
|
||||
const [passwordHover, setPasswordHover] = createSignal(false)
|
||||
|
|
@ -47,17 +47,17 @@ export function DialogPair(props: { credentials?: DialogPairCredentials }) {
|
|||
<box flexDirection={horizontal() ? "row" : "column"} alignItems={horizontal() ? "flex-start" : "center"} gap={2}>
|
||||
<box width={horizontal() ? 29 : "100%"} flexShrink={0} gap={1}>
|
||||
<box>
|
||||
<text fg={themeV2.text.subdued}>URLs</text>
|
||||
<For each={value.urls}>{(url) => <text fg={themeV2.text.default}>{url}</text>}</For>
|
||||
<text fg={theme.text.subdued}>URLs</text>
|
||||
<For each={value.urls}>{(url) => <text fg={theme.text.default}>{url}</text>}</For>
|
||||
</box>
|
||||
<box>
|
||||
<text fg={themeV2.text.subdued}>Username</text>
|
||||
<text fg={themeV2.text.default}>{value.username}</text>
|
||||
<text fg={theme.text.subdued}>Username</text>
|
||||
<text fg={theme.text.default}>{value.username}</text>
|
||||
</box>
|
||||
<box>
|
||||
<text fg={themeV2.text.subdued}>Password</text>
|
||||
<text fg={theme.text.subdued}>Password</text>
|
||||
<text
|
||||
fg={passwordHover() ? themeV2.text.default : themeV2.text.subdued}
|
||||
fg={passwordHover() ? theme.text.default : theme.text.subdued}
|
||||
wrapMode="word"
|
||||
onMouseOver={() => setPasswordHover(true)}
|
||||
onMouseOut={() => setPasswordHover(false)}
|
||||
|
|
@ -67,7 +67,7 @@ export function DialogPair(props: { credentials?: DialogPairCredentials }) {
|
|||
</text>
|
||||
</box>
|
||||
<Show when={value.urls.some((url) => ["localhost", "127.0.0.1", "[::1]"].includes(new URL(url).hostname))}>
|
||||
<text fg={themeV2.text.subdued} wrapMode="word">
|
||||
<text fg={theme.text.subdued} wrapMode="word">
|
||||
Run `opencode service set hostname 0.0.0.0` to access the service remotely.
|
||||
</text>
|
||||
</Show>
|
||||
|
|
@ -78,7 +78,7 @@ export function DialogPair(props: { credentials?: DialogPairCredentials }) {
|
|||
flexShrink={0}
|
||||
alignItems={horizontal() ? "flex-end" : "center"}
|
||||
>
|
||||
<text fg={themeV2.text.default}>{renderUnicodeCompact(JSON.stringify(value), { border: 1 })}</text>
|
||||
<text fg={theme.text.default}>{renderUnicodeCompact(JSON.stringify(value), { border: 1 })}</text>
|
||||
</box>
|
||||
</box>
|
||||
)
|
||||
|
|
@ -87,17 +87,17 @@ export function DialogPair(props: { credentials?: DialogPairCredentials }) {
|
|||
return (
|
||||
<box paddingLeft={2} paddingRight={2} paddingBottom={1} gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={themeV2.text.default} attributes={TextAttributes.BOLD}>
|
||||
<text fg={theme.text.default} attributes={TextAttributes.BOLD}>
|
||||
Pair
|
||||
</text>
|
||||
<text fg={themeV2.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={theme.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
<Show
|
||||
when={loadError()}
|
||||
fallback={
|
||||
<Show when={info()} fallback={<text fg={themeV2.text.subdued}>Loading server information…</text>}>
|
||||
<Show when={info()} fallback={<text fg={theme.text.subdued}>Loading server information…</text>}>
|
||||
<Show
|
||||
when={dimensions().height >= 36}
|
||||
fallback={
|
||||
|
|
@ -116,11 +116,11 @@ export function DialogPair(props: { credentials?: DialogPairCredentials }) {
|
|||
>
|
||||
{(error) => (
|
||||
<box>
|
||||
<text fg={themeV2.text.feedback.error.default} attributes={TextAttributes.BOLD}>
|
||||
<text fg={theme.text.feedback.error.default} attributes={TextAttributes.BOLD}>
|
||||
Could not load server information
|
||||
</text>
|
||||
<text fg={themeV2.text.subdued}>{errorMessage(error())}</text>
|
||||
<text fg={themeV2.text.subdued}>Close and reopen Pair to try again.</text>
|
||||
<text fg={theme.text.subdued}>{errorMessage(error())}</text>
|
||||
<text fg={theme.text.subdued}>Close and reopen Pair to try again.</text>
|
||||
</box>
|
||||
)}
|
||||
</Show>
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ import { InputRenderable, TextAttributes } from "@opentui/core"
|
|||
import { Slug } from "@opencode-ai/core/util/slug"
|
||||
import { createSignal, onMount } from "solid-js"
|
||||
import { Keymap } from "../context/keymap"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { useDialog, type DialogContext } from "../ui/dialog"
|
||||
|
||||
export function DialogProjectCopyName(props: { onConfirm: (name: string) => void }) {
|
||||
const dialog = useDialog()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const shortcuts = Keymap.useShortcuts()
|
||||
const [inputTarget, setInputTarget] = createSignal<InputRenderable>()
|
||||
let input: InputRenderable
|
||||
|
|
@ -47,10 +47,10 @@ export function DialogProjectCopyName(props: { onConfirm: (name: string) => void
|
|||
return (
|
||||
<box paddingLeft={2} paddingRight={2} gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text.default}>
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text.default}>
|
||||
Name project copy
|
||||
</text>
|
||||
<text fg={themeV2.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={theme.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
|
|
@ -61,17 +61,17 @@ export function DialogProjectCopyName(props: { onConfirm: (name: string) => void
|
|||
}}
|
||||
onSubmit={confirm}
|
||||
placeholder="Project copy name"
|
||||
placeholderColor={themeV2.text.subdued}
|
||||
textColor={themeV2.text.formfield.default}
|
||||
focusedTextColor={themeV2.text.formfield.default}
|
||||
cursorColor={themeV2.text.formfield.default}
|
||||
placeholderColor={theme.text.subdued}
|
||||
textColor={theme.text.formfield.default}
|
||||
focusedTextColor={theme.text.formfield.default}
|
||||
cursorColor={theme.text.formfield.default}
|
||||
/>
|
||||
<box paddingBottom={1} flexDirection="row" gap={2}>
|
||||
<text fg={themeV2.text.default}>
|
||||
enter <span style={{ fg: themeV2.text.subdued }}>submit</span>
|
||||
<text fg={theme.text.default}>
|
||||
enter <span style={{ fg: theme.text.subdued }}>submit</span>
|
||||
</text>
|
||||
<text fg={themeV2.text.default}>
|
||||
{shortcuts.get("dialog.project_copy.generate")} <span style={{ fg: themeV2.text.subdued }}>generate one</span>
|
||||
<text fg={theme.text.default}>
|
||||
{shortcuts.get("dialog.project_copy.generate")} <span style={{ fg: theme.text.subdued }}>generate one</span>
|
||||
</text>
|
||||
</box>
|
||||
</box>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { RGBA, TextAttributes } from "@opentui/core"
|
|||
import open from "open"
|
||||
import { createSignal } from "solid-js"
|
||||
import { Keymap } from "../context/keymap"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { useDialog, type DialogContext } from "../ui/dialog"
|
||||
import { Link } from "../ui/link"
|
||||
import { BgPulse } from "./bg-pulse"
|
||||
|
|
@ -38,9 +38,9 @@ function panelOverlay(color: RGBA) {
|
|||
|
||||
export function DialogRetryAction(props: DialogRetryActionProps) {
|
||||
const dialog = useDialog()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const showGoTreatment = () => props.link === GO_URL
|
||||
const textBg = () => (showGoTreatment() ? panelOverlay(themeV2.background.default) : undefined)
|
||||
const textBg = () => (showGoTreatment() ? panelOverlay(theme.background.default) : undefined)
|
||||
const [selected, setSelected] = createSignal<"dismiss" | "action">("action")
|
||||
|
||||
Keymap.createLayer(() => ({
|
||||
|
|
@ -85,26 +85,26 @@ export function DialogRetryAction(props: DialogRetryActionProps) {
|
|||
) : null}
|
||||
<box zIndex={1} paddingLeft={PAD_X} paddingRight={PAD_X} paddingBottom={1} gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text.default} bg={textBg()}>
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text.default} bg={textBg()}>
|
||||
{props.title}
|
||||
</text>
|
||||
<text fg={themeV2.text.subdued} bg={textBg()} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={theme.text.subdued} bg={textBg()} onMouseUp={() => dialog.clear()}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
<box gap={0}>
|
||||
<text fg={themeV2.text.subdued} bg={textBg()}>
|
||||
<text fg={theme.text.subdued} bg={textBg()}>
|
||||
{props.message}
|
||||
</text>
|
||||
</box>
|
||||
{props.link ? (
|
||||
showGoTreatment() ? (
|
||||
<box alignItems="center" justifyContent="flex-end" height={7} paddingBottom={1}>
|
||||
<Link href={props.link} fg={themeV2.markdown.link} bg={textBg()} wrapMode="none" />
|
||||
<Link href={props.link} fg={theme.markdown.link} bg={textBg()} wrapMode="none" />
|
||||
</box>
|
||||
) : (
|
||||
<box width="100%" flexDirection="row" justifyContent="center" paddingBottom={1}>
|
||||
<Link href={props.link} fg={themeV2.markdown.link} wrapMode="none" />
|
||||
<Link href={props.link} fg={theme.markdown.link} wrapMode="none" />
|
||||
</box>
|
||||
)
|
||||
) : (
|
||||
|
|
@ -115,13 +115,13 @@ export function DialogRetryAction(props: DialogRetryActionProps) {
|
|||
paddingLeft={2}
|
||||
paddingRight={2}
|
||||
backgroundColor={
|
||||
selected() === "dismiss" ? themeV2.background.action.primary.focused : RGBA.fromInts(0, 0, 0, 0)
|
||||
selected() === "dismiss" ? theme.background.action.primary.focused : RGBA.fromInts(0, 0, 0, 0)
|
||||
}
|
||||
onMouseOver={() => setSelected("dismiss")}
|
||||
onMouseUp={() => dismiss(props, dialog)}
|
||||
>
|
||||
<text
|
||||
fg={selected() === "dismiss" ? themeV2.text.action.primary.focused : themeV2.text.subdued}
|
||||
fg={selected() === "dismiss" ? theme.text.action.primary.focused : theme.text.subdued}
|
||||
bg={selected() === "dismiss" ? undefined : textBg()}
|
||||
attributes={selected() === "dismiss" ? TextAttributes.BOLD : undefined}
|
||||
>
|
||||
|
|
@ -132,13 +132,13 @@ export function DialogRetryAction(props: DialogRetryActionProps) {
|
|||
paddingLeft={2}
|
||||
paddingRight={2}
|
||||
backgroundColor={
|
||||
selected() === "action" ? themeV2.background.action.primary.focused : RGBA.fromInts(0, 0, 0, 0)
|
||||
selected() === "action" ? theme.background.action.primary.focused : RGBA.fromInts(0, 0, 0, 0)
|
||||
}
|
||||
onMouseOver={() => setSelected("action")}
|
||||
onMouseUp={() => runAction(props, dialog)}
|
||||
>
|
||||
<text
|
||||
fg={selected() === "action" ? themeV2.text.action.primary.focused : themeV2.text.default}
|
||||
fg={selected() === "action" ? theme.text.action.primary.focused : theme.text.default}
|
||||
bg={selected() === "action" ? undefined : textBg()}
|
||||
attributes={selected() === "action" ? TextAttributes.BOLD : undefined}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { TextAttributes } from "@opentui/core"
|
||||
import { Keymap } from "../context/keymap"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { useDialog } from "../ui/dialog"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { For } from "solid-js"
|
||||
|
|
@ -13,7 +13,7 @@ export function DialogSessionDeleteFailed(props: {
|
|||
onDone?: () => void
|
||||
}) {
|
||||
const dialog = useDialog()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const [store, setStore] = createStore({
|
||||
active: "delete" as "delete" | "restore",
|
||||
})
|
||||
|
|
@ -64,17 +64,17 @@ export function DialogSessionDeleteFailed(props: {
|
|||
return (
|
||||
<box paddingLeft={2} paddingRight={2} gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text.default}>
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text.default}>
|
||||
Failed to Delete Session
|
||||
</text>
|
||||
<text fg={themeV2.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={theme.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
<text fg={themeV2.text.subdued} wrapMode="word">
|
||||
<text fg={theme.text.subdued} wrapMode="word">
|
||||
{`The session "${props.session}" could not be deleted because the workspace "${props.workspace}" is not available.`}
|
||||
</text>
|
||||
<text fg={themeV2.text.subdued} wrapMode="word">
|
||||
<text fg={theme.text.subdued} wrapMode="word">
|
||||
Choose how you want to recover this broken workspace session.
|
||||
</text>
|
||||
<box flexDirection="column" paddingBottom={1} gap={1}>
|
||||
|
|
@ -86,7 +86,7 @@ export function DialogSessionDeleteFailed(props: {
|
|||
paddingRight={1}
|
||||
paddingTop={1}
|
||||
paddingBottom={1}
|
||||
backgroundColor={item.id === store.active ? themeV2.background.action.primary.focused : undefined}
|
||||
backgroundColor={item.id === store.active ? theme.background.action.primary.focused : undefined}
|
||||
onMouseUp={() => {
|
||||
setStore("active", item.id)
|
||||
void confirm()
|
||||
|
|
@ -94,12 +94,12 @@ export function DialogSessionDeleteFailed(props: {
|
|||
>
|
||||
<text
|
||||
attributes={TextAttributes.BOLD}
|
||||
fg={item.id === store.active ? themeV2.text.action.primary.focused : themeV2.text.default}
|
||||
fg={item.id === store.active ? theme.text.action.primary.focused : theme.text.default}
|
||||
>
|
||||
{item.title}
|
||||
</text>
|
||||
<text
|
||||
fg={item.id === store.active ? themeV2.text.action.primary.focused : themeV2.text.subdued}
|
||||
fg={item.id === store.active ? theme.text.action.primary.focused : theme.text.subdued}
|
||||
wrapMode="word"
|
||||
>
|
||||
{item.description}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { useRoute } from "../context/route"
|
|||
import { useData } from "../context/data"
|
||||
import { Keymap } from "../context/keymap"
|
||||
import { Locale } from "../util/locale"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { useClient } from "../context/client"
|
||||
import { useLocal } from "../context/local"
|
||||
import { createDebouncedSignal } from "../util/signal"
|
||||
|
|
@ -20,7 +20,9 @@ export function DialogSessionList() {
|
|||
const dialog = useDialog()
|
||||
const route = useRoute()
|
||||
const data = useData()
|
||||
const { themeV2, mode } = useTheme().contextual("elevated")
|
||||
const themes = useThemes()
|
||||
const theme = themes.contextual("elevated")
|
||||
const mode = themes.mode
|
||||
const client = useClient()
|
||||
const local = useLocal()
|
||||
const toast = useToast()
|
||||
|
|
@ -109,13 +111,13 @@ export function DialogSessionList() {
|
|||
value: session.id,
|
||||
category,
|
||||
footer,
|
||||
bg: deleting ? themeV2.background.action.destructive.focused : undefined,
|
||||
fg: deleting ? themeV2.text.action.destructive.focused : undefined,
|
||||
bg: deleting ? theme.background.action.destructive.focused : undefined,
|
||||
fg: deleting ? theme.text.action.destructive.focused : undefined,
|
||||
gutter: data.session.family(session.id).some((id) => data.session.status(id) === "running")
|
||||
? () => <Spinner />
|
||||
: slot === undefined
|
||||
? undefined
|
||||
: () => <text fg={themeV2.hue.accent[mode() === "light" ? 800 : 200]}>{slot}</text>,
|
||||
: () => <text fg={theme.hue.accent[mode() === "light" ? 800 : 200]}>{slot}</text>,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -143,12 +145,12 @@ export function DialogSessionList() {
|
|||
}}
|
||||
emptyView={
|
||||
<box paddingLeft={4} paddingRight={4} paddingTop={1}>
|
||||
<text fg={themeV2.text.subdued}>No sessions available</text>
|
||||
<text fg={theme.text.subdued}>No sessions available</text>
|
||||
</box>
|
||||
}
|
||||
noMatchView={
|
||||
<box paddingLeft={4} paddingRight={4} paddingTop={1}>
|
||||
<text fg={searchState().error ? themeV2.text.feedback.error.default : themeV2.text.subdued}>
|
||||
<text fg={searchState().error ? theme.text.feedback.error.default : theme.text.subdued}>
|
||||
{searchState().message}
|
||||
</text>
|
||||
</box>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export type DialogSkillProps = {
|
|||
export function DialogSkill(props: DialogSkillProps) {
|
||||
const dialog = useDialog()
|
||||
const data = useData()
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
dialog.setSize("large")
|
||||
|
||||
const [loadError, setLoadError] = createSignal<unknown>()
|
||||
|
|
@ -63,29 +63,29 @@ export function DialogSkill(props: DialogSkillProps) {
|
|||
<Switch
|
||||
fallback={
|
||||
<box paddingLeft={4} paddingRight={4} paddingTop={1}>
|
||||
<text fg={themeV2.text.subdued}>No skills available</text>
|
||||
<text fg={theme.text.subdued}>No skills available</text>
|
||||
</box>
|
||||
}
|
||||
>
|
||||
<Match when={showError()}>
|
||||
<box paddingLeft={4} paddingRight={4} paddingTop={1}>
|
||||
<text fg={themeV2.text.feedback.error.default} attributes={TextAttributes.BOLD}>
|
||||
<text fg={theme.text.feedback.error.default} attributes={TextAttributes.BOLD}>
|
||||
Could not load skills
|
||||
</text>
|
||||
<text fg={themeV2.text.subdued}>{errorMessage(loadError())}</text>
|
||||
<text fg={themeV2.text.subdued}>Close and reopen Skills to try again.</text>
|
||||
<text fg={theme.text.subdued}>{errorMessage(loadError())}</text>
|
||||
<text fg={theme.text.subdued}>Close and reopen Skills to try again.</text>
|
||||
</box>
|
||||
</Match>
|
||||
<Match when={skills.loading}>
|
||||
<box paddingLeft={4} paddingRight={4} paddingTop={1}>
|
||||
<text fg={themeV2.text.subdued}>Loading skills…</text>
|
||||
<text fg={theme.text.subdued}>Loading skills…</text>
|
||||
</box>
|
||||
</Match>
|
||||
</Switch>
|
||||
}
|
||||
noMatchView={
|
||||
<box paddingLeft={4} paddingRight={4} paddingTop={1}>
|
||||
<text fg={themeV2.text.subdued}>No skills found</text>
|
||||
<text fg={theme.text.subdued}>No skills found</text>
|
||||
</box>
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { DialogSelect } from "../ui/dialog-select"
|
|||
import { createMemo, createSignal } from "solid-js"
|
||||
import { Locale } from "../util/locale"
|
||||
import { Keymap } from "../context/keymap"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { usePromptStash, type StashEntry } from "./prompt/stash"
|
||||
|
||||
function getRelativeTime(timestamp: number): string {
|
||||
|
|
@ -29,7 +29,7 @@ function getStashPreview(input: string, maxLength: number = 50): string {
|
|||
export function DialogStash(props: { onSelect: (entry: StashEntry) => void }) {
|
||||
const dialog = useDialog()
|
||||
const stash = usePromptStash()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const shortcuts = Keymap.useShortcuts()
|
||||
|
||||
const [toDelete, setToDelete] = createSignal<number>()
|
||||
|
|
@ -45,8 +45,8 @@ export function DialogStash(props: { onSelect: (entry: StashEntry) => void }) {
|
|||
title: isDeleting
|
||||
? `Press ${shortcuts.get("stash.delete")} again to confirm`
|
||||
: getStashPreview(entry.prompt.text),
|
||||
bg: isDeleting ? themeV2.background.action.destructive.focused : undefined,
|
||||
fg: isDeleting ? themeV2.text.action.destructive.focused : undefined,
|
||||
bg: isDeleting ? theme.background.action.destructive.focused : undefined,
|
||||
fg: isDeleting ? theme.text.action.destructive.focused : undefined,
|
||||
value: index,
|
||||
description: getRelativeTime(entry.timestamp),
|
||||
footer: lineCount > 1 ? `~${lineCount} lines` : undefined,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { TextAttributes } from "@opentui/core"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { useDialog } from "../ui/dialog"
|
||||
import { useData } from "../context/data"
|
||||
import { For, Match, Switch, Show, createMemo } from "solid-js"
|
||||
|
|
@ -8,30 +8,30 @@ export type DialogStatusProps = {}
|
|||
|
||||
export function DialogStatus() {
|
||||
const data = useData()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const dialog = useDialog()
|
||||
|
||||
const mcp = createMemo(() => data.location.mcp.server.list() ?? [])
|
||||
const color = (status: string) => {
|
||||
if (status === "connected") return themeV2.text.feedback.success.default
|
||||
if (status === "failed") return themeV2.text.feedback.error.default
|
||||
if (status === "needs_auth") return themeV2.text.feedback.warning.default
|
||||
if (status === "needs_client_registration") return themeV2.text.feedback.error.default
|
||||
return themeV2.text.subdued
|
||||
if (status === "connected") return theme.text.feedback.success.default
|
||||
if (status === "failed") return theme.text.feedback.error.default
|
||||
if (status === "needs_auth") return theme.text.feedback.warning.default
|
||||
if (status === "needs_client_registration") return theme.text.feedback.error.default
|
||||
return theme.text.subdued
|
||||
}
|
||||
return (
|
||||
<box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={themeV2.text.default} attributes={TextAttributes.BOLD}>
|
||||
<text fg={theme.text.default} attributes={TextAttributes.BOLD}>
|
||||
Status
|
||||
</text>
|
||||
<text fg={themeV2.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={theme.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
<Show when={mcp().length > 0} fallback={<text fg={themeV2.text.default}>No MCP servers</text>}>
|
||||
<Show when={mcp().length > 0} fallback={<text fg={theme.text.default}>No MCP servers</text>}>
|
||||
<box>
|
||||
<text fg={themeV2.text.default}>
|
||||
<text fg={theme.text.default}>
|
||||
{mcp().length} MCP server{mcp().length === 1 ? "" : "s"}
|
||||
</text>
|
||||
<For each={mcp()}>
|
||||
|
|
@ -40,9 +40,9 @@ export function DialogStatus() {
|
|||
<text flexShrink={0} style={{ fg: color(item.status.status) }}>
|
||||
•
|
||||
</text>
|
||||
<text fg={themeV2.text.default} wrapMode="word">
|
||||
<text fg={theme.text.default} wrapMode="word">
|
||||
<b>{item.name}</b>{" "}
|
||||
<span style={{ fg: themeV2.text.subdued }}>
|
||||
<span style={{ fg: theme.text.subdued }}>
|
||||
<Switch fallback={item.status.status}>
|
||||
<Match when={item.status.status === "connected"}>Connected</Match>
|
||||
<Match when={item.status.status === "failed" && item.status}>{(val) => val().error}</Match>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { DialogSelect, type DialogSelectRef } from "../ui/dialog-select"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { useDialog } from "../ui/dialog"
|
||||
import { onCleanup } from "solid-js"
|
||||
|
||||
export function DialogThemeList() {
|
||||
const theme = useTheme()
|
||||
const options = Object.keys(theme.all())
|
||||
const themes = useThemes()
|
||||
const options = Object.keys(themes.all())
|
||||
.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }))
|
||||
.map((value) => ({
|
||||
title: value,
|
||||
|
|
@ -14,10 +14,10 @@ export function DialogThemeList() {
|
|||
const dialog = useDialog()
|
||||
let confirmed = false
|
||||
let ref: DialogSelectRef<string>
|
||||
const initial = theme.selected
|
||||
const initial = themes.selected
|
||||
|
||||
onCleanup(() => {
|
||||
if (!confirmed) theme.set(initial)
|
||||
if (!confirmed) themes.set(initial)
|
||||
})
|
||||
|
||||
return (
|
||||
|
|
@ -26,10 +26,10 @@ export function DialogThemeList() {
|
|||
options={options}
|
||||
current={initial}
|
||||
onMove={(opt) => {
|
||||
theme.set(opt.value)
|
||||
themes.set(opt.value)
|
||||
}}
|
||||
onSelect={(opt) => {
|
||||
theme.set(opt.value)
|
||||
themes.set(opt.value)
|
||||
confirmed = true
|
||||
dialog.clear()
|
||||
}}
|
||||
|
|
@ -38,12 +38,12 @@ export function DialogThemeList() {
|
|||
}}
|
||||
onFilter={(query) => {
|
||||
if (query.length === 0) {
|
||||
theme.set(initial)
|
||||
themes.set(initial)
|
||||
return
|
||||
}
|
||||
|
||||
const first = ref.filtered[0]
|
||||
if (first) theme.set(first.value)
|
||||
if (first) themes.set(first.value)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import type { VcsFileStatus } from "@opencode-ai/client"
|
|||
import { createMemo, For } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { FilePath } from "../ui/file-path"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { useConfig } from "../config"
|
||||
import { useDialog, type DialogContext } from "../ui/dialog"
|
||||
import { getScrollAcceleration } from "../util/scroll"
|
||||
|
|
@ -31,8 +31,8 @@ export function DialogWorkspaceFileChanges(props: {
|
|||
message?: string
|
||||
}) {
|
||||
const dialog = useDialog()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const { themeV2: overlayTheme } = useTheme().contextual("overlay")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const overlayTheme = useThemes().contextual("overlay")
|
||||
const config = useConfig().data
|
||||
const dimensions = useTerminalDimensions()
|
||||
const scrollAcceleration = createMemo(() => getScrollAcceleration(config))
|
||||
|
|
@ -72,15 +72,15 @@ export function DialogWorkspaceFileChanges(props: {
|
|||
return (
|
||||
<box gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between" paddingLeft={2} paddingRight={2}>
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text.default}>
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text.default}>
|
||||
{props.title ?? "File Changes Found"}
|
||||
</text>
|
||||
<text fg={themeV2.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={theme.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
<box paddingLeft={2} paddingRight={2}>
|
||||
<text fg={themeV2.text.subdued} wrapMode="word">
|
||||
<text fg={theme.text.subdued} wrapMode="word">
|
||||
{props.message ?? "Do you want to move these changes with the session?"}
|
||||
</text>
|
||||
</box>
|
||||
|
|
@ -118,16 +118,14 @@ export function DialogWorkspaceFileChanges(props: {
|
|||
<box
|
||||
paddingLeft={2}
|
||||
paddingRight={2}
|
||||
backgroundColor={item === store.active ? themeV2.background.action.primary.focused : undefined}
|
||||
backgroundColor={item === store.active ? theme.background.action.primary.focused : undefined}
|
||||
onMouseUp={() => {
|
||||
setStore("active", item)
|
||||
props.onSelect(item)
|
||||
dialog.clear()
|
||||
}}
|
||||
>
|
||||
<text fg={item === store.active ? themeV2.text.action.primary.focused : themeV2.text.subdued}>
|
||||
{item}
|
||||
</text>
|
||||
<text fg={item === store.active ? theme.text.action.primary.focused : theme.text.subdued}>{item}</text>
|
||||
</box>
|
||||
)}
|
||||
</For>
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ import { tint } from "../theme/color"
|
|||
import { logo } from "../logo"
|
||||
|
||||
export function Logo() {
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
|
||||
const renderLine = (line: string, fg: RGBA, bold: boolean): JSX.Element[] => {
|
||||
const shadow = tint(themeV2.background.default, fg, 0.25)
|
||||
const shadow = tint(theme.background.default, fg, 0.25)
|
||||
const attrs = bold ? TextAttributes.BOLD : undefined
|
||||
return Array.from(line).map((char) => {
|
||||
if (char === "_") {
|
||||
|
|
@ -52,8 +52,8 @@ export function Logo() {
|
|||
<For each={logo.left}>
|
||||
{(line, index) => (
|
||||
<box flexDirection="row" gap={1}>
|
||||
<box flexDirection="row">{renderLine(line, themeV2.text.subdued, false)}</box>
|
||||
<box flexDirection="row">{renderLine(logo.right[index()], themeV2.text.default, true)}</box>
|
||||
<box flexDirection="row">{renderLine(line, theme.text.subdued, false)}</box>
|
||||
<box flexDirection="row">{renderLine(logo.right[index()], theme.text.default, true)}</box>
|
||||
</box>
|
||||
)}
|
||||
</For>
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
import { useTheme } from "../context/theme"
|
||||
|
||||
export function PluginRouteMissing(props: { id: string; name: string; onHome: () => void }) {
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
|
||||
return (
|
||||
<box width="100%" height="100%" alignItems="center" justifyContent="center" flexDirection="column" gap={1}>
|
||||
<text fg={themeV2.text.feedback.warning.default}>
|
||||
<text fg={theme.text.feedback.warning.default}>
|
||||
Unknown plugin route: {props.id}/{props.name}
|
||||
</text>
|
||||
<box
|
||||
onMouseUp={props.onHome}
|
||||
backgroundColor={themeV2.background.action.primary.hovered}
|
||||
backgroundColor={theme.background.action.primary.hovered}
|
||||
paddingLeft={1}
|
||||
paddingRight={1}
|
||||
>
|
||||
<text fg={themeV2.text.action.primary.hovered}>go home</text>
|
||||
<text fg={theme.text.action.primary.hovered}>go home</text>
|
||||
</box>
|
||||
</box>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { getScrollAcceleration } from "../../util/scroll"
|
|||
import { useTuiPaths } from "../../context/runtime"
|
||||
import { useConfig } from "../../config"
|
||||
import { useLocation } from "../../context/location"
|
||||
import { useTheme } from "../../context/theme"
|
||||
import { useThemes } from "../../context/theme"
|
||||
import { SplitBorder } from "../../ui/border"
|
||||
import { useTerminalDimensions } from "@opentui/solid"
|
||||
import { Locale } from "../../util/locale"
|
||||
|
|
@ -57,7 +57,7 @@ export function Autocomplete(props: {
|
|||
const data = useData()
|
||||
const keymap = Keymap.use()
|
||||
const keymapCommands = Keymap.useCommands()
|
||||
const { themeV2 } = useTheme().contextual("overlay")
|
||||
const theme = useThemes().contextual("overlay")
|
||||
const dimensions = useTerminalDimensions()
|
||||
const frecency = useFrecency()
|
||||
const config = useConfig().data
|
||||
|
|
@ -698,11 +698,11 @@ export function Autocomplete(props: {
|
|||
width={position().width}
|
||||
zIndex={100}
|
||||
{...SplitBorder}
|
||||
borderColor={themeV2.border.default}
|
||||
borderColor={theme.border.default}
|
||||
>
|
||||
<scrollbox
|
||||
ref={(r: ScrollBoxRenderable) => (scroll = r)}
|
||||
backgroundColor={themeV2.background.default}
|
||||
backgroundColor={theme.background.default}
|
||||
height={height()}
|
||||
scrollbarOptions={{ visible: false }}
|
||||
scrollAcceleration={scrollAcceleration()}
|
||||
|
|
@ -711,9 +711,7 @@ export function Autocomplete(props: {
|
|||
each={options()}
|
||||
fallback={
|
||||
<box paddingLeft={1} paddingRight={1}>
|
||||
<text fg={emptyError() ? themeV2.text.feedback.error.default : themeV2.text.subdued}>
|
||||
{emptyMessage()}
|
||||
</text>
|
||||
<text fg={emptyError() ? theme.text.feedback.error.default : theme.text.subdued}>{emptyMessage()}</text>
|
||||
</box>
|
||||
}
|
||||
>
|
||||
|
|
@ -721,7 +719,7 @@ export function Autocomplete(props: {
|
|||
<box
|
||||
paddingLeft={1}
|
||||
paddingRight={1}
|
||||
backgroundColor={index === store.selected ? themeV2.background.action.primary.focused : undefined}
|
||||
backgroundColor={index === store.selected ? theme.background.action.primary.focused : undefined}
|
||||
flexDirection="row"
|
||||
onMouseMove={() => {
|
||||
setStore("input", "mouse")
|
||||
|
|
@ -737,14 +735,14 @@ export function Autocomplete(props: {
|
|||
onMouseUp={() => select()}
|
||||
>
|
||||
<text
|
||||
fg={index === store.selected ? themeV2.text.action.primary.focused : themeV2.text.default}
|
||||
fg={index === store.selected ? theme.text.action.primary.focused : theme.text.default}
|
||||
flexShrink={0}
|
||||
>
|
||||
{option().display}
|
||||
</text>
|
||||
<Show when={option().description}>
|
||||
<text
|
||||
fg={index === store.selected ? themeV2.text.action.primary.focused : themeV2.text.subdued}
|
||||
fg={index === store.selected ? theme.text.action.primary.focused : theme.text.subdued}
|
||||
wrapMode="none"
|
||||
>
|
||||
{" " + option().description?.trimStart()}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { registerOpencodeSpinner } from "../register-spinner"
|
|||
import path from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
import { useLocal } from "../../context/local"
|
||||
import { useTheme } from "../../context/theme"
|
||||
import { useTheme, useThemes } from "../../context/theme"
|
||||
import { tint } from "../../theme/color"
|
||||
import { EmptyBorder, SplitBorder } from "../../ui/border"
|
||||
import { useTuiPaths, useTuiTerminalEnvironment } from "../../context/runtime"
|
||||
|
|
@ -189,7 +189,8 @@ export function Prompt(props: PromptProps) {
|
|||
const renderer = useRenderer()
|
||||
const exit = useExit()
|
||||
const dimensions = useTerminalDimensions()
|
||||
const { themeV2, syntax } = useTheme()
|
||||
const theme = useTheme()
|
||||
const { currentSyntax: syntax } = useThemes()
|
||||
const animationsEnabled = createMemo(() => config.animations ?? true)
|
||||
const list = createMemo(() => props.placeholders?.normal ?? [])
|
||||
const shell = createMemo(() => props.placeholders?.shell ?? [])
|
||||
|
|
@ -296,8 +297,8 @@ export function Prompt(props: PromptProps) {
|
|||
|
||||
createEffect(() => {
|
||||
if (!input || input.isDestroyed) return
|
||||
if (props.disabled) input.cursorColor = themeV2.background.surface.offset
|
||||
if (!props.disabled) input.cursorColor = themeV2.text.default
|
||||
if (props.disabled) input.cursorColor = theme.background.surface.offset
|
||||
if (!props.disabled) input.cursorColor = theme.text.default
|
||||
})
|
||||
|
||||
const usage = createMemo(() => {
|
||||
|
|
@ -1299,10 +1300,10 @@ export function Prompt(props: PromptProps) {
|
|||
}
|
||||
|
||||
const highlight = createMemo(() => {
|
||||
if (leader()) return themeV2.border.default
|
||||
if (store.mode === "shell") return themeV2.text.action.primary.selected
|
||||
if (leader()) return theme.border.default
|
||||
if (store.mode === "shell") return theme.text.action.primary.selected
|
||||
const agent = local.agent.current()
|
||||
if (!agent) return themeV2.border.default
|
||||
if (!agent) return theme.border.default
|
||||
return local.agent.color(agent.id)
|
||||
})
|
||||
const agentLabel = createMemo(() => {
|
||||
|
|
@ -1324,7 +1325,7 @@ export function Prompt(props: PromptProps) {
|
|||
() => !!local.agent.current() && store.mode === "normal" && showVariant(),
|
||||
animationsEnabled,
|
||||
)
|
||||
const borderHighlight = createMemo(() => tint(themeV2.border.default, highlight(), agentMetaAlpha()))
|
||||
const borderHighlight = createMemo(() => tint(theme.border.default, highlight(), agentMetaAlpha()))
|
||||
|
||||
const placeholderText = createMemo(() => {
|
||||
if (props.showPlaceholder === false) return undefined
|
||||
|
|
@ -1344,7 +1345,7 @@ export function Prompt(props: PromptProps) {
|
|||
|
||||
const spinnerDef = createMemo(() => {
|
||||
const agent = status() === "running" ? local.agent.current() : local.agent.current()
|
||||
const color = agent ? local.agent.color(agent.id) : themeV2.border.default
|
||||
const color = agent ? local.agent.color(agent.id) : theme.border.default
|
||||
return {
|
||||
frames: createFrames({
|
||||
color,
|
||||
|
|
@ -1364,7 +1365,7 @@ export function Prompt(props: PromptProps) {
|
|||
})
|
||||
const maxHeight = createMemo(() => Math.max(6, Math.floor(dimensions().height / 3)))
|
||||
|
||||
const promptBg = createMemo(() => themeV2.raise(themeV2.background.surface.offset))
|
||||
const promptBg = createMemo(() => theme.raise(theme.background.surface.offset))
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -1390,9 +1391,9 @@ export function Prompt(props: PromptProps) {
|
|||
<textarea
|
||||
width="100%"
|
||||
placeholder={placeholderText()}
|
||||
placeholderColor={themeV2.text.subdued}
|
||||
textColor={leader() ? themeV2.text.subdued : themeV2.text.default}
|
||||
focusedTextColor={leader() ? themeV2.text.subdued : themeV2.text.default}
|
||||
placeholderColor={theme.text.subdued}
|
||||
textColor={leader() ? theme.text.subdued : theme.text.default}
|
||||
focusedTextColor={leader() ? theme.text.subdued : theme.text.default}
|
||||
minHeight={1}
|
||||
maxHeight={maxHeight()}
|
||||
onContentChange={() => {
|
||||
|
|
@ -1452,7 +1453,7 @@ export function Prompt(props: PromptProps) {
|
|||
setTimeout(() => {
|
||||
// setTimeout is a workaround and needs to be addressed properly
|
||||
if (!input || input.isDestroyed) return
|
||||
input.cursorColor = themeV2.text.default
|
||||
input.cursorColor = theme.text.default
|
||||
}, 0)
|
||||
}}
|
||||
onMouseDown={(r: MouseEvent) => {
|
||||
|
|
@ -1460,7 +1461,7 @@ export function Prompt(props: PromptProps) {
|
|||
r.target?.focus()
|
||||
}}
|
||||
focusedBackgroundColor="transparent"
|
||||
cursorColor={props.disabled ? themeV2.background.surface.offset : themeV2.text.default}
|
||||
cursorColor={props.disabled ? theme.background.surface.offset : theme.text.default}
|
||||
syntaxStyle={syntax()}
|
||||
/>
|
||||
<box flexDirection="row" flexShrink={0} paddingTop={1} gap={1} justifyContent="space-between">
|
||||
|
|
@ -1470,24 +1471,24 @@ export function Prompt(props: PromptProps) {
|
|||
<>
|
||||
<text fg={fadeColor(highlight(), agentMetaAlpha())}>{label()}</text>
|
||||
<Show when={store.mode === "normal" && local.permission.mode === "auto"}>
|
||||
<text fg={fadeColor(themeV2.text.subdued, agentMetaAlpha())}>auto</text>
|
||||
<text fg={fadeColor(theme.text.subdued, agentMetaAlpha())}>auto</text>
|
||||
</Show>
|
||||
<Show when={store.mode === "normal"}>
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text fg={fadeColor(themeV2.text.subdued, modelMetaAlpha())}>·</text>
|
||||
<text fg={fadeColor(theme.text.subdued, modelMetaAlpha())}>·</text>
|
||||
<text
|
||||
flexShrink={0}
|
||||
fg={fadeColor(leader() ? themeV2.text.subdued : themeV2.text.default, modelMetaAlpha())}
|
||||
fg={fadeColor(leader() ? theme.text.subdued : theme.text.default, modelMetaAlpha())}
|
||||
>
|
||||
{local.model.parsed().model}
|
||||
</text>
|
||||
<text fg={fadeColor(themeV2.text.subdued, modelMetaAlpha())}>{currentProviderLabel()}</text>
|
||||
<text fg={fadeColor(theme.text.subdued, modelMetaAlpha())}>{currentProviderLabel()}</text>
|
||||
<Show when={showVariant()}>
|
||||
<text fg={fadeColor(themeV2.text.subdued, variantMetaAlpha())}>·</text>
|
||||
<text fg={fadeColor(theme.text.subdued, variantMetaAlpha())}>·</text>
|
||||
<text>
|
||||
<span
|
||||
style={{
|
||||
fg: fadeColor(themeV2.text.feedback.warning.default, variantMetaAlpha()),
|
||||
fg: fadeColor(theme.text.feedback.warning.default, variantMetaAlpha()),
|
||||
bold: true,
|
||||
}}
|
||||
>
|
||||
|
|
@ -1541,12 +1542,12 @@ export function Prompt(props: PromptProps) {
|
|||
<Match when={status() === "running"}>
|
||||
<box flexDirection="row" gap={1} flexGrow={1} justifyContent="flex-start">
|
||||
<box marginLeft={1}>
|
||||
<Show when={config.animations ?? true} fallback={<text fg={themeV2.text.subdued}>[⋯]</text>}>
|
||||
<Show when={config.animations ?? true} fallback={<text fg={theme.text.subdued}>[⋯]</text>}>
|
||||
<spinner color={spinnerDef().color} frames={spinnerDef().frames} interval={40} />
|
||||
</Show>
|
||||
</box>
|
||||
<text
|
||||
fg={store.interrupt > 0 ? themeV2.background.action.primary.default : themeV2.text.default}
|
||||
fg={store.interrupt > 0 ? theme.background.action.primary.default : theme.text.default}
|
||||
wrapMode="none"
|
||||
truncate
|
||||
flexShrink={1}
|
||||
|
|
@ -1554,7 +1555,7 @@ export function Prompt(props: PromptProps) {
|
|||
esc{" "}
|
||||
<span
|
||||
style={{
|
||||
fg: store.interrupt > 0 ? themeV2.background.action.primary.default : themeV2.text.subdued,
|
||||
fg: store.interrupt > 0 ? theme.background.action.primary.default : theme.text.subdued,
|
||||
}}
|
||||
>
|
||||
{store.interrupt > 0 ? "again to interrupt" : "interrupt"}
|
||||
|
|
@ -1565,16 +1566,16 @@ export function Prompt(props: PromptProps) {
|
|||
<Match when={move.progress()}>
|
||||
{(progress) => (
|
||||
<box paddingLeft={3} height={1} minHeight={0} flexShrink={1}>
|
||||
<Spinner color={themeV2.hue.accent[500]}>
|
||||
<Spinner color={theme.hue.accent[500]}>
|
||||
{progress()}
|
||||
<span style={{ fg: themeV2.text.subdued }}>{".".repeat(move.creatingDots())}</span>
|
||||
<span style={{ fg: theme.text.subdued }}>{".".repeat(move.creatingDots())}</span>
|
||||
</Spinner>
|
||||
</box>
|
||||
)}
|
||||
</Match>
|
||||
<Match when={move.pendingNew()}>
|
||||
<box paddingLeft={3} height={1} minHeight={0} flexShrink={1}>
|
||||
<text fg={themeV2.hue.accent[500]} wrapMode="none" truncate>
|
||||
<text fg={theme.hue.accent[500]} wrapMode="none" truncate>
|
||||
(new working copy)
|
||||
</text>
|
||||
</box>
|
||||
|
|
@ -1582,7 +1583,7 @@ export function Prompt(props: PromptProps) {
|
|||
<Match when={true}>
|
||||
<Show when={!props.hint && locationLabel()} fallback={props.hint ?? <text />}>
|
||||
{(location) => (
|
||||
<text fg={themeV2.text.subdued} wrapMode="none" truncate flexGrow={1} flexShrink={1}>
|
||||
<text fg={theme.text.subdued} wrapMode="none" truncate flexGrow={1} flexShrink={1}>
|
||||
{location()}
|
||||
</text>
|
||||
)}
|
||||
|
|
@ -1596,7 +1597,7 @@ export function Prompt(props: PromptProps) {
|
|||
wrapMode="none"
|
||||
truncate
|
||||
flexShrink={1}
|
||||
fg={editorContextLabelState() === "pending" ? themeV2.hue.accent[500] : themeV2.text.subdued}
|
||||
fg={editorContextLabelState() === "pending" ? theme.hue.accent[500] : theme.text.subdued}
|
||||
>
|
||||
{file()}
|
||||
</text>
|
||||
|
|
@ -1606,40 +1607,40 @@ export function Prompt(props: PromptProps) {
|
|||
<Match when={store.mode === "normal"}>
|
||||
<Switch>
|
||||
<Match when={liveWorkStatusVisible() || statusItems().length > 0}>
|
||||
<text fg={themeV2.text.subdued} wrapMode="none" truncate flexShrink={1}>
|
||||
<text fg={theme.text.subdued} wrapMode="none" truncate flexShrink={1}>
|
||||
<Show when={liveWorkStatusVisible() && liveWorkShortcut()}>
|
||||
{(shortcut) => <span style={{ fg: themeV2.text.default }}>{shortcut()} </span>}
|
||||
{(shortcut) => <span style={{ fg: theme.text.default }}>{shortcut()} </span>}
|
||||
</Show>
|
||||
<Show when={subagentStatusLabel()}>
|
||||
{(label) => <span style={{ fg: themeV2.text.subdued }}>{label()}</span>}
|
||||
{(label) => <span style={{ fg: theme.text.subdued }}>{label()}</span>}
|
||||
</Show>
|
||||
<Show when={subagentStatusLabel() && shellStatusLabel()}>
|
||||
<span style={{ fg: themeV2.text.subdued }}> · </span>
|
||||
<span style={{ fg: theme.text.subdued }}> · </span>
|
||||
</Show>
|
||||
<Show when={shellStatusLabel()}>
|
||||
{(label) => <span style={{ fg: themeV2.text.subdued }}>{label()}</span>}
|
||||
{(label) => <span style={{ fg: theme.text.subdued }}>{label()}</span>}
|
||||
</Show>
|
||||
<Show when={liveWorkStatusVisible() && statusItems().length > 0}>
|
||||
<span style={{ fg: themeV2.text.subdued }}> · </span>
|
||||
<span style={{ fg: theme.text.subdued }}> · </span>
|
||||
</Show>
|
||||
<Show when={statusItems().length > 0}>
|
||||
<span style={{ fg: themeV2.text.subdued }}>{statusItems().join(" · ")}</span>
|
||||
<span style={{ fg: theme.text.subdued }}>{statusItems().join(" · ")}</span>
|
||||
</Show>
|
||||
</text>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<text fg={themeV2.text.default} flexShrink={0}>
|
||||
{agentShortcut()} <span style={{ fg: themeV2.text.subdued }}>agents</span>
|
||||
<text fg={theme.text.default} flexShrink={0}>
|
||||
{agentShortcut()} <span style={{ fg: theme.text.subdued }}>agents</span>
|
||||
</text>
|
||||
</Match>
|
||||
</Switch>
|
||||
<text fg={themeV2.text.default} flexShrink={0}>
|
||||
{paletteShortcut()} <span style={{ fg: themeV2.text.subdued }}>commands</span>
|
||||
<text fg={theme.text.default} flexShrink={0}>
|
||||
{paletteShortcut()} <span style={{ fg: theme.text.subdued }}>commands</span>
|
||||
</text>
|
||||
</Match>
|
||||
<Match when={store.mode === "shell"}>
|
||||
<text fg={themeV2.text.default} flexShrink={0}>
|
||||
esc <span style={{ fg: themeV2.text.subdued }}>exit shell mode</span>
|
||||
<text fg={theme.text.default} flexShrink={0}>
|
||||
esc <span style={{ fg: theme.text.subdued }}>exit shell mode</span>
|
||||
</text>
|
||||
</Match>
|
||||
</Switch>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { RGBA } from "@opentui/core"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { Spinner } from "./spinner"
|
||||
|
||||
export function Reconnecting() {
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
|
||||
return (
|
||||
<box
|
||||
|
|
@ -21,15 +21,15 @@ export function Reconnecting() {
|
|||
width={48}
|
||||
maxWidth="90%"
|
||||
flexDirection="column"
|
||||
backgroundColor={themeV2.background.default}
|
||||
backgroundColor={theme.background.default}
|
||||
paddingTop={1}
|
||||
paddingBottom={1}
|
||||
paddingLeft={2}
|
||||
paddingRight={2}
|
||||
gap={1}
|
||||
>
|
||||
<Spinner color={themeV2.text.default}>Restarting service...</Spinner>
|
||||
<text fg={themeV2.text.subdued}>Your session will resume automatically.</text>
|
||||
<Spinner color={theme.text.default}>Restarting service...</Spinner>
|
||||
<text fg={theme.text.subdued}>Your session will resume automatically.</text>
|
||||
</box>
|
||||
</box>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ export { SPINNER_FRAMES } from "./spinner-frames"
|
|||
registerOpencodeSpinner()
|
||||
|
||||
export function Spinner(props: { children?: JSX.Element; color?: RGBA }) {
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
const config = useConfig().data
|
||||
const color = () => props.color ?? themeV2.text.subdued
|
||||
const color = () => props.color ?? theme.text.subdued
|
||||
return (
|
||||
<Show
|
||||
when={config.animations ?? true}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { createEffect, createMemo, createSignal, onCleanup, Show } from "solid-js"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { Spinner } from "./spinner"
|
||||
|
||||
export function StartupLoading(props: { ready: () => boolean }) {
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const [show, setShow] = createSignal(false)
|
||||
const text = createMemo(() => (props.ready() ? "Finishing startup..." : "Loading plugins..."))
|
||||
let wait: NodeJS.Timeout | undefined
|
||||
|
|
@ -54,8 +54,8 @@ export function StartupLoading(props: { ready: () => boolean }) {
|
|||
return (
|
||||
<Show when={show()}>
|
||||
<box position="absolute" zIndex={5000} left={0} right={0} bottom={1} justifyContent="center" alignItems="center">
|
||||
<box backgroundColor={themeV2.background.default} paddingLeft={1} paddingRight={1}>
|
||||
<Spinner color={themeV2.text.subdued}>{text()}</Spinner>
|
||||
<box backgroundColor={theme.background.default} paddingLeft={1} paddingRight={1}>
|
||||
<Spinner color={theme.text.subdued}>{text()}</Spinner>
|
||||
</box>
|
||||
</box>
|
||||
</Show>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import { onCleanup } from "solid-js"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { useToast } from "../ui/toast"
|
||||
|
||||
export function ThemeErrorToast() {
|
||||
const theme = useTheme()
|
||||
const themes = useThemes()
|
||||
const toast = useToast()
|
||||
|
||||
onCleanup(
|
||||
theme.onError(({ name, error }) =>
|
||||
themes.onError(({ name, error }) =>
|
||||
toast.show({
|
||||
variant: "error",
|
||||
title: `Failed to load theme: ${name}`,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import {
|
|||
type ModelPreference,
|
||||
type ModelPreferenceModel,
|
||||
} from "../model-preference"
|
||||
import { useTheme } from "./theme"
|
||||
import { useTheme, useThemes } from "./theme"
|
||||
import { useToast } from "../ui/toast"
|
||||
import { useRoute } from "./route"
|
||||
import { useData } from "./data"
|
||||
|
|
@ -50,7 +50,8 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||
const data = useData()
|
||||
const client = useClient()
|
||||
const toast = useToast()
|
||||
const { themeV2, mode } = useTheme()
|
||||
const theme = useTheme()
|
||||
const { mode } = useThemes()
|
||||
const route = useRoute()
|
||||
const paths = useTuiPaths()
|
||||
const args = useArgs()
|
||||
|
|
@ -82,7 +83,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||
const colors = createMemo(() => {
|
||||
const step = mode() === "light" ? 800 : 200
|
||||
return dedupeWith(
|
||||
themeV2.categorical.map((scale) => scale[step]),
|
||||
theme.categorical.map((scale) => scale[step]),
|
||||
(first, second) => first.equals(second),
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -96,13 +96,13 @@ type State = {
|
|||
}
|
||||
|
||||
type ContextName = "elevated" | "overlay"
|
||||
type ThemeService = {
|
||||
themeV2: ComponentTheme
|
||||
contextual(context: ContextName): ThemeService
|
||||
type Themes = {
|
||||
current: ComponentTheme
|
||||
contextual(context: ContextName): ComponentTheme
|
||||
readonly selected: string
|
||||
all: typeof allThemes
|
||||
has: typeof hasTheme
|
||||
syntax: Accessor<SyntaxStyle>
|
||||
currentSyntax: Accessor<SyntaxStyle>
|
||||
mode: Accessor<"dark" | "light">
|
||||
modes: Accessor<readonly ("dark" | "light")[]>
|
||||
supports(mode: "dark" | "light"): boolean
|
||||
|
|
@ -308,7 +308,7 @@ const themeContext = createSimpleContext({
|
|||
const valuesV2 = () => selected().theme
|
||||
valuesV2()
|
||||
themePerformance.set("Init", `${(performance.now() - initStarted).toFixed(2)} ms`)
|
||||
const themeV2 = createComponentTheme(valuesV2, mode)
|
||||
const current = createComponentTheme(valuesV2, mode)
|
||||
const contextsV2 = {
|
||||
elevated: createComponentTheme(() => valuesV2().contexts["@context:elevated"] ?? valuesV2(), mode),
|
||||
overlay: createComponentTheme(() => valuesV2().contexts["@context:overlay"] ?? valuesV2(), mode),
|
||||
|
|
@ -316,19 +316,19 @@ const themeContext = createSimpleContext({
|
|||
|
||||
createEffect(() => renderer.setBackgroundColor(valuesV2().background.default))
|
||||
|
||||
const syntax = createSyntaxStyleMemo(() => generateSyntax(valuesV2(), mode()))
|
||||
const currentSyntax = createSyntaxStyleMemo(() => generateSyntax(valuesV2(), mode()))
|
||||
function contextual(context: ContextName) {
|
||||
return contextualServices[context]
|
||||
return contextsV2[context]
|
||||
}
|
||||
const service: ThemeService = {
|
||||
themeV2,
|
||||
const service: Themes = {
|
||||
current,
|
||||
currentSyntax,
|
||||
contextual,
|
||||
get selected() {
|
||||
return store.active
|
||||
},
|
||||
all: allThemes,
|
||||
has: hasTheme,
|
||||
syntax,
|
||||
mode,
|
||||
modes,
|
||||
supports: (requested) => modes().includes(requested),
|
||||
|
|
@ -355,21 +355,28 @@ const themeContext = createSimpleContext({
|
|||
return store.ready
|
||||
},
|
||||
}
|
||||
const contextualServices = {
|
||||
elevated: Object.assign(Object.create(service) as ThemeService, { themeV2: contextsV2.elevated }),
|
||||
overlay: Object.assign(Object.create(service) as ThemeService, { themeV2: contextsV2.overlay }),
|
||||
return {
|
||||
current,
|
||||
themes: service,
|
||||
get ready() {
|
||||
return service.ready
|
||||
},
|
||||
}
|
||||
return service
|
||||
},
|
||||
})
|
||||
|
||||
export const useTheme = themeContext.use
|
||||
export function useThemes() {
|
||||
return themeContext.use().themes
|
||||
}
|
||||
export function useTheme() {
|
||||
return themeContext.use().current
|
||||
}
|
||||
export const ThemeProvider = themeContext.provider
|
||||
|
||||
export function ThemeContextProvider(props: ParentProps<{ context: ContextName }>) {
|
||||
const theme = useTheme()
|
||||
const themes = useThemes()
|
||||
return (
|
||||
<themeContext.context.Provider value={theme.contextual(props.context)}>
|
||||
<themeContext.context.Provider value={{ current: themes.contextual(props.context), themes, ready: themes.ready }}>
|
||||
{props.children}
|
||||
</themeContext.context.Provider>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import { Keymap } from "../context/keymap"
|
|||
import { useRoute } from "../context/route"
|
||||
import { useTuiApp, useTuiLifecycle, useTuiPaths } from "../context/runtime"
|
||||
import { useLocation } from "../context/location"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useTheme, useThemes } from "../context/theme"
|
||||
import { DialogAlert } from "../ui/dialog-alert"
|
||||
import { DialogConfirm } from "../ui/dialog-confirm"
|
||||
import { DialogPrompt } from "../ui/dialog-prompt"
|
||||
|
|
@ -80,7 +80,8 @@ export function PluginProvider(props: ParentProps<{ packages: PackageResolver }>
|
|||
const paths = useTuiPaths()
|
||||
const location = useLocation()
|
||||
const theme = useTheme()
|
||||
const pluginTheme = createPluginTheme(theme)
|
||||
const themes = useThemes()
|
||||
const pluginTheme = createPluginTheme(theme, themes)
|
||||
const dialog = useDialog()
|
||||
const toast = useToast()
|
||||
const attention = useAttention()
|
||||
|
|
@ -528,19 +529,20 @@ function isPlugin(value: unknown): value is Plugin.Definition {
|
|||
)
|
||||
}
|
||||
|
||||
type PluginTheme = ReturnType<typeof useTheme>["themeV2"] & {
|
||||
type PluginTheme = ReturnType<typeof useTheme> & {
|
||||
contextual(context: "elevated" | "overlay"): PluginTheme
|
||||
syntaxStyle(): ReturnType<ReturnType<typeof useTheme>["syntax"]>
|
||||
syntaxStyle(): ReturnType<ReturnType<typeof useThemes>["currentSyntax"]>
|
||||
}
|
||||
|
||||
export function createPluginTheme(theme: ReturnType<typeof useTheme>): PluginTheme {
|
||||
return new Proxy(theme.themeV2 as PluginTheme, {
|
||||
export function createPluginTheme(theme: ReturnType<typeof useTheme>, themes: ReturnType<typeof useThemes>): PluginTheme {
|
||||
return new Proxy(theme as PluginTheme, {
|
||||
get(target, property, receiver) {
|
||||
if (property === "contextual")
|
||||
return (context: "elevated" | "overlay") => createPluginTheme(theme.contextual(context))
|
||||
if (property === "syntaxStyle") return theme.syntax
|
||||
if (property === "contextual") {
|
||||
return (context: "elevated" | "overlay") => createPluginTheme(themes.contextual(context), themes)
|
||||
}
|
||||
if (property === "syntaxStyle") return themes.currentSyntax
|
||||
if (Reflect.has(target, property)) return Reflect.get(target, property, receiver)
|
||||
return Reflect.get(theme, property, theme)
|
||||
return Reflect.get(themes, property, themes)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { createEffect, createMemo, For, onCleanup, Show, useContext, createContext } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { TextAttributes } from "@opentui/core"
|
||||
import { useTheme } from "../../../context/theme"
|
||||
import { useThemes } from "../../../context/theme"
|
||||
import { SplitBorder } from "../../../ui/border"
|
||||
import { Keymap } from "../../../context/keymap"
|
||||
import { SubagentsTab } from "./subagents-tab"
|
||||
|
|
@ -39,7 +39,7 @@ export type ComposerProps = {
|
|||
}
|
||||
|
||||
export function Composer(props: ComposerProps) {
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
|
||||
const [store, setStore] = createStore({
|
||||
tabs: {} as Record<string, Tab>,
|
||||
|
|
@ -113,8 +113,8 @@ export function Composer(props: ComposerProps) {
|
|||
<box
|
||||
{...SplitBorder}
|
||||
border={["left"]}
|
||||
borderColor={themeV2.border.default}
|
||||
backgroundColor={themeV2.background.default}
|
||||
borderColor={theme.border.default}
|
||||
backgroundColor={theme.background.default}
|
||||
paddingLeft={1}
|
||||
paddingRight={2}
|
||||
paddingTop={1}
|
||||
|
|
@ -125,7 +125,7 @@ export function Composer(props: ComposerProps) {
|
|||
<Show
|
||||
when={tabList().length > 1}
|
||||
fallback={
|
||||
<text fg={themeV2.text.default} attributes={TextAttributes.BOLD}>
|
||||
<text fg={theme.text.default} attributes={TextAttributes.BOLD}>
|
||||
{tabList()[0]?.label ?? ""}
|
||||
</text>
|
||||
}
|
||||
|
|
@ -136,7 +136,7 @@ export function Composer(props: ComposerProps) {
|
|||
const isActive = createMemo(() => store.active === t.id)
|
||||
return (
|
||||
<text
|
||||
fg={isActive() ? themeV2.text.default : themeV2.text.subdued}
|
||||
fg={isActive() ? theme.text.default : theme.text.subdued}
|
||||
attributes={isActive() ? TextAttributes.BOLD : undefined}
|
||||
>
|
||||
{t.label}
|
||||
|
|
@ -146,7 +146,7 @@ export function Composer(props: ComposerProps) {
|
|||
</For>
|
||||
</box>
|
||||
</Show>
|
||||
<text fg={themeV2.text.subdued} onMouseUp={close}>
|
||||
<text fg={theme.text.subdued} onMouseUp={close}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
|
|
@ -156,19 +156,19 @@ export function Composer(props: ComposerProps) {
|
|||
<For each={footerHints()}>
|
||||
{(hint) => (
|
||||
<text>
|
||||
<span style={{ fg: themeV2.text.default }}>
|
||||
<span style={{ fg: theme.text.default }}>
|
||||
<b>{hint.label}</b>{" "}
|
||||
</span>
|
||||
<span style={{ fg: themeV2.text.subdued }}>{hint.shortcut}</span>
|
||||
<span style={{ fg: theme.text.subdued }}>{hint.shortcut}</span>
|
||||
</text>
|
||||
)}
|
||||
</For>
|
||||
<Show when={tabList().length > 1}>
|
||||
<text>
|
||||
<span style={{ fg: themeV2.text.default }}>
|
||||
<span style={{ fg: theme.text.default }}>
|
||||
<b>tabs</b>{" "}
|
||||
</span>
|
||||
<span style={{ fg: themeV2.text.subdued }}>←/→</span>
|
||||
<span style={{ fg: theme.text.subdued }}>←/→</span>
|
||||
</text>
|
||||
</Show>
|
||||
</box>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export function ShellTab(props: { sessionID: string }) {
|
|||
const data = useData()
|
||||
const location = useLocation()
|
||||
const client = useClient()
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
const composer = useComposerTab()
|
||||
const shortcuts = Keymap.useShortcuts()
|
||||
|
||||
|
|
@ -98,7 +98,7 @@ export function ShellTab(props: { sessionID: string }) {
|
|||
return (
|
||||
<Show when={composer.active("shell")}>
|
||||
<scrollbox scrollbarOptions={{ visible: false }} maxHeight={5} ref={(r: ScrollBoxRenderable) => (scroll = r)}>
|
||||
<Show when={entries().length > 0} fallback={<text fg={themeV2.text.subdued}> No shell commands</text>}>
|
||||
<Show when={entries().length > 0} fallback={<text fg={theme.text.subdued}> No shell commands</text>}>
|
||||
<For each={entries()}>
|
||||
{(shell, index) => {
|
||||
const active = createMemo(() => index() === store.selected)
|
||||
|
|
@ -108,12 +108,12 @@ export function ShellTab(props: { sessionID: string }) {
|
|||
paddingLeft={1}
|
||||
paddingRight={1}
|
||||
backgroundColor={
|
||||
active() ? themeV2.background.action.primary.focused : themeV2.background.action.primary.default
|
||||
active() ? theme.background.action.primary.focused : theme.background.action.primary.default
|
||||
}
|
||||
onMouseOver={() => setStore("selected", index())}
|
||||
>
|
||||
<text
|
||||
fg={active() ? themeV2.text.action.primary.focused : themeV2.text.action.primary.default}
|
||||
fg={active() ? theme.text.action.primary.focused : theme.text.action.primary.default}
|
||||
attributes={active() ? TextAttributes.BOLD : undefined}
|
||||
wrapMode="none"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export function SubagentsTab(props: { sessionID: string }) {
|
|||
const route = useRouteData("session")
|
||||
const data = useData()
|
||||
const client = useClient()
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
const navigate = useRoute().navigate
|
||||
const composer = useComposerTab()
|
||||
const shortcuts = Keymap.useShortcuts()
|
||||
|
|
@ -206,7 +206,7 @@ export function SubagentsTab(props: { sessionID: string }) {
|
|||
return (
|
||||
<Show when={composer.active("subagents")}>
|
||||
<scrollbox scrollbarOptions={{ visible: false }} maxHeight={5} ref={(r: ScrollBoxRenderable) => (scroll = r)}>
|
||||
<Show when={entries().length > 0} fallback={<text fg={themeV2.text.subdued}> No subagents</text>}>
|
||||
<Show when={entries().length > 0} fallback={<text fg={theme.text.subdued}> No subagents</text>}>
|
||||
<For each={entries()}>
|
||||
{(entry, index) => {
|
||||
const active = createMemo(() => index() === selected())
|
||||
|
|
@ -221,10 +221,10 @@ export function SubagentsTab(props: { sessionID: string }) {
|
|||
paddingRight={1}
|
||||
backgroundColor={
|
||||
active()
|
||||
? themeV2.background.action.primary.focused
|
||||
? theme.background.action.primary.focused
|
||||
: entry.current
|
||||
? themeV2.background.action.primary.selected
|
||||
: themeV2.background.action.primary.default
|
||||
? theme.background.action.primary.selected
|
||||
: theme.background.action.primary.default
|
||||
}
|
||||
onMouseOver={() => setStore("selected", index())}
|
||||
onMouseUp={() => {
|
||||
|
|
@ -236,10 +236,10 @@ export function SubagentsTab(props: { sessionID: string }) {
|
|||
<text
|
||||
fg={
|
||||
active()
|
||||
? themeV2.text.action.primary.focused
|
||||
? theme.text.action.primary.focused
|
||||
: entry.current
|
||||
? themeV2.text.action.primary.selected
|
||||
: themeV2.text.action.primary.default
|
||||
? theme.text.action.primary.selected
|
||||
: theme.text.action.primary.default
|
||||
}
|
||||
attributes={active() ? TextAttributes.BOLD : undefined}
|
||||
wrapMode="none"
|
||||
|
|
@ -248,7 +248,7 @@ export function SubagentsTab(props: { sessionID: string }) {
|
|||
</text>
|
||||
</box>
|
||||
<Show when={status()}>
|
||||
<text fg={active() ? themeV2.text.action.primary.focused : themeV2.text.subdued} wrapMode="none">
|
||||
<text fg={active() ? theme.text.action.primary.focused : theme.text.subdued} wrapMode="none">
|
||||
{status()}
|
||||
</text>
|
||||
</Show>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { useRoute } from "../../context/route"
|
|||
import { usePermission } from "../../context/permission"
|
||||
|
||||
export function Footer() {
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
const data = useData()
|
||||
const route = useRoute()
|
||||
const permission = usePermission()
|
||||
|
|
@ -54,35 +54,35 @@ export function Footer() {
|
|||
|
||||
return (
|
||||
<box flexDirection="row" justifyContent="space-between" gap={1} flexShrink={0}>
|
||||
<text fg={themeV2.text.subdued}>{directory()}</text>
|
||||
<text fg={theme.text.subdued}>{directory()}</text>
|
||||
<box gap={2} flexDirection="row" flexShrink={0}>
|
||||
<Switch>
|
||||
<Match when={store.welcome}>
|
||||
<text fg={themeV2.text.default}>
|
||||
Get started <span style={{ fg: themeV2.text.subdued }}>/connect</span>
|
||||
<text fg={theme.text.default}>
|
||||
Get started <span style={{ fg: theme.text.subdued }}>/connect</span>
|
||||
</text>
|
||||
</Match>
|
||||
<Match when={connected()}>
|
||||
<Show when={permission.mode !== "auto" && permissions().length > 0}>
|
||||
<text fg={themeV2.text.feedback.warning.default}>
|
||||
<span style={{ fg: themeV2.text.feedback.warning.default }}>△</span> {permissions().length} Permission
|
||||
<text fg={theme.text.feedback.warning.default}>
|
||||
<span style={{ fg: theme.text.feedback.warning.default }}>△</span> {permissions().length} Permission
|
||||
{permissions().length > 1 ? "s" : ""}
|
||||
</text>
|
||||
</Show>
|
||||
<Show when={mcp()}>
|
||||
<text fg={themeV2.text.default}>
|
||||
<text fg={theme.text.default}>
|
||||
<Switch>
|
||||
<Match when={mcpError()}>
|
||||
<span style={{ fg: themeV2.text.feedback.error.default }}>⊙ </span>
|
||||
<span style={{ fg: theme.text.feedback.error.default }}>⊙ </span>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<span style={{ fg: themeV2.text.feedback.success.default }}>⊙ </span>
|
||||
<span style={{ fg: theme.text.feedback.success.default }}>⊙ </span>
|
||||
</Match>
|
||||
</Switch>
|
||||
{mcp()} MCP
|
||||
</text>
|
||||
</Show>
|
||||
<text fg={themeV2.text.subdued}>/status</text>
|
||||
<text fg={theme.text.subdued}>/status</text>
|
||||
</Match>
|
||||
</Switch>
|
||||
</box>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { createMemo, createSignal, For, onCleanup, onMount, Show } from "solid-j
|
|||
import { useRenderer, useTerminalDimensions } from "@opentui/solid"
|
||||
import type { ScrollBoxRenderable, TextareaRenderable } from "@opentui/core"
|
||||
import open from "open"
|
||||
import { useTheme } from "../../context/theme"
|
||||
import { useThemes } from "../../context/theme"
|
||||
import type { FormField, FormValue } from "@opencode-ai/client"
|
||||
import type { FormWithLocation } from "../../context/data"
|
||||
import { useClient } from "../../context/client"
|
||||
|
|
@ -44,7 +44,9 @@ function requestOptions(form: FormWithLocation) {
|
|||
|
||||
export function FormPrompt(props: { form: FormWithLocation }) {
|
||||
const client = useClient()
|
||||
const { themeV2, mode: themeMode } = useTheme().contextual("elevated")
|
||||
const themes = useThemes()
|
||||
const theme = themes.contextual("elevated")
|
||||
const themeMode = themes.mode
|
||||
const renderer = useRenderer()
|
||||
const dimensions = useTerminalDimensions()
|
||||
const keymap = Keymap.use()
|
||||
|
|
@ -624,27 +626,27 @@ export function FormPrompt(props: { form: FormWithLocation }) {
|
|||
|
||||
return (
|
||||
<box
|
||||
backgroundColor={themeV2.background.default}
|
||||
backgroundColor={theme.background.default}
|
||||
border={["left"]}
|
||||
borderColor={themeV2.hue.interactive[themeMode() === "light" ? 800 : 200]}
|
||||
borderColor={theme.hue.interactive[themeMode() === "light" ? 800 : 200]}
|
||||
customBorderChars={SplitBorder.customBorderChars}
|
||||
>
|
||||
<box gap={1} paddingLeft={1} paddingRight={3} paddingTop={1} paddingBottom={1}>
|
||||
<box paddingLeft={1}>
|
||||
<text fg={themeV2.text.subdued}>{props.form.title}</text>
|
||||
<text fg={theme.text.subdued}>{props.form.title}</text>
|
||||
</box>
|
||||
<Show when={message()}>
|
||||
<box paddingLeft={1}>
|
||||
<text fg={themeV2.text.default}>{message()}</text>
|
||||
<text fg={theme.text.default}>{message()}</text>
|
||||
</box>
|
||||
</Show>
|
||||
<Show when={!single() && !tabbed()}>
|
||||
<box flexDirection="row" gap={1} paddingLeft={1}>
|
||||
<text fg={themeV2.text.subdued}>
|
||||
<text fg={theme.text.subdued}>
|
||||
{confirm() ? "Review" : `Field ${Math.min(store.tab, fields().length - 1) + 1} of ${fields().length}`}
|
||||
</text>
|
||||
<Show when={fields().length > 0}>
|
||||
<text fg={themeV2.text.subdued}>
|
||||
<text fg={theme.text.subdued}>
|
||||
· {answered()}/{fields().length} completed
|
||||
</text>
|
||||
</Show>
|
||||
|
|
@ -661,10 +663,10 @@ export function FormPrompt(props: { form: FormWithLocation }) {
|
|||
paddingRight={2}
|
||||
backgroundColor={
|
||||
isTab()
|
||||
? themeV2.background.formfield.selected
|
||||
? theme.background.formfield.selected
|
||||
: tabHover() === index()
|
||||
? themeV2.background.formfield.focused
|
||||
: themeV2.background.default
|
||||
? theme.background.formfield.focused
|
||||
: theme.background.default
|
||||
}
|
||||
onMouseOver={() => setTabHover(index())}
|
||||
onMouseOut={() => setTabHover(null)}
|
||||
|
|
@ -676,12 +678,12 @@ export function FormPrompt(props: { form: FormWithLocation }) {
|
|||
<text
|
||||
fg={
|
||||
isTab()
|
||||
? themeV2.text.formfield.selected
|
||||
? theme.text.formfield.selected
|
||||
: tabHover() === index()
|
||||
? themeV2.text.formfield.focused
|
||||
? theme.text.formfield.focused
|
||||
: isAnswered()
|
||||
? themeV2.text.default
|
||||
: themeV2.text.subdued
|
||||
? theme.text.default
|
||||
: theme.text.subdued
|
||||
}
|
||||
>
|
||||
{truncate(formLabel(item), 24)}
|
||||
|
|
@ -693,10 +695,10 @@ export function FormPrompt(props: { form: FormWithLocation }) {
|
|||
<box
|
||||
backgroundColor={
|
||||
confirm()
|
||||
? themeV2.background.formfield.selected
|
||||
? theme.background.formfield.selected
|
||||
: tabHover() === "confirm"
|
||||
? themeV2.background.formfield.focused
|
||||
: themeV2.background.default
|
||||
? theme.background.formfield.focused
|
||||
: theme.background.default
|
||||
}
|
||||
onMouseOver={() => setTabHover("confirm")}
|
||||
onMouseOut={() => setTabHover(null)}
|
||||
|
|
@ -705,7 +707,7 @@ export function FormPrompt(props: { form: FormWithLocation }) {
|
|||
selectTabFromMouse()
|
||||
}}
|
||||
>
|
||||
<text fg={confirm() ? themeV2.text.formfield.selected : themeV2.text.formfield.default}>Confirm</text>
|
||||
<text fg={confirm() ? theme.text.formfield.selected : theme.text.formfield.default}>Confirm</text>
|
||||
</box>
|
||||
</box>
|
||||
</Show>
|
||||
|
|
@ -714,13 +716,13 @@ export function FormPrompt(props: { form: FormWithLocation }) {
|
|||
{(external) => (
|
||||
<box paddingLeft={1} gap={1}>
|
||||
<Show when={external().title}>
|
||||
<text fg={themeV2.text.default}>{external().title}</text>
|
||||
<text fg={theme.text.default}>{external().title}</text>
|
||||
</Show>
|
||||
<Show when={external().description}>
|
||||
<text fg={themeV2.text.subdued}>{external().description}</text>
|
||||
<text fg={theme.text.subdued}>{external().description}</text>
|
||||
</Show>
|
||||
<text
|
||||
fg={themeV2.background.action.primary.default}
|
||||
fg={theme.background.action.primary.default}
|
||||
onMouseUp={() => {
|
||||
if (renderer.getSelection()?.getSelectedText()) return
|
||||
openExternal()
|
||||
|
|
@ -729,9 +731,7 @@ export function FormPrompt(props: { form: FormWithLocation }) {
|
|||
{external().url}
|
||||
</text>
|
||||
<text
|
||||
fg={
|
||||
store.answers[external().key] === true ? themeV2.text.feedback.success.default : themeV2.text.subdued
|
||||
}
|
||||
fg={store.answers[external().key] === true ? theme.text.feedback.success.default : theme.text.subdued}
|
||||
>
|
||||
{store.answers[external().key] === true
|
||||
? "✓ Acknowledged"
|
||||
|
|
@ -746,7 +746,7 @@ export function FormPrompt(props: { form: FormWithLocation }) {
|
|||
<Show when={!confirm() && answerField()}>
|
||||
<box paddingLeft={1} gap={1}>
|
||||
<box>
|
||||
<text fg={themeV2.text.default}>{answerField()!.description ?? formLabel(answerField()!)}</text>
|
||||
<text fg={theme.text.default}>{answerField()!.description ?? formLabel(answerField()!)}</text>
|
||||
</box>
|
||||
<Show when={textual() ? answerField()!.key : undefined} keyed>
|
||||
<box paddingLeft={1}>
|
||||
|
|
@ -763,12 +763,12 @@ export function FormPrompt(props: { form: FormWithLocation }) {
|
|||
input() || formDisplayValue(answerField()!, store.answers[answerField()!.key], "(none)")
|
||||
}
|
||||
placeholder={placeholder()}
|
||||
placeholderColor={themeV2.text.subdued}
|
||||
placeholderColor={theme.text.subdued}
|
||||
minHeight={1}
|
||||
maxHeight={6}
|
||||
textColor={themeV2.text.default}
|
||||
focusedTextColor={themeV2.text.default}
|
||||
cursorColor={themeV2.text.default}
|
||||
textColor={theme.text.default}
|
||||
focusedTextColor={theme.text.default}
|
||||
cursorColor={theme.text.default}
|
||||
/>
|
||||
</box>
|
||||
</Show>
|
||||
|
|
@ -793,39 +793,35 @@ export function FormPrompt(props: { form: FormWithLocation }) {
|
|||
>
|
||||
<box flexDirection="row">
|
||||
<box
|
||||
backgroundColor={
|
||||
active() ? themeV2.background.formfield.focused : themeV2.background.default
|
||||
}
|
||||
backgroundColor={active() ? theme.background.formfield.focused : theme.background.default}
|
||||
paddingRight={1}
|
||||
>
|
||||
<text
|
||||
fg={active() ? themeV2.text.formfield.focused : themeV2.text.formfield.default}
|
||||
fg={active() ? theme.text.formfield.focused : theme.text.formfield.default}
|
||||
>{`${i() + 1}.`}</text>
|
||||
</box>
|
||||
<box
|
||||
backgroundColor={
|
||||
active() ? themeV2.background.formfield.focused : themeV2.background.default
|
||||
}
|
||||
backgroundColor={active() ? theme.background.formfield.focused : theme.background.default}
|
||||
>
|
||||
<text
|
||||
fg={
|
||||
active()
|
||||
? themeV2.text.formfield.focused
|
||||
? theme.text.formfield.focused
|
||||
: picked()
|
||||
? themeV2.text.formfield.selected
|
||||
: themeV2.text.formfield.default
|
||||
? theme.text.formfield.selected
|
||||
: theme.text.formfield.default
|
||||
}
|
||||
>
|
||||
{multi() ? `[${picked() ? "✓" : " "}] ${row.label}` : row.label}
|
||||
</text>
|
||||
</box>
|
||||
<Show when={!multi()}>
|
||||
<text fg={themeV2.text.formfield.selected}>{picked() ? " ✓" : ""}</text>
|
||||
<text fg={theme.text.formfield.selected}>{picked() ? " ✓" : ""}</text>
|
||||
</Show>
|
||||
</box>
|
||||
<Show when={row.description}>
|
||||
<box paddingLeft={3}>
|
||||
<text fg={themeV2.text.subdued}>{row.description}</text>
|
||||
<text fg={theme.text.subdued}>{row.description}</text>
|
||||
</box>
|
||||
</Show>
|
||||
</box>
|
||||
|
|
@ -843,30 +839,28 @@ export function FormPrompt(props: { form: FormWithLocation }) {
|
|||
>
|
||||
<box flexDirection="row">
|
||||
<box
|
||||
backgroundColor={other() ? themeV2.background.formfield.focused : themeV2.background.default}
|
||||
backgroundColor={other() ? theme.background.formfield.focused : theme.background.default}
|
||||
paddingRight={1}
|
||||
>
|
||||
<text fg={other() ? themeV2.text.formfield.focused : themeV2.text.formfield.default}>
|
||||
<text fg={other() ? theme.text.formfield.focused : theme.text.formfield.default}>
|
||||
{`${rows().length + 1}.`}
|
||||
</text>
|
||||
</box>
|
||||
<box
|
||||
backgroundColor={other() ? themeV2.background.formfield.focused : themeV2.background.default}
|
||||
>
|
||||
<box backgroundColor={other() ? theme.background.formfield.focused : theme.background.default}>
|
||||
<text
|
||||
fg={
|
||||
other()
|
||||
? themeV2.text.formfield.focused
|
||||
? theme.text.formfield.focused
|
||||
: customPicked()
|
||||
? themeV2.text.feedback.success.default
|
||||
: themeV2.text.default
|
||||
? theme.text.feedback.success.default
|
||||
: theme.text.default
|
||||
}
|
||||
>
|
||||
{multi() ? `[${customPicked() ? "✓" : " "}] Type your own answer` : "Type your own answer"}
|
||||
</text>
|
||||
</box>
|
||||
<Show when={!multi()}>
|
||||
<text fg={themeV2.text.feedback.success.default}>{customPicked() ? " ✓" : ""}</text>
|
||||
<text fg={theme.text.feedback.success.default}>{customPicked() ? " ✓" : ""}</text>
|
||||
</Show>
|
||||
</box>
|
||||
<Show when={store.editing}>
|
||||
|
|
@ -882,18 +876,18 @@ export function FormPrompt(props: { form: FormWithLocation }) {
|
|||
}}
|
||||
initialValue={input()}
|
||||
placeholder="Type your own answer"
|
||||
placeholderColor={themeV2.text.subdued}
|
||||
placeholderColor={theme.text.subdued}
|
||||
minHeight={1}
|
||||
maxHeight={6}
|
||||
textColor={themeV2.text.default}
|
||||
focusedTextColor={themeV2.text.default}
|
||||
cursorColor={themeV2.text.default}
|
||||
textColor={theme.text.default}
|
||||
focusedTextColor={theme.text.default}
|
||||
cursorColor={theme.text.default}
|
||||
/>
|
||||
</box>
|
||||
</Show>
|
||||
<Show when={!store.editing && input()}>
|
||||
<box paddingLeft={3}>
|
||||
<text fg={themeV2.text.subdued}>{input()}</text>
|
||||
<text fg={theme.text.subdued}>{input()}</text>
|
||||
</box>
|
||||
</Show>
|
||||
</box>
|
||||
|
|
@ -906,7 +900,7 @@ export function FormPrompt(props: { form: FormWithLocation }) {
|
|||
<Show when={confirm()}>
|
||||
<Show when={tabbed()}>
|
||||
<box paddingLeft={1}>
|
||||
<text fg={themeV2.text.default}>Review</text>
|
||||
<text fg={theme.text.default}>Review</text>
|
||||
</box>
|
||||
</Show>
|
||||
<scrollbox
|
||||
|
|
@ -921,12 +915,12 @@ export function FormPrompt(props: { form: FormWithLocation }) {
|
|||
return (
|
||||
<box paddingLeft={1}>
|
||||
<text>
|
||||
<span style={{ fg: themeV2.text.subdued }}>{truncate(formLabel(item), 40)}:</span>{" "}
|
||||
<span style={{ fg: theme.text.subdued }}>{truncate(formLabel(item), 40)}:</span>{" "}
|
||||
<span
|
||||
style={{
|
||||
fg: acknowledged()
|
||||
? themeV2.text.feedback.success.default
|
||||
: themeV2.text.feedback.error.default,
|
||||
? theme.text.feedback.success.default
|
||||
: theme.text.feedback.error.default,
|
||||
}}
|
||||
>
|
||||
{acknowledged() ? "Acknowledged" : "(acknowledgement required)"}
|
||||
|
|
@ -942,15 +936,15 @@ export function FormPrompt(props: { form: FormWithLocation }) {
|
|||
return (
|
||||
<box paddingLeft={1}>
|
||||
<text>
|
||||
<span style={{ fg: themeV2.text.subdued }}>{truncate(formLabel(item), 40)}:</span>{" "}
|
||||
<span style={{ fg: theme.text.subdued }}>{truncate(formLabel(item), 40)}:</span>{" "}
|
||||
<span
|
||||
style={{
|
||||
fg:
|
||||
invalid() || missing()
|
||||
? themeV2.text.feedback.error.default
|
||||
? theme.text.feedback.error.default
|
||||
: answered()
|
||||
? themeV2.text.default
|
||||
: themeV2.text.subdued,
|
||||
? theme.text.default
|
||||
: theme.text.subdued,
|
||||
}}
|
||||
>
|
||||
{invalid() ?? (answered() ? value() : missing() ? "(required)" : "(not answered)")}
|
||||
|
|
@ -974,41 +968,41 @@ export function FormPrompt(props: { form: FormWithLocation }) {
|
|||
>
|
||||
<box flexDirection="row" gap={2}>
|
||||
<Show when={!single()}>
|
||||
<text fg={themeV2.text.default}>
|
||||
{"⇆"} <span style={{ fg: themeV2.text.subdued }}>tab</span>
|
||||
<text fg={theme.text.default}>
|
||||
{"⇆"} <span style={{ fg: theme.text.subdued }}>tab</span>
|
||||
</text>
|
||||
</Show>
|
||||
<Show when={!confirm() && !textual() && !externalField()}>
|
||||
<text fg={themeV2.text.default}>
|
||||
{"↑↓"} <span style={{ fg: themeV2.text.subdued }}>select</span>
|
||||
<text fg={theme.text.default}>
|
||||
{"↑↓"} <span style={{ fg: theme.text.subdued }}>select</span>
|
||||
</text>
|
||||
</Show>
|
||||
<Show when={confirm() && fields().length > 0}>
|
||||
<text fg={themeV2.text.default}>
|
||||
{"↑↓"} <span style={{ fg: themeV2.text.subdued }}>scroll</span>
|
||||
<text fg={theme.text.default}>
|
||||
{"↑↓"} <span style={{ fg: theme.text.subdued }}>scroll</span>
|
||||
</text>
|
||||
</Show>
|
||||
<text
|
||||
fg={themeV2.text.default}
|
||||
fg={theme.text.default}
|
||||
onMouseUp={() => {
|
||||
if (renderer.getSelection()?.getSelectedText()) return
|
||||
if (confirm()) submit()
|
||||
if (externalField()) acknowledgeExternal()
|
||||
}}
|
||||
>
|
||||
enter <span style={{ fg: themeV2.text.subdued }}>{actionLabel()}</span>
|
||||
enter <span style={{ fg: theme.text.subdued }}>{actionLabel()}</span>
|
||||
</text>
|
||||
<Show when={externalField() && clipboard.write}>
|
||||
<text fg={themeV2.text.default} onMouseUp={copyExternal}>
|
||||
c <span style={{ fg: themeV2.text.subdued }}>copy</span>
|
||||
<text fg={theme.text.default} onMouseUp={copyExternal}>
|
||||
c <span style={{ fg: theme.text.subdued }}>copy</span>
|
||||
</text>
|
||||
</Show>
|
||||
<text fg={themeV2.text.default} onMouseUp={cancel}>
|
||||
esc <span style={{ fg: themeV2.text.subdued }}>dismiss</span>
|
||||
<text fg={theme.text.default} onMouseUp={cancel}>
|
||||
esc <span style={{ fg: theme.text.subdued }}>dismiss</span>
|
||||
</text>
|
||||
</box>
|
||||
<Show when={store.error}>
|
||||
<text fg={themeV2.text.feedback.error.default}>{store.error}</text>
|
||||
<text fg={theme.text.feedback.error.default}>{store.error}</text>
|
||||
</Show>
|
||||
</box>
|
||||
</box>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import { useData } from "../../context/data"
|
|||
import { SplitBorder } from "../../ui/border"
|
||||
import { useTuiPaths, useTuiTerminalEnvironment } from "../../context/runtime"
|
||||
import { Spinner, SPINNER_FRAMES } from "../../component/spinner"
|
||||
import { ThemeContextProvider, useTheme } from "../../context/theme"
|
||||
import { ThemeContextProvider, useTheme, useThemes } from "../../context/theme"
|
||||
import { BoxRenderable, ScrollBoxRenderable, addDefaultParsers, TextAttributes, RGBA } from "@opentui/core"
|
||||
import { Prompt, type PromptRef } from "../../component/prompt"
|
||||
import type {
|
||||
|
|
@ -133,7 +133,7 @@ export function Session() {
|
|||
const paths = useTuiPaths()
|
||||
const configState = useConfig()
|
||||
const config = configState.data
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
const promptRef = usePromptRef()
|
||||
const session = createMemo(() => data.session.get(route.sessionID))
|
||||
const messages = () => data.session.message.list(route.sessionID)
|
||||
|
|
@ -925,8 +925,8 @@ export function Session() {
|
|||
paddingLeft: 1,
|
||||
visible: showScrollbar(),
|
||||
trackOptions: {
|
||||
backgroundColor: themeV2.raise(themeV2.background.surface.offset),
|
||||
foregroundColor: themeV2.border.default,
|
||||
backgroundColor: theme.raise(theme.background.surface.offset),
|
||||
foregroundColor: theme.border.default,
|
||||
},
|
||||
}}
|
||||
stickyScroll={!navigationMessage()}
|
||||
|
|
@ -1101,7 +1101,7 @@ function TurnTokenUsage(props: {
|
|||
message: (messageID: string) => SessionMessageInfo | undefined
|
||||
}) {
|
||||
const config = useConfig()
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
const verbose = () => config.data.debug?.turn_tokens === "verbose"
|
||||
const steps = createMemo(() => {
|
||||
let previousCache = props.previousCache
|
||||
|
|
@ -1141,15 +1141,15 @@ function TurnTokenUsage(props: {
|
|||
<Show when={Boolean(config.data.debug?.turn_tokens) && steps().length > 0}>
|
||||
<box paddingLeft={3} flexDirection="column">
|
||||
<box flexDirection="row">
|
||||
<text width={INLINE_TOOL_ICON_WIDTH} fg={themeV2.text.subdued}>
|
||||
<text width={INLINE_TOOL_ICON_WIDTH} fg={theme.text.subdued}>
|
||||
◈
|
||||
</text>
|
||||
<text fg={themeV2.text.subdued} attributes={TextAttributes.BOLD}>
|
||||
<text fg={theme.text.subdued} attributes={TextAttributes.BOLD}>
|
||||
Tokens
|
||||
</text>
|
||||
</box>
|
||||
<box paddingLeft={INLINE_TOOL_ICON_WIDTH}>
|
||||
<text fg={themeV2.text.subdued} attributes={TextAttributes.ITALIC}>
|
||||
<text fg={theme.text.subdued} attributes={TextAttributes.ITALIC}>
|
||||
{"Step".padEnd(columns().step + 2)}
|
||||
{"New".padStart(columns().newTokens)}
|
||||
{" "}
|
||||
|
|
@ -1161,7 +1161,7 @@ function TurnTokenUsage(props: {
|
|||
<For each={steps()}>
|
||||
{(item) => (
|
||||
<box paddingLeft={INLINE_TOOL_ICON_WIDTH} flexDirection="column">
|
||||
<text fg={verbose() && item.finish === "tool-call" ? undefined : themeV2.text.subdued}>
|
||||
<text fg={verbose() && item.finish === "tool-call" ? undefined : theme.text.subdued}>
|
||||
{item.finish.padEnd(columns().step + 2)}
|
||||
<span style={{ attributes: TextAttributes.BOLD }}>
|
||||
{item.newTokens.toLocaleString().padStart(columns().newTokens)}
|
||||
|
|
@ -1173,7 +1173,7 @@ function TurnTokenUsage(props: {
|
|||
</text>
|
||||
<TurnTokenToolCalls tools={item.tools} />
|
||||
<Show when={item.reuseDrop !== undefined}>
|
||||
<text fg={themeV2.text.feedback.warning.default}>
|
||||
<text fg={theme.text.feedback.warning.default}>
|
||||
! Likely cache bust: {item.reuseDrop?.toLocaleString()} fewer cached tokens than the previous step
|
||||
</text>
|
||||
</Show>
|
||||
|
|
@ -1186,7 +1186,7 @@ function TurnTokenUsage(props: {
|
|||
}
|
||||
|
||||
function TurnTokenToolCalls(props: { tools: SessionMessageAssistantTool[] }) {
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
const nameWidth = () => Math.max(0, ...props.tools.map((tool) => tool.name.length)) + 2
|
||||
return (
|
||||
<Show when={props.tools.length > 0}>
|
||||
|
|
@ -1197,13 +1197,13 @@ function TurnTokenToolCalls(props: { tools: SessionMessageAssistantTool[] }) {
|
|||
<text
|
||||
width={nameWidth()}
|
||||
flexShrink={0}
|
||||
fg={themeV2.text.subdued}
|
||||
fg={theme.text.subdued}
|
||||
attributes={TextAttributes.BOLD}
|
||||
>
|
||||
{tool.name}
|
||||
</text>
|
||||
<text
|
||||
fg={themeV2.text.subdued}
|
||||
fg={theme.text.subdued}
|
||||
attributes={TextAttributes.DIM}
|
||||
wrapMode="word"
|
||||
flexGrow={1}
|
||||
|
|
@ -1234,7 +1234,7 @@ function turnTokenToolSummary(tool: SessionMessageAssistantTool) {
|
|||
}
|
||||
|
||||
function BackgroundToolHint(props: { messages: SessionMessageInfo[] }) {
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
const shortcut = Keymap.useShortcut("session.background")
|
||||
const visible = createMemo(() => {
|
||||
const current = props.messages.findLast(
|
||||
|
|
@ -1252,8 +1252,8 @@ function BackgroundToolHint(props: { messages: SessionMessageInfo[] }) {
|
|||
<Show when={visible() && shortcut()}>
|
||||
{(value) => (
|
||||
<box marginTop={1} paddingLeft={3} flexShrink={0}>
|
||||
<text fg={themeV2.text.subdued}>
|
||||
Press <span style={{ fg: themeV2.text.default }}>{value()}</span> to move running work to the background
|
||||
<text fg={theme.text.subdued}>
|
||||
Press <span style={{ fg: theme.text.default }}>{value()}</span> to move running work to the background
|
||||
</text>
|
||||
</box>
|
||||
)}
|
||||
|
|
@ -1323,7 +1323,8 @@ function SessionReasoningGroupView(props: {
|
|||
message: (messageID: string) => SessionMessageInfo | undefined
|
||||
}) {
|
||||
const ctx = use()
|
||||
const { themeV2, syntax } = useTheme()
|
||||
const theme = useTheme()
|
||||
const { currentSyntax: syntax } = useThemes()
|
||||
const renderer = useRenderer()
|
||||
const [expanded, setExpanded] = createSignal(false)
|
||||
const [hover, setHover] = createSignal(false)
|
||||
|
|
@ -1363,13 +1364,13 @@ function SessionReasoningGroupView(props: {
|
|||
icon={expanded() ? "-" : "+"}
|
||||
color={
|
||||
!props.completed
|
||||
? themeV2.text.default
|
||||
? theme.text.default
|
||||
: hover() || expanded()
|
||||
? themeV2.text.feedback.warning.default
|
||||
? theme.text.feedback.warning.default
|
||||
: RGBA.fromValues(
|
||||
themeV2.text.feedback.warning.default.r,
|
||||
themeV2.text.feedback.warning.default.g,
|
||||
themeV2.text.feedback.warning.default.b,
|
||||
theme.text.feedback.warning.default.r,
|
||||
theme.text.feedback.warning.default.g,
|
||||
theme.text.feedback.warning.default.b,
|
||||
0.6,
|
||||
)
|
||||
}
|
||||
|
|
@ -1412,7 +1413,7 @@ function SessionReasoningGroupView(props: {
|
|||
<box
|
||||
border={["left"]}
|
||||
customBorderChars={SplitBorder.customBorderChars}
|
||||
borderColor={themeV2.raise(themeV2.background.surface.offset)}
|
||||
borderColor={theme.raise(theme.background.surface.offset)}
|
||||
paddingLeft={1}
|
||||
>
|
||||
<code
|
||||
|
|
@ -1422,7 +1423,7 @@ function SessionReasoningGroupView(props: {
|
|||
syntaxStyle={syntax()}
|
||||
content={content()}
|
||||
conceal={ctx.markdownMode() === "rendered"}
|
||||
fg={themeV2.text.subdued}
|
||||
fg={theme.text.subdued}
|
||||
/>
|
||||
</box>
|
||||
</box>
|
||||
|
|
@ -1444,7 +1445,7 @@ function SessionGroupView(props: {
|
|||
completed: boolean
|
||||
message: (messageID: string) => SessionMessageInfo | undefined
|
||||
}) {
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
const ctx = use()
|
||||
const renderer = useRenderer()
|
||||
const [expanded, setExpanded] = createSignal(false)
|
||||
|
|
@ -1480,7 +1481,7 @@ function SessionGroupView(props: {
|
|||
<Show when={grouped().length > 0}>
|
||||
<InlineToolRow
|
||||
icon={props.completed ? "→" : "✱"}
|
||||
color={hover() ? themeV2.text.default : themeV2.text.subdued}
|
||||
color={hover() ? theme.text.default : theme.text.subdued}
|
||||
complete={props.completed}
|
||||
pending={label()}
|
||||
spinner={!props.completed}
|
||||
|
|
@ -1506,7 +1507,7 @@ function SessionGroupView(props: {
|
|||
function AssistantFooter(props: { message: SessionMessageAssistant }) {
|
||||
const ctx = use()
|
||||
const local = useLocal()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const model = createMemo(
|
||||
() =>
|
||||
ctx
|
||||
|
|
@ -1526,25 +1527,25 @@ function AssistantFooter(props: { message: SessionMessageAssistant }) {
|
|||
paddingTop={1}
|
||||
paddingBottom={1}
|
||||
paddingLeft={2}
|
||||
backgroundColor={themeV2.background.default}
|
||||
backgroundColor={theme.background.default}
|
||||
customBorderChars={SplitBorder.customBorderChars}
|
||||
borderColor={themeV2.text.feedback.error.default}
|
||||
borderColor={theme.text.feedback.error.default}
|
||||
>
|
||||
<text fg={themeV2.text.subdued}>{errorMessage(props.message.error)}</text>
|
||||
<text fg={theme.text.subdued}>{errorMessage(props.message.error)}</text>
|
||||
</box>
|
||||
</Show>
|
||||
<AssistantRetry retry={props.message.retry} />
|
||||
<box paddingLeft={3} marginTop={props.message.error && !interrupted() ? 1 : 0}>
|
||||
<text>
|
||||
<span style={{ fg: props.message.error ? themeV2.text.subdued : local.agent.color(props.message.agent) }}>
|
||||
<span style={{ fg: props.message.error ? theme.text.subdued : local.agent.color(props.message.agent) }}>
|
||||
{Locale.titlecase(props.message.agent)}
|
||||
</span>
|
||||
<span style={{ fg: themeV2.text.subdued }}> · {model()}</span>
|
||||
<span style={{ fg: theme.text.subdued }}> · {model()}</span>
|
||||
<Show when={duration()}>
|
||||
<span style={{ fg: themeV2.text.subdued }}> · {Locale.duration(duration())}</span>
|
||||
<span style={{ fg: theme.text.subdued }}> · {Locale.duration(duration())}</span>
|
||||
</Show>
|
||||
<Show when={interrupted()}>
|
||||
<span style={{ fg: themeV2.text.subdued }}> · interrupted</span>
|
||||
<span style={{ fg: theme.text.subdued }}> · interrupted</span>
|
||||
</Show>
|
||||
</text>
|
||||
</box>
|
||||
|
|
@ -1554,7 +1555,7 @@ function AssistantFooter(props: { message: SessionMessageAssistant }) {
|
|||
|
||||
function SessionSwitchMessageV2(props: { message: SessionMessageInfo }) {
|
||||
const ctx = use()
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
const text = () => {
|
||||
if (props.message.type === "agent-switched") return `Switched agent to ${props.message.agent}`
|
||||
if (props.message.type === "model-switched")
|
||||
|
|
@ -1563,14 +1564,14 @@ function SessionSwitchMessageV2(props: { message: SessionMessageInfo }) {
|
|||
}
|
||||
return (
|
||||
<box paddingLeft={3}>
|
||||
<text fg={themeV2.text.subdued}>{text()}</text>
|
||||
<text fg={theme.text.subdued}>{text()}</text>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
||||
function SessionNoticeMessageV2(props: { message: SessionMessageInfo }) {
|
||||
const ctx = use()
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
const metadata = () => (props.message.type === "synthetic" ? props.message.metadata : undefined)
|
||||
const source = () => stringValue(metadata()?.source)
|
||||
const completion = () => source() === "subagent" || source() === "shell"
|
||||
|
|
@ -1590,15 +1591,15 @@ function SessionNoticeMessageV2(props: { message: SessionMessageInfo }) {
|
|||
const heading = () => `${state() === "completed" ? "↳" : "!"} ${actor()} ${status()}`
|
||||
const suffix = () => Locale.truncateWidth(` · ${description()}`, Math.max(0, ctx.width - 3 - stringWidth(heading())))
|
||||
const color = () => {
|
||||
if (state() === "error") return themeV2.text.feedback.error.default
|
||||
if (state() === "cancelled") return themeV2.text.feedback.warning.default
|
||||
return themeV2.text.feedback.info.default
|
||||
if (state() === "error") return theme.text.feedback.error.default
|
||||
if (state() === "cancelled") return theme.text.feedback.warning.default
|
||||
return theme.text.feedback.info.default
|
||||
}
|
||||
return (
|
||||
<Show
|
||||
when={completion()}
|
||||
fallback={
|
||||
<InlineToolRow icon="◈" color={themeV2.text.subdued} pending="Notice" complete={true}>
|
||||
<InlineToolRow icon="◈" color={theme.text.subdued} pending="Notice" complete={true}>
|
||||
{text()}
|
||||
</InlineToolRow>
|
||||
}
|
||||
|
|
@ -1606,7 +1607,7 @@ function SessionNoticeMessageV2(props: { message: SessionMessageInfo }) {
|
|||
<box marginLeft={3}>
|
||||
<text wrapMode="none">
|
||||
<span style={{ fg: color() }}>{heading()}</span>
|
||||
<span style={{ fg: themeV2.text.subdued }}>{suffix()}</span>
|
||||
<span style={{ fg: theme.text.subdued }}>{suffix()}</span>
|
||||
</text>
|
||||
</box>
|
||||
</Show>
|
||||
|
|
@ -1614,9 +1615,9 @@ function SessionNoticeMessageV2(props: { message: SessionMessageInfo }) {
|
|||
}
|
||||
|
||||
function SessionSkillMessage(props: { message: Extract<SessionMessageInfo, { type: "skill" }> }) {
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<InlineToolRow icon="→" color={themeV2.text.subdued} pending="Skill" complete={true}>
|
||||
<InlineToolRow icon="→" color={theme.text.subdued} pending="Skill" complete={true}>
|
||||
Skill {props.message.name}
|
||||
</InlineToolRow>
|
||||
)
|
||||
|
|
@ -1624,14 +1625,15 @@ function SessionSkillMessage(props: { message: Extract<SessionMessageInfo, { typ
|
|||
|
||||
function CompactionMessage(props: { message: Extract<SessionMessageInfo, { type: "compaction" }> }) {
|
||||
const ctx = use()
|
||||
const { themeV2, syntax } = useTheme()
|
||||
const theme = useTheme()
|
||||
const { currentSyntax: syntax } = useThemes()
|
||||
const status = () => props.message.status
|
||||
const cancelled = () => props.message.status === "failed" && props.message.error.type === "aborted"
|
||||
const text = () =>
|
||||
props.message.status === "failed" ? (cancelled() ? "" : props.message.error.message) : props.message.summary
|
||||
const content = createMemo(() => text().trim())
|
||||
const color = () =>
|
||||
status() === "failed" && !cancelled() ? themeV2.text.feedback.error.default : themeV2.text.subdued
|
||||
status() === "failed" && !cancelled() ? theme.text.feedback.error.default : theme.text.subdued
|
||||
return (
|
||||
<box>
|
||||
<box flexDirection="row" alignItems="center">
|
||||
|
|
@ -1663,8 +1665,8 @@ function CompactionMessage(props: { message: Extract<SessionMessageInfo, { type:
|
|||
content={content()}
|
||||
tableOptions={{ style: "grid" }}
|
||||
conceal={ctx.markdownMode() === "rendered"}
|
||||
fg={themeV2.markdown.text}
|
||||
bg={themeV2.background.default}
|
||||
fg={theme.markdown.text}
|
||||
bg={theme.background.default}
|
||||
/>
|
||||
</box>
|
||||
</Show>
|
||||
|
|
@ -1673,15 +1675,15 @@ function CompactionMessage(props: { message: Extract<SessionMessageInfo, { type:
|
|||
}
|
||||
|
||||
function CompactionQueued() {
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<box flexDirection="row" alignItems="center">
|
||||
<box border={["top"]} borderColor={themeV2.border.default} flexGrow={1} />
|
||||
<box border={["top"]} borderColor={theme.border.default} flexGrow={1} />
|
||||
<box flexDirection="row" gap={1} paddingLeft={1} paddingRight={1}>
|
||||
<text fg={themeV2.text.subdued}>◇</text>
|
||||
<text fg={themeV2.text.subdued}>Compaction queued</text>
|
||||
<text fg={theme.text.subdued}>◇</text>
|
||||
<text fg={theme.text.subdued}>Compaction queued</text>
|
||||
</box>
|
||||
<box border={["top"]} borderColor={themeV2.border.default} flexGrow={1} />
|
||||
<box border={["top"]} borderColor={theme.border.default} flexGrow={1} />
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
|
@ -1702,7 +1704,7 @@ function RevertMessage(props: {
|
|||
}>
|
||||
}) {
|
||||
const ctx = use()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const route = useRouteData("session")
|
||||
const client = useClient()
|
||||
const toast = useToast()
|
||||
|
|
@ -1727,15 +1729,15 @@ function RevertMessage(props: {
|
|||
marginTop={1}
|
||||
border={["left"]}
|
||||
customBorderChars={SplitBorder.customBorderChars}
|
||||
borderColor={themeV2.background.default}
|
||||
borderColor={theme.background.default}
|
||||
>
|
||||
<box
|
||||
paddingTop={1}
|
||||
paddingBottom={1}
|
||||
paddingLeft={2}
|
||||
backgroundColor={hover() ? themeV2.raise(themeV2.background.default) : themeV2.background.default}
|
||||
backgroundColor={hover() ? theme.raise(theme.background.default) : theme.background.default}
|
||||
>
|
||||
<text fg={themeV2.text.subdued}>
|
||||
<text fg={theme.text.subdued}>
|
||||
{props.count} message{props.count === 1 ? "" : "s"} reverted
|
||||
</text>
|
||||
<Show when={props.files.length > 0}>
|
||||
|
|
@ -1743,7 +1745,7 @@ function RevertMessage(props: {
|
|||
<For each={props.files}>
|
||||
{(file) => (
|
||||
<box flexDirection="row" gap={1} flexShrink={0}>
|
||||
<text fg={themeV2.text.subdued}>{statusLabel(file.status)}</text>
|
||||
<text fg={theme.text.subdued}>{statusLabel(file.status)}</text>
|
||||
<FilePath
|
||||
value={file.file}
|
||||
maxWidth={Math.max(
|
||||
|
|
@ -1753,21 +1755,21 @@ function RevertMessage(props: {
|
|||
(file.additions > 0 ? stringWidth(`+${file.additions}`) + 1 : 0) -
|
||||
(file.deletions > 0 ? stringWidth(`-${file.deletions}`) + 1 : 0),
|
||||
)}
|
||||
fg={themeV2.text.default}
|
||||
fg={theme.text.default}
|
||||
/>
|
||||
<Show when={file.additions > 0}>
|
||||
<text fg={themeV2.diff.text.added}>+{file.additions}</text>
|
||||
<text fg={theme.diff.text.added}>+{file.additions}</text>
|
||||
</Show>
|
||||
<Show when={file.deletions > 0}>
|
||||
<text fg={themeV2.diff.text.removed}>-{file.deletions}</text>
|
||||
<text fg={theme.diff.text.removed}>-{file.deletions}</text>
|
||||
</Show>
|
||||
</box>
|
||||
)}
|
||||
</For>
|
||||
</box>
|
||||
</Show>
|
||||
<text fg={themeV2.text.subdued}>
|
||||
<span style={{ fg: themeV2.text.default }}>{redoKey()}</span> or /redo to restore
|
||||
<text fg={theme.text.subdued}>
|
||||
<span style={{ fg: theme.text.default }}>{redoKey()}</span> or /redo to restore
|
||||
</text>
|
||||
</box>
|
||||
</box>
|
||||
|
|
@ -1775,7 +1777,7 @@ function RevertMessage(props: {
|
|||
}
|
||||
|
||||
function ShellMessage(props: { message: Extract<SessionMessageInfo, { type: "shell" }> }) {
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const output = createMemo(() => stripAnsi(props.message.output?.output.trim() ?? ""))
|
||||
|
||||
return (
|
||||
|
|
@ -1785,13 +1787,13 @@ function ShellMessage(props: { message: Extract<SessionMessageInfo, { type: "she
|
|||
paddingBottom={1}
|
||||
paddingLeft={2}
|
||||
gap={1}
|
||||
backgroundColor={themeV2.background.default}
|
||||
backgroundColor={theme.background.default}
|
||||
customBorderChars={SplitBorder.customBorderChars}
|
||||
borderColor={themeV2.background.default}
|
||||
borderColor={theme.background.default}
|
||||
>
|
||||
<text fg={themeV2.text.default}>$ {props.message.command}</text>
|
||||
<text fg={theme.text.default}>$ {props.message.command}</text>
|
||||
<Show when={output()}>
|
||||
<text fg={themeV2.text.subdued}>{output()}</text>
|
||||
<text fg={theme.text.subdued}>{output()}</text>
|
||||
</Show>
|
||||
</box>
|
||||
)
|
||||
|
|
@ -1802,7 +1804,9 @@ function UserMessage(props: { message: SessionMessageUser }) {
|
|||
const data = useData()
|
||||
const local = useLocal()
|
||||
const files = createMemo(() => props.message.files ?? [])
|
||||
const { themeV2, mode } = useTheme().contextual("elevated")
|
||||
const themes = useThemes()
|
||||
const theme = themes.contextual("elevated")
|
||||
const mode = themes.mode
|
||||
const [hover, setHover] = createSignal(false)
|
||||
const color = createMemo(() => local.agent.color(data.session.get(ctx.sessionID)?.agent ?? "build"))
|
||||
const queued = createMemo(
|
||||
|
|
@ -1816,7 +1820,7 @@ function UserMessage(props: { message: SessionMessageUser }) {
|
|||
<Show when={props.message.text.trim() || files().length}>
|
||||
<box
|
||||
border={["left"]}
|
||||
borderColor={queued() ? themeV2.border.default : color()}
|
||||
borderColor={queued() ? theme.border.default : color()}
|
||||
customBorderChars={SplitBorder.customBorderChars}
|
||||
>
|
||||
<box
|
||||
|
|
@ -1839,27 +1843,27 @@ function UserMessage(props: { message: SessionMessageUser }) {
|
|||
paddingTop={1}
|
||||
paddingBottom={1}
|
||||
paddingLeft={2}
|
||||
backgroundColor={hover() ? themeV2.raise(themeV2.background.default) : themeV2.background.default}
|
||||
backgroundColor={hover() ? theme.raise(theme.background.default) : theme.background.default}
|
||||
flexShrink={0}
|
||||
>
|
||||
<text fg={themeV2.text.default}>{props.message.text}</text>
|
||||
<text fg={theme.text.default}>{props.message.text}</text>
|
||||
<Show when={files().length}>
|
||||
<box flexDirection="row" paddingTop={1} gap={1} flexWrap="wrap">
|
||||
<For each={files()}>
|
||||
{(file) => {
|
||||
const label = file.mime === "application/x-directory" ? "dir" : "file"
|
||||
return (
|
||||
<text fg={themeV2.text.default}>
|
||||
<text fg={theme.text.default}>
|
||||
<span
|
||||
style={{
|
||||
bg: themeV2.hue.accent[mode() === "light" ? 700 : 200],
|
||||
fg: themeV2.background.default,
|
||||
bg: theme.hue.accent[mode() === "light" ? 700 : 200],
|
||||
fg: theme.background.default,
|
||||
bold: true,
|
||||
}}
|
||||
>
|
||||
{` ${label} `}
|
||||
</span>
|
||||
<span style={{ bg: themeV2.raise(themeV2.background.default), fg: themeV2.text.subdued }}>
|
||||
<span style={{ bg: theme.raise(theme.background.default), fg: theme.text.subdued }}>
|
||||
{" "}
|
||||
{file.name ?? (file.source.type === "uri" ? file.source.uri : "attachment")}{" "}
|
||||
</span>
|
||||
|
|
@ -1878,7 +1882,7 @@ function UserMessage(props: { message: SessionMessageUser }) {
|
|||
function AssistantMessage(props: { message: SessionMessageAssistant; last: boolean }) {
|
||||
const ctx = use()
|
||||
const local = useLocal()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const model = createMemo(
|
||||
() =>
|
||||
ctx
|
||||
|
|
@ -1964,11 +1968,11 @@ function AssistantMessage(props: { message: SessionMessageAssistant; last: boole
|
|||
paddingTop={1}
|
||||
paddingBottom={1}
|
||||
paddingLeft={2}
|
||||
backgroundColor={themeV2.background.default}
|
||||
backgroundColor={theme.background.default}
|
||||
customBorderChars={SplitBorder.customBorderChars}
|
||||
borderColor={themeV2.text.feedback.error.default}
|
||||
borderColor={theme.text.feedback.error.default}
|
||||
>
|
||||
<text fg={themeV2.text.subdued}>{errorMessage(props.message.error)}</text>
|
||||
<text fg={theme.text.subdued}>{errorMessage(props.message.error)}</text>
|
||||
</box>
|
||||
</Show>
|
||||
<AssistantRetry retry={props.message.retry} />
|
||||
|
|
@ -1976,12 +1980,12 @@ function AssistantMessage(props: { message: SessionMessageAssistant; last: boole
|
|||
<Match when={props.last || final() || props.message.error}>
|
||||
<box paddingLeft={3}>
|
||||
<text>
|
||||
<span style={{ fg: props.message.error ? themeV2.text.subdued : local.agent.color(props.message.agent) }}>
|
||||
<span style={{ fg: props.message.error ? theme.text.subdued : local.agent.color(props.message.agent) }}>
|
||||
{Locale.titlecase(props.message.agent)}
|
||||
</span>
|
||||
<span style={{ fg: themeV2.text.subdued }}> · {model()}</span>
|
||||
<span style={{ fg: theme.text.subdued }}> · {model()}</span>
|
||||
<Show when={duration()}>
|
||||
<span style={{ fg: themeV2.text.subdued }}> · {Locale.duration(duration())}</span>
|
||||
<span style={{ fg: theme.text.subdued }}> · {Locale.duration(duration())}</span>
|
||||
</Show>
|
||||
</text>
|
||||
</box>
|
||||
|
|
@ -1992,12 +1996,12 @@ function AssistantMessage(props: { message: SessionMessageAssistant; last: boole
|
|||
}
|
||||
|
||||
function AssistantRetry(props: { retry: SessionMessageAssistant["retry"] }) {
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<Show when={props.retry}>
|
||||
{(retry) => (
|
||||
<box paddingLeft={3} marginTop={1}>
|
||||
<text fg={themeV2.text.subdued}>
|
||||
<text fg={theme.text.subdued}>
|
||||
Retry attempt {retry().attempt} scheduled: {retry().error.message} [{retry().error.type}]
|
||||
</text>
|
||||
</box>
|
||||
|
|
@ -2007,7 +2011,7 @@ function AssistantRetry(props: { retry: SessionMessageAssistant["retry"] }) {
|
|||
}
|
||||
|
||||
function ExplorationSummary(props: { parts: SessionMessageAssistantTool[]; active: boolean }) {
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
const pathFormatter = usePathFormatter()
|
||||
const label = (part: SessionMessageAssistantTool) => {
|
||||
const input = typeof part.state.input === "string" ? {} : part.state.input
|
||||
|
|
@ -2020,7 +2024,7 @@ function ExplorationSummary(props: { parts: SessionMessageAssistantTool[]; activ
|
|||
<box flexDirection="column">
|
||||
<InlineToolRow
|
||||
icon="✱"
|
||||
color={themeV2.text.subdued}
|
||||
color={theme.text.subdued}
|
||||
complete={!props.active}
|
||||
pending="Exploring"
|
||||
spinner={props.active}
|
||||
|
|
@ -2030,7 +2034,7 @@ function ExplorationSummary(props: { parts: SessionMessageAssistantTool[]; activ
|
|||
<For each={props.parts}>
|
||||
{(part, index) => (
|
||||
<box paddingLeft={5}>
|
||||
<text fg={part.state.status === "error" ? themeV2.text.feedback.error.default : themeV2.text.subdued}>
|
||||
<text fg={part.state.status === "error" ? theme.text.feedback.error.default : theme.text.subdued}>
|
||||
{index() === props.parts.length - 1 ? "└" : "├"} {label(part)}
|
||||
</text>
|
||||
</box>
|
||||
|
|
@ -2047,7 +2051,8 @@ function ReasoningPart(props: {
|
|||
part: SessionMessageAssistantReasoning
|
||||
message: SessionMessageAssistant
|
||||
}) {
|
||||
const { themeV2, syntax } = useTheme()
|
||||
const theme = useTheme()
|
||||
const { currentSyntax: syntax } = useThemes()
|
||||
const ctx = use()
|
||||
// Collapsed by default in hide mode: a single line throughout, so the
|
||||
// layout never shifts. Click to open the full markdown block, click to close.
|
||||
|
|
@ -2075,7 +2080,7 @@ function ReasoningPart(props: {
|
|||
<box
|
||||
border={!inMinimal() || expanded() ? ["left"] : undefined}
|
||||
customBorderChars={SplitBorder.customBorderChars}
|
||||
borderColor={themeV2.raise(themeV2.background.default)}
|
||||
borderColor={theme.raise(theme.background.default)}
|
||||
paddingLeft={!inMinimal() || expanded() ? 1 : 0}
|
||||
>
|
||||
<box onMouseUp={toggle}>
|
||||
|
|
@ -2093,7 +2098,7 @@ function ReasoningPart(props: {
|
|||
<box
|
||||
border={["left"]}
|
||||
customBorderChars={SplitBorder.customBorderChars}
|
||||
borderColor={themeV2.raise(themeV2.background.default)}
|
||||
borderColor={theme.raise(theme.background.default)}
|
||||
paddingLeft={inMinimal() ? 3 : 1}
|
||||
>
|
||||
<code
|
||||
|
|
@ -2103,7 +2108,7 @@ function ReasoningPart(props: {
|
|||
syntaxStyle={syntax()}
|
||||
content={content()}
|
||||
conceal={ctx.markdownMode() === "rendered"}
|
||||
fg={themeV2.text.subdued}
|
||||
fg={theme.text.subdued}
|
||||
/>
|
||||
</box>
|
||||
</box>
|
||||
|
|
@ -2125,16 +2130,16 @@ function ReasoningHeader(props: {
|
|||
title: string | null
|
||||
duration?: string
|
||||
}) {
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
const fg = () =>
|
||||
props.open
|
||||
? RGBA.fromValues(
|
||||
themeV2.text.feedback.warning.default.r,
|
||||
themeV2.text.feedback.warning.default.g,
|
||||
themeV2.text.feedback.warning.default.b,
|
||||
theme.text.feedback.warning.default.r,
|
||||
theme.text.feedback.warning.default.g,
|
||||
theme.text.feedback.warning.default.b,
|
||||
0.6,
|
||||
)
|
||||
: themeV2.text.feedback.warning.default
|
||||
: theme.text.feedback.warning.default
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
|
|
@ -2169,7 +2174,8 @@ function ReasoningHeader(props: {
|
|||
|
||||
function TextPart(props: { last: boolean; part: SessionMessageAssistantText }) {
|
||||
const ctx = use()
|
||||
const { themeV2, syntax } = useTheme()
|
||||
const theme = useTheme()
|
||||
const { currentSyntax: syntax } = useThemes()
|
||||
return (
|
||||
<Show when={props.part.text.trim()}>
|
||||
<box paddingLeft={3} flexShrink={0}>
|
||||
|
|
@ -2180,8 +2186,8 @@ function TextPart(props: { last: boolean; part: SessionMessageAssistantText }) {
|
|||
content={props.part.text.trim()}
|
||||
tableOptions={{ style: "grid" }}
|
||||
conceal={ctx.markdownMode() === "rendered"}
|
||||
fg={themeV2.markdown.text}
|
||||
bg={themeV2.background.default}
|
||||
fg={theme.markdown.text}
|
||||
bg={theme.background.default}
|
||||
/>
|
||||
</box>
|
||||
</Show>
|
||||
|
|
@ -2270,7 +2276,8 @@ type ToolProps = {
|
|||
part: SessionMessageAssistantTool
|
||||
}
|
||||
function GenericTool(props: ToolProps) {
|
||||
const { themeV2, syntax } = useTheme()
|
||||
const theme = useTheme()
|
||||
const { currentSyntax: syntax } = useThemes()
|
||||
const output = createMemo(() => props.output?.trim() ?? "")
|
||||
const args = createMemo(() => JSON.stringify(props.input, null, 2))
|
||||
const [expanded, setExpanded] = createSignal(false)
|
||||
|
|
@ -2288,7 +2295,7 @@ function GenericTool(props: ToolProps) {
|
|||
<Show when={Object.keys(props.input).length > 0}>
|
||||
<box gap={1}>
|
||||
<text>
|
||||
<span style={{ bg: themeV2.raise(themeV2.background.default), fg: themeV2.text.subdued }}> Input </span>
|
||||
<span style={{ bg: theme.raise(theme.background.default), fg: theme.text.subdued }}> Input </span>
|
||||
</text>
|
||||
<box paddingLeft={1}>
|
||||
<code
|
||||
|
|
@ -2297,7 +2304,7 @@ function GenericTool(props: ToolProps) {
|
|||
syntaxStyle={syntax()}
|
||||
conceal={false}
|
||||
drawUnstyledText={false}
|
||||
fg={themeV2.text.default}
|
||||
fg={theme.text.default}
|
||||
/>
|
||||
</box>
|
||||
</box>
|
||||
|
|
@ -2306,13 +2313,13 @@ function GenericTool(props: ToolProps) {
|
|||
{(value) => (
|
||||
<box gap={1}>
|
||||
<text>
|
||||
<span style={{ bg: themeV2.raise(themeV2.background.default), fg: themeV2.text.subdued }}>
|
||||
<span style={{ bg: theme.raise(theme.background.default), fg: theme.text.subdued }}>
|
||||
{" "}
|
||||
Output{" "}
|
||||
</span>
|
||||
</text>
|
||||
<box paddingLeft={1}>
|
||||
<text fg={themeV2.text.default} wrapMode="word">
|
||||
<text fg={theme.text.default} wrapMode="word">
|
||||
{value()}
|
||||
</text>
|
||||
</box>
|
||||
|
|
@ -2349,7 +2356,7 @@ function InlineTool(props: {
|
|||
part: SessionMessageAssistantTool
|
||||
onClick?: () => void
|
||||
}) {
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
const renderer = useRenderer()
|
||||
const [hover, setHover] = createSignal(false)
|
||||
const [errorExpanded, setErrorExpanded] = createSignal(false)
|
||||
|
|
@ -2369,10 +2376,10 @@ function InlineTool(props: {
|
|||
const clickable = createMemo(() => Boolean(props.onClick || failed()))
|
||||
const fg = createMemo(() => {
|
||||
if (props.color) return props.color
|
||||
if (permission()) return themeV2.text.feedback.warning.default
|
||||
if (failed()) return themeV2.text.feedback.error.default
|
||||
if (hover() && props.onClick) return themeV2.text.default
|
||||
return themeV2.text.subdued
|
||||
if (permission()) return theme.text.feedback.warning.default
|
||||
if (failed()) return theme.text.feedback.error.default
|
||||
if (hover() && props.onClick) return theme.text.default
|
||||
return theme.text.subdued
|
||||
})
|
||||
|
||||
return (
|
||||
|
|
@ -2380,7 +2387,7 @@ function InlineTool(props: {
|
|||
icon={props.icon}
|
||||
iconColor={props.iconColor}
|
||||
color={fg()}
|
||||
errorColor={themeV2.text.feedback.error.default}
|
||||
errorColor={theme.text.feedback.error.default}
|
||||
failed={failed()}
|
||||
denied={Boolean(denied())}
|
||||
error={error()}
|
||||
|
|
@ -2502,9 +2509,9 @@ function InlineToolLabel(props: { color?: RGBA; denied?: boolean; status: JSX.El
|
|||
}
|
||||
|
||||
function StatusBadge(props: { children: string }) {
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<text flexShrink={0} bg={themeV2.raise(themeV2.background.default)} fg={themeV2.text.subdued}>
|
||||
<text flexShrink={0} bg={theme.raise(theme.background.default)} fg={theme.text.subdued}>
|
||||
{" "}
|
||||
{props.children}{" "}
|
||||
</text>
|
||||
|
|
@ -2524,13 +2531,13 @@ function BlockTool(props: BlockToolProps) {
|
|||
const parentTheme = useTheme()
|
||||
return (
|
||||
<ThemeContextProvider context="elevated">
|
||||
<BlockToolContent {...props} borderColor={parentTheme.themeV2.background.default} />
|
||||
<BlockToolContent {...props} borderColor={parentTheme.background.default} />
|
||||
</ThemeContextProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function BlockToolContent(props: BlockToolProps & { borderColor: RGBA }) {
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
const ctx = use()
|
||||
const renderer = useRenderer()
|
||||
const [hover, setHover] = createSignal(false)
|
||||
|
|
@ -2543,7 +2550,7 @@ function BlockToolContent(props: BlockToolProps & { borderColor: RGBA }) {
|
|||
paddingBottom={1}
|
||||
paddingLeft={2}
|
||||
gap={1}
|
||||
backgroundColor={hover() ? themeV2.raise(themeV2.background.default) : themeV2.background.default}
|
||||
backgroundColor={hover() ? theme.raise(theme.background.default) : theme.background.default}
|
||||
customBorderChars={SplitBorder.customBorderChars}
|
||||
borderColor={props.borderColor}
|
||||
onMouseOver={() => props.onClick && setHover(true)}
|
||||
|
|
@ -2561,12 +2568,12 @@ function BlockToolContent(props: BlockToolProps & { borderColor: RGBA }) {
|
|||
<Show
|
||||
when={props.spinner}
|
||||
fallback={
|
||||
<text fg={permission() ? themeV2.text.feedback.warning.default : themeV2.text.subdued}>
|
||||
<text fg={permission() ? theme.text.feedback.warning.default : theme.text.subdued}>
|
||||
{title()}
|
||||
</text>
|
||||
}
|
||||
>
|
||||
<Spinner color={permission() ? themeV2.text.feedback.warning.default : themeV2.text.subdued}>
|
||||
<Spinner color={permission() ? theme.text.feedback.warning.default : theme.text.subdued}>
|
||||
{title().replace(/^# /, "")}
|
||||
</Spinner>
|
||||
</Show>
|
||||
|
|
@ -2579,26 +2586,26 @@ function BlockToolContent(props: BlockToolProps & { borderColor: RGBA }) {
|
|||
<Show
|
||||
when={props.spinner}
|
||||
fallback={
|
||||
<text flexShrink={0} fg={permission() ? themeV2.text.feedback.warning.default : themeV2.text.subdued}>
|
||||
<text flexShrink={0} fg={permission() ? theme.text.feedback.warning.default : theme.text.subdued}>
|
||||
{path().label}
|
||||
</text>
|
||||
}
|
||||
>
|
||||
<Spinner color={permission() ? themeV2.text.feedback.warning.default : themeV2.text.subdued}>
|
||||
<Spinner color={permission() ? theme.text.feedback.warning.default : theme.text.subdued}>
|
||||
{path().label.replace(/^# /, "")}
|
||||
</Spinner>
|
||||
</Show>
|
||||
<FilePath
|
||||
value={path().value}
|
||||
maxWidth={Math.max(2, ctx.width - 4 - stringWidth(path().label) - (props.spinner ? 2 : 0))}
|
||||
fg={permission() ? themeV2.text.feedback.warning.default : themeV2.text.subdued}
|
||||
fg={permission() ? theme.text.feedback.warning.default : theme.text.subdued}
|
||||
/>
|
||||
</box>
|
||||
)}
|
||||
</Show>
|
||||
{props.children}
|
||||
<Show when={error()}>
|
||||
<text fg={themeV2.text.feedback.error.default}>{error()}</text>
|
||||
<text fg={theme.text.feedback.error.default}>{error()}</text>
|
||||
</Show>
|
||||
</box>
|
||||
)
|
||||
|
|
@ -2607,13 +2614,13 @@ function BlockToolContent(props: BlockToolProps & { borderColor: RGBA }) {
|
|||
const SHELL_DISPLAY_LIMIT = 1024 * 1024
|
||||
|
||||
function Shell(props: ToolProps) {
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
const ctx = use()
|
||||
const client = useClient()
|
||||
const data = useData()
|
||||
const pathFormatter = usePathFormatter()
|
||||
const permission = useToolPermission(() => props.part)
|
||||
const color = createMemo(() => (permission() ? themeV2.text.feedback.warning.default : themeV2.text.default))
|
||||
const color = createMemo(() => (permission() ? theme.text.feedback.warning.default : theme.text.default))
|
||||
const shellID = createMemo(() => stringValue(props.metadata.shellID))
|
||||
const background = createMemo(() => Boolean(shellID()) && props.part.state.status !== "running")
|
||||
const backgroundRunning = createMemo(() => {
|
||||
|
|
@ -2723,7 +2730,7 @@ function Shell(props: ToolProps) {
|
|||
isRunning() || props.part.state.status === "streaming" ? (
|
||||
<Spinner color={color()}>Writing command...</Spinner>
|
||||
) : (
|
||||
<text fg={themeV2.text.subdued}>Writing command...</text>
|
||||
<text fg={theme.text.subdued}>Writing command...</text>
|
||||
)
|
||||
}
|
||||
>
|
||||
|
|
@ -2731,14 +2738,14 @@ function Shell(props: ToolProps) {
|
|||
when={isRunning()}
|
||||
fallback={
|
||||
<text>
|
||||
<span style={{ fg: themeV2.text.default }}>{limited().slice(0, input().length)}</span>
|
||||
<span style={{ fg: themeV2.text.subdued }}>{limited().slice(input().length)}</span>
|
||||
<span style={{ fg: theme.text.default }}>{limited().slice(0, input().length)}</span>
|
||||
<span style={{ fg: theme.text.subdued }}>{limited().slice(input().length)}</span>
|
||||
</text>
|
||||
}
|
||||
>
|
||||
<Spinner color={color()}>
|
||||
<span style={{ fg: themeV2.text.default }}>{limited().slice(0, input().length)}</span>
|
||||
<span style={{ fg: themeV2.text.subdued }}>{limited().slice(input().length)}</span>
|
||||
<span style={{ fg: theme.text.default }}>{limited().slice(0, input().length)}</span>
|
||||
<span style={{ fg: theme.text.subdued }}>{limited().slice(input().length)}</span>
|
||||
</Spinner>
|
||||
</Show>
|
||||
</Show>
|
||||
|
|
@ -2751,7 +2758,8 @@ function Shell(props: ToolProps) {
|
|||
}
|
||||
|
||||
function Write(props: ToolProps) {
|
||||
const { themeV2, syntax } = useTheme()
|
||||
const theme = useTheme()
|
||||
const { currentSyntax: syntax } = useThemes()
|
||||
const pathFormatter = usePathFormatter()
|
||||
const code = createMemo(() => {
|
||||
return stringValue(props.input.content) ?? ""
|
||||
|
|
@ -2764,10 +2772,10 @@ function Write(props: ToolProps) {
|
|||
path={{ label: "# Wrote", value: pathFormatter.format(stringValue(props.input.path)) }}
|
||||
part={props.part}
|
||||
>
|
||||
<line_number fg={themeV2.text.subdued} minWidth={3} paddingRight={1}>
|
||||
<line_number fg={theme.text.subdued} minWidth={3} paddingRight={1}>
|
||||
<code
|
||||
conceal={false}
|
||||
fg={themeV2.text.default}
|
||||
fg={theme.text.default}
|
||||
filetype={filetype(stringValue(props.input.path))}
|
||||
syntaxStyle={syntax()}
|
||||
content={code()}
|
||||
|
|
@ -2799,7 +2807,7 @@ function Glob(props: ToolProps) {
|
|||
}
|
||||
|
||||
function Read(props: ToolProps) {
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
const pathFormatter = usePathFormatter()
|
||||
const isRunning = createMemo(() => props.part.state.status === "running")
|
||||
const loaded = createMemo(() => {
|
||||
|
|
@ -2822,7 +2830,7 @@ function Read(props: ToolProps) {
|
|||
<For each={loaded()}>
|
||||
{(filepath) => (
|
||||
<box paddingLeft={3}>
|
||||
<text paddingLeft={3} fg={themeV2.text.subdued}>
|
||||
<text paddingLeft={3} fg={theme.text.subdued}>
|
||||
↳ Loaded {pathFormatter.format(filepath)}
|
||||
</text>
|
||||
</box>
|
||||
|
|
@ -2920,7 +2928,7 @@ function executeCalls(value: unknown): ExecuteCall[] {
|
|||
// The `execute` tool streams child tool calls through metadata, not a child session like Task.
|
||||
function Execute(props: ToolProps) {
|
||||
const ctx = use()
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
const isLoading = createMemo(() => props.part.state.status === "streaming" || props.part.state.status === "running")
|
||||
const calls = createMemo(() => executeCalls(props.metadata.toolCalls))
|
||||
const output = createMemo(() => stripAnsi(props.output?.trim() ?? ""))
|
||||
|
|
@ -2940,7 +2948,7 @@ function Execute(props: ToolProps) {
|
|||
<>
|
||||
<InlineTool
|
||||
icon={hasRuntimeError() ? "✗" : props.part.state.status === "completed" ? "✓" : "│"}
|
||||
color={hasRuntimeError() ? themeV2.text.feedback.error.default : undefined}
|
||||
color={hasRuntimeError() ? theme.text.feedback.error.default : undefined}
|
||||
spinner={isLoading()}
|
||||
pending="execute"
|
||||
complete={true}
|
||||
|
|
@ -2952,7 +2960,7 @@ function Execute(props: ToolProps) {
|
|||
<box paddingLeft={3}>
|
||||
<For each={outputPreview().split("\n")}>
|
||||
{(line, index) => (
|
||||
<text paddingLeft={3} fg={themeV2.text.feedback.error.default}>
|
||||
<text paddingLeft={3} fg={theme.text.feedback.error.default}>
|
||||
{index() === 0 ? "↳ " : " "}
|
||||
{line}
|
||||
</text>
|
||||
|
|
@ -2966,7 +2974,8 @@ function Execute(props: ToolProps) {
|
|||
|
||||
function Edit(props: ToolProps) {
|
||||
const ctx = use()
|
||||
const { themeV2, syntax } = useTheme()
|
||||
const theme = useTheme()
|
||||
const { currentSyntax: syntax } = useThemes()
|
||||
const pathFormatter = usePathFormatter()
|
||||
|
||||
const view = createMemo(() => {
|
||||
|
|
@ -2994,16 +3003,16 @@ function Edit(props: ToolProps) {
|
|||
showLineNumbers={true}
|
||||
width="100%"
|
||||
wrapMode={ctx.diffWrapMode()}
|
||||
fg={themeV2.text.default}
|
||||
addedBg={themeV2.diff.background.added}
|
||||
removedBg={themeV2.diff.background.removed}
|
||||
contextBg={themeV2.diff.background.context}
|
||||
addedSignColor={themeV2.diff.highlight.added}
|
||||
removedSignColor={themeV2.diff.highlight.removed}
|
||||
lineNumberFg={themeV2.diff.lineNumber.text}
|
||||
lineNumberBg={themeV2.diff.background.context}
|
||||
addedLineNumberBg={themeV2.diff.lineNumber.background.added}
|
||||
removedLineNumberBg={themeV2.diff.lineNumber.background.removed}
|
||||
fg={theme.text.default}
|
||||
addedBg={theme.diff.background.added}
|
||||
removedBg={theme.diff.background.removed}
|
||||
contextBg={theme.diff.background.context}
|
||||
addedSignColor={theme.diff.highlight.added}
|
||||
removedSignColor={theme.diff.highlight.removed}
|
||||
lineNumberFg={theme.diff.lineNumber.text}
|
||||
lineNumberBg={theme.diff.background.context}
|
||||
addedLineNumberBg={theme.diff.lineNumber.background.added}
|
||||
removedLineNumberBg={theme.diff.lineNumber.background.removed}
|
||||
/>
|
||||
</box>
|
||||
<Diagnostics diagnostics={props.metadata.diagnostics} filePath={stringValue(props.input.path) ?? ""} />
|
||||
|
|
@ -3028,7 +3037,8 @@ function Edit(props: ToolProps) {
|
|||
|
||||
function ApplyPatch(props: ToolProps) {
|
||||
const ctx = use()
|
||||
const { themeV2, syntax } = useTheme()
|
||||
const theme = useTheme()
|
||||
const { currentSyntax: syntax } = useThemes()
|
||||
const pathFormatter = usePathFormatter()
|
||||
const files = createMemo(() => parseApplyPatchFiles(props.metadata.files))
|
||||
const targets = createMemo(() => {
|
||||
|
|
@ -3068,7 +3078,7 @@ function ApplyPatch(props: ToolProps) {
|
|||
<Show
|
||||
when={file.type !== "delete"}
|
||||
fallback={
|
||||
<text fg={themeV2.diff.text.removed}>
|
||||
<text fg={theme.diff.text.removed}>
|
||||
-{file.deletions} line{file.deletions !== 1 ? "s" : ""}
|
||||
</text>
|
||||
}
|
||||
|
|
@ -3082,16 +3092,16 @@ function ApplyPatch(props: ToolProps) {
|
|||
showLineNumbers={true}
|
||||
width="100%"
|
||||
wrapMode={ctx.diffWrapMode()}
|
||||
fg={themeV2.text.default}
|
||||
addedBg={themeV2.diff.background.added}
|
||||
removedBg={themeV2.diff.background.removed}
|
||||
contextBg={themeV2.diff.background.context}
|
||||
addedSignColor={themeV2.diff.highlight.added}
|
||||
removedSignColor={themeV2.diff.highlight.removed}
|
||||
lineNumberFg={themeV2.diff.lineNumber.text}
|
||||
lineNumberBg={themeV2.diff.background.context}
|
||||
addedLineNumberBg={themeV2.diff.lineNumber.background.added}
|
||||
removedLineNumberBg={themeV2.diff.lineNumber.background.removed}
|
||||
fg={theme.text.default}
|
||||
addedBg={theme.diff.background.added}
|
||||
removedBg={theme.diff.background.removed}
|
||||
contextBg={theme.diff.background.context}
|
||||
addedSignColor={theme.diff.highlight.added}
|
||||
removedSignColor={theme.diff.highlight.removed}
|
||||
lineNumberFg={theme.diff.lineNumber.text}
|
||||
lineNumberBg={theme.diff.background.context}
|
||||
addedLineNumberBg={theme.diff.lineNumber.background.added}
|
||||
removedLineNumberBg={theme.diff.lineNumber.background.removed}
|
||||
/>
|
||||
</box>
|
||||
</Show>
|
||||
|
|
@ -3114,7 +3124,7 @@ function ApplyPatch(props: ToolProps) {
|
|||
<FilePath
|
||||
value={file.resource}
|
||||
maxWidth={Math.max(2, ctx.width - 3)}
|
||||
fg={file.type === "delete" ? themeV2.diff.text.removed : themeV2.text.subdued}
|
||||
fg={file.type === "delete" ? theme.diff.text.removed : theme.text.subdued}
|
||||
/>
|
||||
</BlockTool>
|
||||
)}
|
||||
|
|
@ -3143,7 +3153,7 @@ function ApplyPatch(props: ToolProps) {
|
|||
}
|
||||
|
||||
function Question(props: ToolProps) {
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
const questions = createMemo(() => parseQuestions(props.input.questions))
|
||||
const answers = createMemo(() => parseQuestionAnswers(props.metadata.answers))
|
||||
const count = createMemo(() => questions().length)
|
||||
|
|
@ -3161,8 +3171,8 @@ function Question(props: ToolProps) {
|
|||
<For each={questions()}>
|
||||
{(q, i) => (
|
||||
<box flexDirection="column">
|
||||
<text fg={themeV2.text.subdued}>{q.question}</text>
|
||||
<text fg={themeV2.text.default}>{format(answers()?.[i()])}</text>
|
||||
<text fg={theme.text.subdued}>{q.question}</text>
|
||||
<text fg={theme.text.default}>{format(answers()?.[i()])}</text>
|
||||
</box>
|
||||
)}
|
||||
</For>
|
||||
|
|
@ -3188,7 +3198,7 @@ function Skill(props: ToolProps) {
|
|||
}
|
||||
|
||||
function Diagnostics(props: { diagnostics: unknown; filePath: string }) {
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
const terminalEnvironment = useTuiTerminalEnvironment()
|
||||
const errors = createMemo(() => {
|
||||
const normalized = normalizePath(
|
||||
|
|
@ -3203,7 +3213,7 @@ function Diagnostics(props: { diagnostics: unknown; filePath: string }) {
|
|||
<box>
|
||||
<For each={errors()}>
|
||||
{(diagnostic) => (
|
||||
<text fg={themeV2.text.feedback.error.default}>
|
||||
<text fg={theme.text.feedback.error.default}>
|
||||
Error [{diagnostic.range.start.line + 1}:{diagnostic.range.start.character + 1}] {diagnostic.message}
|
||||
</text>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { createStore } from "solid-js/store"
|
|||
import { createMemo, For, Match, Show, Switch } from "solid-js"
|
||||
import { Portal, useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid"
|
||||
import type { TextareaRenderable } from "@opentui/core"
|
||||
import { useTheme } from "../../context/theme"
|
||||
import { useTheme, useThemes } from "../../context/theme"
|
||||
import type { PermissionRequest } from "@opencode-ai/client"
|
||||
import { useClient } from "../../context/client"
|
||||
import { SplitBorder } from "../../ui/border"
|
||||
|
|
@ -18,9 +18,9 @@ import { SimulationSemantics } from "../../simulation/semantics"
|
|||
type PermissionStage = "permission" | "always" | "reject"
|
||||
|
||||
function EditBody(props: { file?: string; diff?: string; patch?: string }) {
|
||||
const themeState = useTheme()
|
||||
const themeV2 = themeState.themeV2
|
||||
const syntax = themeState.syntax
|
||||
const theme = useTheme()
|
||||
const themes = useThemes()
|
||||
const syntax = themes.currentSyntax
|
||||
const config = useConfig().data
|
||||
const dimensions = useTerminalDimensions()
|
||||
|
||||
|
|
@ -45,8 +45,8 @@ function EditBody(props: { file?: string; diff?: string; patch?: string }) {
|
|||
scrollAcceleration={scrollAcceleration()}
|
||||
verticalScrollbarOptions={{
|
||||
trackOptions: {
|
||||
backgroundColor: themeV2.background.default,
|
||||
foregroundColor: themeV2.scrollbar.default,
|
||||
backgroundColor: theme.background.default,
|
||||
foregroundColor: theme.scrollbar.default,
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
|
@ -58,16 +58,16 @@ function EditBody(props: { file?: string; diff?: string; patch?: string }) {
|
|||
showLineNumbers={true}
|
||||
width="100%"
|
||||
wrapMode="word"
|
||||
fg={themeV2.text.default}
|
||||
addedBg={themeV2.diff.background.added}
|
||||
removedBg={themeV2.diff.background.removed}
|
||||
contextBg={themeV2.diff.background.context}
|
||||
addedSignColor={themeV2.diff.highlight.added}
|
||||
removedSignColor={themeV2.diff.highlight.removed}
|
||||
lineNumberFg={themeV2.diff.lineNumber.text}
|
||||
lineNumberBg={themeV2.diff.background.context}
|
||||
addedLineNumberBg={themeV2.diff.lineNumber.background.added}
|
||||
removedLineNumberBg={themeV2.diff.lineNumber.background.removed}
|
||||
fg={theme.text.default}
|
||||
addedBg={theme.diff.background.added}
|
||||
removedBg={theme.diff.background.removed}
|
||||
contextBg={theme.diff.background.context}
|
||||
addedSignColor={theme.diff.highlight.added}
|
||||
removedSignColor={theme.diff.highlight.removed}
|
||||
lineNumberFg={theme.diff.lineNumber.text}
|
||||
lineNumberBg={theme.diff.background.context}
|
||||
addedLineNumberBg={theme.diff.lineNumber.background.added}
|
||||
removedLineNumberBg={theme.diff.lineNumber.background.removed}
|
||||
/>
|
||||
</scrollbox>
|
||||
</Show>
|
||||
|
|
@ -76,7 +76,7 @@ function EditBody(props: { file?: string; diff?: string; patch?: string }) {
|
|||
when={props.patch}
|
||||
fallback={
|
||||
<box paddingLeft={1}>
|
||||
<text fg={themeV2.text.subdued}>No diff provided</text>
|
||||
<text fg={theme.text.subdued}>No diff provided</text>
|
||||
</box>
|
||||
}
|
||||
>
|
||||
|
|
@ -86,8 +86,8 @@ function EditBody(props: { file?: string; diff?: string; patch?: string }) {
|
|||
scrollAcceleration={scrollAcceleration()}
|
||||
verticalScrollbarOptions={{
|
||||
trackOptions: {
|
||||
backgroundColor: themeV2.background.default,
|
||||
foregroundColor: themeV2.scrollbar.default,
|
||||
backgroundColor: theme.background.default,
|
||||
foregroundColor: theme.scrollbar.default,
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
|
@ -97,7 +97,7 @@ function EditBody(props: { file?: string; diff?: string; patch?: string }) {
|
|||
streaming={true}
|
||||
syntaxStyle={syntax()}
|
||||
content={patch()}
|
||||
fg={themeV2.text.subdued}
|
||||
fg={theme.text.subdued}
|
||||
/>
|
||||
</scrollbox>
|
||||
)}
|
||||
|
|
@ -128,7 +128,7 @@ export function PermissionPrompt(props: { request: PermissionRequest; directory?
|
|||
return { input: undefined, metadata: undefined }
|
||||
})
|
||||
|
||||
const { themeV2 } = useTheme()
|
||||
const theme = useTheme()
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
|
|
@ -140,7 +140,7 @@ export function PermissionPrompt(props: { request: PermissionRequest; directory?
|
|||
body={
|
||||
<box paddingLeft={1} gap={1}>
|
||||
<For each={permissionAlwaysLines(props.request)}>
|
||||
{(line, index) => <text fg={index() === 0 ? themeV2.text.subdued : themeV2.text.default}>{line}</text>}
|
||||
{(line, index) => <text fg={index() === 0 ? theme.text.subdued : theme.text.default}>{line}</text>}
|
||||
</For>
|
||||
</box>
|
||||
}
|
||||
|
|
@ -192,9 +192,9 @@ export function PermissionPrompt(props: { request: PermissionRequest; directory?
|
|||
) : props.request.action === "external_directory" ? (
|
||||
<Show when={current.lines.length > 0}>
|
||||
<box paddingLeft={1} gap={1}>
|
||||
<text fg={themeV2.text.subdued}>Patterns</text>
|
||||
<text fg={theme.text.subdued}>Patterns</text>
|
||||
<box>
|
||||
<For each={current.lines}>{(line) => <text fg={themeV2.text.default}>{line}</text>}</For>
|
||||
<For each={current.lines}>{(line) => <text fg={theme.text.default}>{line}</text>}</For>
|
||||
</box>
|
||||
</box>
|
||||
</Show>
|
||||
|
|
@ -207,8 +207,8 @@ export function PermissionPrompt(props: { request: PermissionRequest; directory?
|
|||
props.request.action === "shell" ||
|
||||
props.request.action === "subagent" ||
|
||||
props.request.action === "task"
|
||||
? themeV2.text.default
|
||||
: themeV2.text.subdued
|
||||
? theme.text.default
|
||||
: theme.text.subdued
|
||||
}
|
||||
>
|
||||
{line}
|
||||
|
|
@ -221,15 +221,15 @@ export function PermissionPrompt(props: { request: PermissionRequest; directory?
|
|||
const header = () => (
|
||||
<box flexDirection="column" gap={0}>
|
||||
<box flexDirection="row" gap={1} flexShrink={0}>
|
||||
<text fg={themeV2.text.feedback.warning.default}>{"△"}</text>
|
||||
<text fg={themeV2.text.default}>Permission required</text>
|
||||
<text fg={theme.text.feedback.warning.default}>{"△"}</text>
|
||||
<text fg={theme.text.default}>Permission required</text>
|
||||
</box>
|
||||
<Show when={props.request.action !== "shell" && current.title}>
|
||||
<box flexDirection="row" gap={1} paddingLeft={2} flexShrink={0}>
|
||||
<text fg={themeV2.text.subdued} flexShrink={0}>
|
||||
<text fg={theme.text.subdued} flexShrink={0}>
|
||||
{current.icon}
|
||||
</text>
|
||||
<text fg={themeV2.text.default}>{current.title}</text>
|
||||
<text fg={theme.text.default}>{current.title}</text>
|
||||
</box>
|
||||
</Show>
|
||||
</box>
|
||||
|
|
@ -297,7 +297,7 @@ function RejectPrompt(props: {
|
|||
onCancel: () => void
|
||||
}) {
|
||||
let input: TextareaRenderable
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const dimensions = useTerminalDimensions()
|
||||
const narrow = createMemo(() => dimensions().width < 80)
|
||||
Keymap.createLayer(() => ({
|
||||
|
|
@ -329,18 +329,18 @@ function RejectPrompt(props: {
|
|||
role: "dialog",
|
||||
label: `Reject permission: ${props.action}`,
|
||||
}))}
|
||||
backgroundColor={themeV2.background.default}
|
||||
backgroundColor={theme.background.default}
|
||||
border={["left"]}
|
||||
borderColor={themeV2.text.feedback.error.default}
|
||||
borderColor={theme.text.feedback.error.default}
|
||||
customBorderChars={SplitBorder.customBorderChars}
|
||||
>
|
||||
<box gap={1} paddingLeft={1} paddingRight={3} paddingTop={1} paddingBottom={1}>
|
||||
<box flexDirection="row" gap={1} paddingLeft={1}>
|
||||
<text fg={themeV2.text.feedback.error.default}>{"△"}</text>
|
||||
<text fg={themeV2.text.default}>Reject permission</text>
|
||||
<text fg={theme.text.feedback.error.default}>{"△"}</text>
|
||||
<text fg={theme.text.default}>Reject permission</text>
|
||||
</box>
|
||||
<box paddingLeft={1}>
|
||||
<text fg={themeV2.text.subdued}>Tell OpenCode what to do differently</text>
|
||||
<text fg={theme.text.subdued}>Tell OpenCode what to do differently</text>
|
||||
</box>
|
||||
</box>
|
||||
<box
|
||||
|
|
@ -350,7 +350,7 @@ function RejectPrompt(props: {
|
|||
paddingLeft={2}
|
||||
paddingRight={3}
|
||||
paddingBottom={1}
|
||||
backgroundColor={themeV2.raise(themeV2.background.default)}
|
||||
backgroundColor={theme.raise(theme.background.default)}
|
||||
justifyContent={narrow() ? "flex-start" : "space-between"}
|
||||
alignItems={narrow() ? "flex-start" : "center"}
|
||||
gap={1}
|
||||
|
|
@ -369,9 +369,9 @@ function RejectPrompt(props: {
|
|||
val.traits = { status: "REJECT" }
|
||||
}}
|
||||
focused
|
||||
textColor={themeV2.text.default}
|
||||
focusedTextColor={themeV2.text.default}
|
||||
cursorColor={themeV2.text.default}
|
||||
textColor={theme.text.default}
|
||||
focusedTextColor={theme.text.default}
|
||||
cursorColor={theme.text.default}
|
||||
/>
|
||||
<box
|
||||
id="session.permission.reject.actions"
|
||||
|
|
@ -394,8 +394,8 @@ function RejectPrompt(props: {
|
|||
}))}
|
||||
onMouseUp={() => props.onConfirm(input.plainText)}
|
||||
>
|
||||
<text fg={themeV2.text.default}>
|
||||
enter <span style={{ fg: themeV2.text.subdued }}>confirm</span>
|
||||
<text fg={theme.text.default}>
|
||||
enter <span style={{ fg: theme.text.subdued }}>confirm</span>
|
||||
</text>
|
||||
</box>
|
||||
<box
|
||||
|
|
@ -408,8 +408,8 @@ function RejectPrompt(props: {
|
|||
}))}
|
||||
onMouseUp={props.onCancel}
|
||||
>
|
||||
<text fg={themeV2.text.default}>
|
||||
esc <span style={{ fg: themeV2.text.subdued }}>cancel</span>
|
||||
<text fg={theme.text.default}>
|
||||
esc <span style={{ fg: theme.text.subdued }}>cancel</span>
|
||||
</text>
|
||||
</box>
|
||||
</box>
|
||||
|
|
@ -429,7 +429,7 @@ function Prompt<const T extends Record<string, string>>(props: {
|
|||
fullscreen?: boolean
|
||||
onSelect: (option: keyof T) => void
|
||||
}) {
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const dimensions = useTerminalDimensions()
|
||||
const keys = Object.keys(props.options) as (keyof T)[]
|
||||
const [store, setStore] = createStore({
|
||||
|
|
@ -534,9 +534,9 @@ function Prompt<const T extends Record<string, string>>(props: {
|
|||
label: props.semanticLabel ?? props.title,
|
||||
expanded: store.expanded,
|
||||
}))}
|
||||
backgroundColor={themeV2.background.default}
|
||||
backgroundColor={theme.background.default}
|
||||
border={["left"]}
|
||||
borderColor={themeV2.background.action.primary.focused}
|
||||
borderColor={theme.background.action.primary.focused}
|
||||
customBorderChars={SplitBorder.customBorderChars}
|
||||
{...(store.expanded
|
||||
? { top: dimensions().height * -1 + 1, bottom: 1, left: 2, right: 2, position: "absolute" }
|
||||
|
|
@ -554,8 +554,8 @@ function Prompt<const T extends Record<string, string>>(props: {
|
|||
when={props.header}
|
||||
fallback={
|
||||
<box flexDirection="row" gap={1} paddingLeft={1} flexShrink={0}>
|
||||
<text fg={themeV2.text.feedback.warning.default}>{"△"}</text>
|
||||
<text fg={themeV2.text.default}>{props.title}</text>
|
||||
<text fg={theme.text.feedback.warning.default}>{"△"}</text>
|
||||
<text fg={theme.text.default}>{props.title}</text>
|
||||
</box>
|
||||
}
|
||||
>
|
||||
|
|
@ -573,7 +573,7 @@ function Prompt<const T extends Record<string, string>>(props: {
|
|||
paddingLeft={2}
|
||||
paddingRight={3}
|
||||
paddingBottom={1}
|
||||
backgroundColor={themeV2.raise(themeV2.background.default)}
|
||||
backgroundColor={theme.raise(theme.background.default)}
|
||||
justifyContent={narrow() ? "flex-start" : "space-between"}
|
||||
alignItems={narrow() ? "flex-start" : "center"}
|
||||
>
|
||||
|
|
@ -604,8 +604,8 @@ function Prompt<const T extends Record<string, string>>(props: {
|
|||
paddingRight={1}
|
||||
backgroundColor={
|
||||
option === store.selected
|
||||
? themeV2.background.action.primary.focused
|
||||
: themeV2.background.action.primary.default
|
||||
? theme.background.action.primary.focused
|
||||
: theme.background.action.primary.default
|
||||
}
|
||||
onMouseOver={() => setStore("selected", option)}
|
||||
onMouseUp={() => {
|
||||
|
|
@ -614,11 +614,7 @@ function Prompt<const T extends Record<string, string>>(props: {
|
|||
}}
|
||||
>
|
||||
<text
|
||||
fg={
|
||||
option === store.selected
|
||||
? themeV2.text.action.primary.focused
|
||||
: themeV2.text.action.primary.default
|
||||
}
|
||||
fg={option === store.selected ? theme.text.action.primary.focused : theme.text.action.primary.default}
|
||||
>
|
||||
{props.options[option]}
|
||||
</text>
|
||||
|
|
@ -628,15 +624,15 @@ function Prompt<const T extends Record<string, string>>(props: {
|
|||
</box>
|
||||
<box flexDirection="row" gap={2} flexShrink={0}>
|
||||
<Show when={props.fullscreen}>
|
||||
<text fg={themeV2.text.default}>
|
||||
{shortcuts.get("permission.prompt.fullscreen")} <span style={{ fg: themeV2.text.subdued }}>{hint()}</span>
|
||||
<text fg={theme.text.default}>
|
||||
{shortcuts.get("permission.prompt.fullscreen")} <span style={{ fg: theme.text.subdued }}>{hint()}</span>
|
||||
</text>
|
||||
</Show>
|
||||
<text fg={themeV2.text.default}>
|
||||
{"⇆"} <span style={{ fg: themeV2.text.subdued }}>select</span>
|
||||
<text fg={theme.text.default}>
|
||||
{"⇆"} <span style={{ fg: theme.text.subdued }}>select</span>
|
||||
</text>
|
||||
<text fg={themeV2.text.default}>
|
||||
enter <span style={{ fg: themeV2.text.subdued }}>confirm</span>
|
||||
<text fg={theme.text.default}>
|
||||
enter <span style={{ fg: theme.text.subdued }}>confirm</span>
|
||||
</text>
|
||||
</box>
|
||||
</box>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useData } from "../../context/data"
|
||||
import { createMemo, Show } from "solid-js"
|
||||
import { useTheme } from "../../context/theme"
|
||||
import { useThemes } from "../../context/theme"
|
||||
import { useConfig } from "../../config"
|
||||
import { usePluginRuntime } from "../../plugin/runtime"
|
||||
import { PluginSlot } from "../../plugin/context"
|
||||
|
|
@ -10,7 +10,7 @@ import { getScrollAcceleration } from "../../util/scroll"
|
|||
export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
|
||||
const pluginRuntime = usePluginRuntime()
|
||||
const data = useData()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const config = useConfig().data
|
||||
const session = createMemo(() => data.session.get(props.sessionID))
|
||||
const scrollAcceleration = createMemo(() => getScrollAcceleration(config))
|
||||
|
|
@ -18,7 +18,7 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
|
|||
return (
|
||||
<Show when={session()}>
|
||||
<box
|
||||
backgroundColor={themeV2.background.default}
|
||||
backgroundColor={theme.background.default}
|
||||
width={42}
|
||||
height="100%"
|
||||
paddingTop={1}
|
||||
|
|
@ -32,8 +32,8 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
|
|||
scrollAcceleration={scrollAcceleration()}
|
||||
verticalScrollbarOptions={{
|
||||
trackOptions: {
|
||||
backgroundColor: themeV2.background.default,
|
||||
foregroundColor: themeV2.scrollbar.default,
|
||||
backgroundColor: theme.background.default,
|
||||
foregroundColor: theme.scrollbar.default,
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
|
@ -45,11 +45,11 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
|
|||
title={session()!.title}
|
||||
>
|
||||
<box paddingRight={1}>
|
||||
<text fg={themeV2.text.default}>
|
||||
<text fg={theme.text.default}>
|
||||
<b>{session()!.title}</b>
|
||||
</text>
|
||||
<Show when={session()!.location.workspaceID}>
|
||||
<text fg={themeV2.text.subdued}>{session()!.location.workspaceID}</text>
|
||||
<text fg={theme.text.subdued}>{session()!.location.workspaceID}</text>
|
||||
</Show>
|
||||
</box>
|
||||
</pluginRuntime.Slot>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { createMemo, createSignal, Show } from "solid-js"
|
||||
import { useRouteData } from "../../context/route"
|
||||
import { useData } from "../../context/data"
|
||||
import { useTheme } from "../../context/theme"
|
||||
import { useThemes } from "../../context/theme"
|
||||
import { SplitBorder } from "../../ui/border"
|
||||
import { Locale } from "../../util/locale"
|
||||
import { useTerminalDimensions } from "@opentui/solid"
|
||||
|
|
@ -42,7 +42,7 @@ export function SubagentFooter() {
|
|||
}
|
||||
})
|
||||
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const keymap = Keymap.use()
|
||||
const shortcuts = Keymap.useShortcuts()
|
||||
const [hover, setHover] = createSignal<"parent" | "prev" | "next" | null>(null)
|
||||
|
|
@ -57,18 +57,18 @@ export function SubagentFooter() {
|
|||
paddingRight={1}
|
||||
{...SplitBorder}
|
||||
border={["left"]}
|
||||
borderColor={themeV2.border.default}
|
||||
borderColor={theme.border.default}
|
||||
flexShrink={0}
|
||||
backgroundColor={themeV2.background.default}
|
||||
backgroundColor={theme.background.default}
|
||||
>
|
||||
<box flexDirection="row" justifyContent="space-between" gap={1}>
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text fg={themeV2.text.default}>
|
||||
<text fg={theme.text.default}>
|
||||
<b>{subagentInfo()}</b>
|
||||
</text>
|
||||
<Show when={usage()}>
|
||||
{(item) => (
|
||||
<text fg={themeV2.text.subdued} wrapMode="none">
|
||||
<text fg={theme.text.subdued} wrapMode="none">
|
||||
{[item().context, item().cost].filter(Boolean).join(" · ")}
|
||||
</text>
|
||||
)}
|
||||
|
|
@ -80,35 +80,31 @@ export function SubagentFooter() {
|
|||
onMouseOut={() => setHover(null)}
|
||||
onMouseUp={() => keymap.dispatch("session.parent")}
|
||||
backgroundColor={
|
||||
hover() === "parent" ? themeV2.background.action.primary.hovered : themeV2.background.default
|
||||
hover() === "parent" ? theme.background.action.primary.hovered : theme.background.default
|
||||
}
|
||||
>
|
||||
<text fg={themeV2.text.default}>
|
||||
Parent <span style={{ fg: themeV2.text.subdued }}>{shortcuts.get("session.parent")}</span>
|
||||
<text fg={theme.text.default}>
|
||||
Parent <span style={{ fg: theme.text.subdued }}>{shortcuts.get("session.parent")}</span>
|
||||
</text>
|
||||
</box>
|
||||
<box
|
||||
onMouseOver={() => setHover("prev")}
|
||||
onMouseOut={() => setHover(null)}
|
||||
onMouseUp={() => keymap.dispatch("session.child.previous")}
|
||||
backgroundColor={
|
||||
hover() === "prev" ? themeV2.background.action.primary.hovered : themeV2.background.default
|
||||
}
|
||||
backgroundColor={hover() === "prev" ? theme.background.action.primary.hovered : theme.background.default}
|
||||
>
|
||||
<text fg={themeV2.text.default}>
|
||||
Prev <span style={{ fg: themeV2.text.subdued }}>{shortcuts.get("session.child.previous")}</span>
|
||||
<text fg={theme.text.default}>
|
||||
Prev <span style={{ fg: theme.text.subdued }}>{shortcuts.get("session.child.previous")}</span>
|
||||
</text>
|
||||
</box>
|
||||
<box
|
||||
onMouseOver={() => setHover("next")}
|
||||
onMouseOut={() => setHover(null)}
|
||||
onMouseUp={() => keymap.dispatch("session.child.next")}
|
||||
backgroundColor={
|
||||
hover() === "next" ? themeV2.background.action.primary.hovered : themeV2.background.default
|
||||
}
|
||||
backgroundColor={hover() === "next" ? theme.background.action.primary.hovered : theme.background.default}
|
||||
>
|
||||
<text fg={themeV2.text.default}>
|
||||
Next <span style={{ fg: themeV2.text.subdued }}>{shortcuts.get("session.child.next")}</span>
|
||||
<text fg={theme.text.default}>
|
||||
Next <span style={{ fg: theme.text.subdued }}>{shortcuts.get("session.child.next")}</span>
|
||||
</text>
|
||||
</box>
|
||||
</box>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { TextAttributes } from "@opentui/core"
|
||||
import { Keymap } from "../context/keymap"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { useDialog, type DialogContext } from "./dialog"
|
||||
|
||||
export type DialogAlertProps = {
|
||||
|
|
@ -11,7 +11,7 @@ export type DialogAlertProps = {
|
|||
|
||||
export function DialogAlert(props: DialogAlertProps) {
|
||||
const dialog = useDialog()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
|
||||
Keymap.createLayer(() => ({
|
||||
mode: "modal",
|
||||
|
|
@ -30,27 +30,27 @@ export function DialogAlert(props: DialogAlertProps) {
|
|||
return (
|
||||
<box paddingLeft={2} paddingRight={2} gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text.default}>
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text.default}>
|
||||
{props.title}
|
||||
</text>
|
||||
<text fg={themeV2.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={theme.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
<box paddingBottom={1}>
|
||||
<text fg={themeV2.text.subdued}>{props.message}</text>
|
||||
<text fg={theme.text.subdued}>{props.message}</text>
|
||||
</box>
|
||||
<box flexDirection="row" justifyContent="flex-end" paddingBottom={1}>
|
||||
<box
|
||||
paddingLeft={3}
|
||||
paddingRight={3}
|
||||
backgroundColor={themeV2.background.action.primary.focused}
|
||||
backgroundColor={theme.background.action.primary.focused}
|
||||
onMouseUp={() => {
|
||||
props.onConfirm?.()
|
||||
dialog.clear()
|
||||
}}
|
||||
>
|
||||
<text fg={themeV2.text.action.primary.focused}>ok</text>
|
||||
<text fg={theme.text.action.primary.focused}>ok</text>
|
||||
</box>
|
||||
</box>
|
||||
</box>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { TextAttributes } from "@opentui/core"
|
||||
import { Keymap } from "../context/keymap"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { useDialog, type DialogContext } from "./dialog"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { For } from "solid-js"
|
||||
|
|
@ -21,7 +21,7 @@ export type DialogConfirmResult = boolean | undefined
|
|||
|
||||
export function DialogConfirm(props: DialogConfirmProps) {
|
||||
const dialog = useDialog()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const [store, setStore] = createStore({
|
||||
active: "confirm" as "confirm" | "cancel",
|
||||
})
|
||||
|
|
@ -60,15 +60,15 @@ export function DialogConfirm(props: DialogConfirmProps) {
|
|||
return (
|
||||
<box paddingLeft={2} paddingRight={2} gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text.default}>
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text.default}>
|
||||
{props.title}
|
||||
</text>
|
||||
<text fg={themeV2.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={theme.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
<box paddingBottom={1}>
|
||||
<text fg={themeV2.text.subdued}>{props.message}</text>
|
||||
<text fg={theme.text.subdued}>{props.message}</text>
|
||||
</box>
|
||||
<box flexDirection="row" justifyContent="flex-end" paddingBottom={1}>
|
||||
<For each={["cancel", "confirm"] as const}>
|
||||
|
|
@ -76,14 +76,14 @@ export function DialogConfirm(props: DialogConfirmProps) {
|
|||
<box
|
||||
paddingLeft={1}
|
||||
paddingRight={1}
|
||||
backgroundColor={key === store.active ? themeV2.background.action.primary.focused : undefined}
|
||||
backgroundColor={key === store.active ? theme.background.action.primary.focused : undefined}
|
||||
onMouseUp={() => {
|
||||
if (key === "confirm") props.onConfirm?.()
|
||||
if (key === "cancel") props.onCancel?.()
|
||||
dialog.clear()
|
||||
}}
|
||||
>
|
||||
<text fg={key === store.active ? themeV2.text.action.primary.focused : themeV2.text.subdued}>
|
||||
<text fg={key === store.active ? theme.text.action.primary.focused : theme.text.subdued}>
|
||||
{Locale.titlecase(props.label?.[key] ?? key)}
|
||||
</text>
|
||||
</box>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { TextAttributes } from "@opentui/core"
|
||||
import { Keymap } from "../context/keymap"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { useDialog, type DialogContext } from "./dialog"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { For, Show } from "solid-js"
|
||||
|
|
@ -17,8 +17,8 @@ type Active = ExportFormat | "thinking" | "copy" | "export"
|
|||
|
||||
export function DialogExportOptions(props: DialogExportOptionsProps) {
|
||||
const dialog = useDialog()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const { themeV2: overlayTheme } = useTheme().contextual("overlay")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const overlayTheme = useThemes().contextual("overlay")
|
||||
const [store, setStore] = createStore({
|
||||
format: "markdown" as ExportFormat,
|
||||
thinking: props.defaultThinking,
|
||||
|
|
@ -73,15 +73,15 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
|
|||
return (
|
||||
<box paddingLeft={2} paddingRight={2} gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text.default}>
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text.default}>
|
||||
Export session
|
||||
</text>
|
||||
<text fg={themeV2.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={theme.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text fg={themeV2.text.default}>Export as:</text>
|
||||
<text fg={theme.text.default}>Export as:</text>
|
||||
<box flexDirection="row" gap={1}>
|
||||
<For each={["markdown", "json"] as const}>
|
||||
{(format) => (
|
||||
|
|
@ -90,20 +90,20 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
|
|||
paddingRight={1}
|
||||
backgroundColor={
|
||||
store.active === format
|
||||
? themeV2.background.formfield.focused
|
||||
? theme.background.formfield.focused
|
||||
: store.format === format
|
||||
? themeV2.background.formfield.selected
|
||||
: themeV2.background.formfield.default
|
||||
? theme.background.formfield.selected
|
||||
: theme.background.formfield.default
|
||||
}
|
||||
onMouseUp={() => selectFormat(format)}
|
||||
>
|
||||
<text
|
||||
fg={
|
||||
store.active === format
|
||||
? themeV2.text.formfield.focused
|
||||
? theme.text.formfield.focused
|
||||
: store.format === format
|
||||
? themeV2.text.formfield.selected
|
||||
: themeV2.text.formfield.default
|
||||
? theme.text.formfield.selected
|
||||
: theme.text.formfield.default
|
||||
}
|
||||
>
|
||||
{store.format === format ? "◉" : "○"} {format === "markdown" ? "Markdown" : "JSON"}
|
||||
|
|
@ -119,10 +119,10 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
|
|||
gap={1}
|
||||
backgroundColor={
|
||||
store.active === "thinking"
|
||||
? themeV2.background.formfield.focused
|
||||
? theme.background.formfield.focused
|
||||
: store.thinking
|
||||
? themeV2.background.formfield.selected
|
||||
: themeV2.background.formfield.default
|
||||
? theme.background.formfield.selected
|
||||
: theme.background.formfield.default
|
||||
}
|
||||
onMouseUp={() => {
|
||||
setStore("active", "thinking")
|
||||
|
|
@ -132,10 +132,10 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
|
|||
<text
|
||||
fg={
|
||||
store.active === "thinking"
|
||||
? themeV2.text.formfield.focused
|
||||
? theme.text.formfield.focused
|
||||
: store.thinking
|
||||
? themeV2.text.formfield.selected
|
||||
: themeV2.text.formfield.default
|
||||
? theme.text.formfield.selected
|
||||
: theme.text.formfield.default
|
||||
}
|
||||
>
|
||||
{store.thinking ? "[x]" : "[ ]"}
|
||||
|
|
@ -143,10 +143,10 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
|
|||
<text
|
||||
fg={
|
||||
store.active === "thinking"
|
||||
? themeV2.text.formfield.focused
|
||||
? theme.text.formfield.focused
|
||||
: store.thinking
|
||||
? themeV2.text.formfield.selected
|
||||
: themeV2.text.formfield.default
|
||||
? theme.text.formfield.selected
|
||||
: theme.text.formfield.default
|
||||
}
|
||||
>
|
||||
Include thinking
|
||||
|
|
@ -167,14 +167,12 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
|
|||
paddingRight={4}
|
||||
backgroundColor={
|
||||
store.active === "export"
|
||||
? themeV2.background.action.primary.focused
|
||||
: themeV2.background.action.primary.default
|
||||
? theme.background.action.primary.focused
|
||||
: theme.background.action.primary.default
|
||||
}
|
||||
onMouseUp={() => confirm("export")}
|
||||
>
|
||||
<text
|
||||
fg={store.active === "export" ? themeV2.text.action.primary.focused : themeV2.text.action.primary.default}
|
||||
>
|
||||
<text fg={store.active === "export" ? theme.text.action.primary.focused : theme.text.action.primary.default}>
|
||||
Export
|
||||
</text>
|
||||
</box>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { TextAttributes } from "@opentui/core"
|
||||
import { Keymap } from "../context/keymap"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { useDialog, type DialogContext } from "./dialog"
|
||||
|
||||
export function DialogExportResult(props: { path: string; onClose?: () => void }) {
|
||||
const dialog = useDialog()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
|
||||
const close = () => {
|
||||
props.onClose?.()
|
||||
|
|
@ -27,24 +27,24 @@ export function DialogExportResult(props: { path: string; onClose?: () => void }
|
|||
return (
|
||||
<box paddingLeft={2} paddingRight={2} gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text.default}>
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text.default}>
|
||||
Session exported
|
||||
</text>
|
||||
<text fg={themeV2.text.subdued} onMouseUp={close}>
|
||||
<text fg={theme.text.subdued} onMouseUp={close}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
<box>
|
||||
<text fg={themeV2.text.default}>{props.path}</text>
|
||||
<text fg={theme.text.default}>{props.path}</text>
|
||||
</box>
|
||||
<box flexDirection="row" justifyContent="flex-end" gap={1} paddingBottom={1}>
|
||||
<box
|
||||
paddingLeft={3}
|
||||
paddingRight={3}
|
||||
backgroundColor={themeV2.background.action.primary.focused}
|
||||
backgroundColor={theme.background.action.primary.focused}
|
||||
onMouseUp={close}
|
||||
>
|
||||
<text fg={themeV2.text.action.primary.focused}>Close</text>
|
||||
<text fg={theme.text.action.primary.focused}>Close</text>
|
||||
</box>
|
||||
</box>
|
||||
</box>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { TextAttributes } from "@opentui/core"
|
||||
import { Keymap } from "../context/keymap"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { useDialog } from "./dialog"
|
||||
|
||||
export function DialogHelp() {
|
||||
const dialog = useDialog()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const shortcuts = Keymap.useShortcuts()
|
||||
|
||||
Keymap.createLayer(() => ({
|
||||
|
|
@ -19,15 +19,15 @@ export function DialogHelp() {
|
|||
return (
|
||||
<box paddingLeft={2} paddingRight={2} gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text.default}>
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text.default}>
|
||||
Help
|
||||
</text>
|
||||
<text fg={themeV2.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={theme.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
esc/enter
|
||||
</text>
|
||||
</box>
|
||||
<box paddingBottom={1}>
|
||||
<text fg={themeV2.text.subdued}>
|
||||
<text fg={theme.text.subdued}>
|
||||
Press {shortcuts.get("command.palette.show")} to see all available actions and commands in any context.
|
||||
</text>
|
||||
</box>
|
||||
|
|
@ -35,10 +35,10 @@ export function DialogHelp() {
|
|||
<box
|
||||
paddingLeft={3}
|
||||
paddingRight={3}
|
||||
backgroundColor={themeV2.background.action.primary.focused}
|
||||
backgroundColor={theme.background.action.primary.focused}
|
||||
onMouseUp={() => dialog.clear()}
|
||||
>
|
||||
<text fg={themeV2.text.action.primary.focused}>ok</text>
|
||||
<text fg={theme.text.action.primary.focused}>ok</text>
|
||||
</box>
|
||||
</box>
|
||||
</box>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { TextareaRenderable, TextAttributes } from "@opentui/core"
|
||||
import { Keymap } from "../context/keymap"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { useDialog, type DialogContext } from "./dialog"
|
||||
import { Show, createEffect, createSignal, onMount, type JSX } from "solid-js"
|
||||
import { Spinner } from "../component/spinner"
|
||||
|
|
@ -18,7 +18,7 @@ export type DialogPromptProps = {
|
|||
|
||||
export function DialogPrompt(props: DialogPromptProps) {
|
||||
const dialog = useDialog()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const shortcuts = Keymap.useShortcuts()
|
||||
const [textareaTarget, setTextareaTarget] = createSignal<TextareaRenderable>()
|
||||
let textarea: TextareaRenderable
|
||||
|
|
@ -74,10 +74,10 @@ export function DialogPrompt(props: DialogPromptProps) {
|
|||
return (
|
||||
<box paddingLeft={2} paddingRight={2} gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text.default}>
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text.default}>
|
||||
{props.title}
|
||||
</text>
|
||||
<text fg={themeV2.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={theme.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
|
|
@ -91,20 +91,20 @@ export function DialogPrompt(props: DialogPromptProps) {
|
|||
}}
|
||||
initialValue={props.value}
|
||||
placeholder={props.placeholder ?? "Enter text"}
|
||||
placeholderColor={themeV2.text.subdued}
|
||||
textColor={props.busy ? themeV2.text.formfield.disabled : themeV2.text.formfield.default}
|
||||
focusedTextColor={props.busy ? themeV2.text.formfield.disabled : themeV2.text.formfield.default}
|
||||
cursorColor={props.busy ? themeV2.background.formfield.disabled : themeV2.text.default}
|
||||
placeholderColor={theme.text.subdued}
|
||||
textColor={props.busy ? theme.text.formfield.disabled : theme.text.formfield.default}
|
||||
focusedTextColor={props.busy ? theme.text.formfield.disabled : theme.text.formfield.default}
|
||||
cursorColor={props.busy ? theme.background.formfield.disabled : theme.text.default}
|
||||
/>
|
||||
<Show when={props.busy}>
|
||||
<Spinner color={themeV2.text.subdued}>{props.busyText ?? "Working..."}</Spinner>
|
||||
<Spinner color={theme.text.subdued}>{props.busyText ?? "Working..."}</Spinner>
|
||||
</Show>
|
||||
</box>
|
||||
<box paddingBottom={1} gap={1} flexDirection="row">
|
||||
<Show when={!props.busy} fallback={<text fg={themeV2.text.subdued}>processing...</text>}>
|
||||
<Show when={!props.busy} fallback={<text fg={theme.text.subdued}>processing...</text>}>
|
||||
<Show when={shortcuts.get("dialog.prompt.submit")}>
|
||||
<text fg={themeV2.text.default}>
|
||||
{shortcuts.get("dialog.prompt.submit")} <span style={{ fg: themeV2.text.subdued }}>submit</span>
|
||||
<text fg={theme.text.default}>
|
||||
{shortcuts.get("dialog.prompt.submit")} <span style={{ fg: theme.text.subdued }}>submit</span>
|
||||
</text>
|
||||
</Show>
|
||||
</Show>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { InputRenderable, RGBA, ScrollBoxRenderable, TextAttributes } from "@opentui/core"
|
||||
import { Keymap, type KeymapCommand } from "../context/keymap"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { entries, filter, flatMap, groupBy, pipe } from "remeda"
|
||||
import { batch, createEffect, createMemo, createSignal, For, Show, type JSX, on, onCleanup } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
|
|
@ -95,7 +95,9 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||
type VisibleAction = (Action & { label: string }) | FooterHint
|
||||
|
||||
const dialog = useDialog()
|
||||
const { themeV2, mode } = useTheme().contextual("elevated")
|
||||
const themes = useThemes()
|
||||
const theme = themes.contextual("elevated")
|
||||
const mode = themes.mode
|
||||
const config = useConfig().data
|
||||
const scrollAcceleration = createMemo(() => getScrollAcceleration(config))
|
||||
|
||||
|
|
@ -522,10 +524,10 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||
if (!isActionItem(action.item))
|
||||
return (
|
||||
<text>
|
||||
<span style={{ fg: themeV2.text.default }}>
|
||||
<span style={{ fg: theme.text.default }}>
|
||||
<b>{action.item.title}</b>{" "}
|
||||
</span>
|
||||
<span style={{ fg: themeV2.text.subdued }}>{action.item.label}</span>
|
||||
<span style={{ fg: theme.text.subdued }}>{action.item.label}</span>
|
||||
</text>
|
||||
)
|
||||
const item = action.item
|
||||
|
|
@ -534,16 +536,16 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||
return (
|
||||
<box
|
||||
flexDirection="row"
|
||||
backgroundColor={active() ? themeV2.background.action.primary.focused : RGBA.fromInts(0, 0, 0, 0)}
|
||||
backgroundColor={active() ? theme.background.action.primary.focused : RGBA.fromInts(0, 0, 0, 0)}
|
||||
onMouseUp={() => trigger(item)}
|
||||
>
|
||||
<text
|
||||
fg={
|
||||
disabled()
|
||||
? themeV2.text.action.primary.disabled
|
||||
? theme.text.action.primary.disabled
|
||||
: active()
|
||||
? themeV2.text.action.primary.focused
|
||||
: themeV2.text.default
|
||||
? theme.text.action.primary.focused
|
||||
: theme.text.default
|
||||
}
|
||||
attributes={active() ? TextAttributes.BOLD : undefined}
|
||||
>
|
||||
|
|
@ -552,10 +554,10 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||
<text
|
||||
fg={
|
||||
disabled()
|
||||
? themeV2.text.action.primary.disabled
|
||||
? theme.text.action.primary.disabled
|
||||
: active()
|
||||
? themeV2.text.action.primary.focused
|
||||
: themeV2.text.subdued
|
||||
? theme.text.action.primary.focused
|
||||
: theme.text.subdued
|
||||
}
|
||||
>
|
||||
{" " + item.label}
|
||||
|
|
@ -569,11 +571,11 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||
<box paddingLeft={4} paddingRight={4}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
{props.titleView ?? (
|
||||
<text fg={themeV2.text.default} attributes={TextAttributes.BOLD}>
|
||||
<text fg={theme.text.default} attributes={TextAttributes.BOLD}>
|
||||
{props.title}
|
||||
</text>
|
||||
)}
|
||||
<text fg={themeV2.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={theme.text.subdued} onMouseUp={() => dialog.clear()}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
|
|
@ -587,9 +589,9 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||
props.onFilter?.(e)
|
||||
})
|
||||
}}
|
||||
focusedBackgroundColor={themeV2.background.formfield.focused}
|
||||
cursorColor={themeV2.text.formfield.focused}
|
||||
focusedTextColor={themeV2.text.formfield.focused}
|
||||
focusedBackgroundColor={theme.background.formfield.focused}
|
||||
cursorColor={theme.text.formfield.focused}
|
||||
focusedTextColor={theme.text.formfield.focused}
|
||||
ref={(r) => {
|
||||
input = r
|
||||
input.traits = { status: "FILTER" }
|
||||
|
|
@ -600,7 +602,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||
}, 1)
|
||||
}}
|
||||
placeholder={props.placeholder ?? "Search"}
|
||||
placeholderColor={themeV2.text.subdued}
|
||||
placeholderColor={theme.text.subdued}
|
||||
/>
|
||||
</box>
|
||||
</Show>
|
||||
|
|
@ -614,14 +616,14 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||
fallback={
|
||||
props.emptyView ?? (
|
||||
<box paddingLeft={4} paddingRight={4} paddingTop={1}>
|
||||
<text fg={themeV2.text.subdued}>No items available</text>
|
||||
<text fg={theme.text.subdued}>No items available</text>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
>
|
||||
{props.noMatchView ?? (
|
||||
<box paddingLeft={4} paddingRight={4} paddingTop={1}>
|
||||
<text fg={themeV2.text.subdued}>No results found</text>
|
||||
<text fg={theme.text.subdued}>No results found</text>
|
||||
</box>
|
||||
)}
|
||||
</Show>
|
||||
|
|
@ -643,10 +645,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||
<Show
|
||||
when={options[0]?.categoryView}
|
||||
fallback={
|
||||
<text
|
||||
fg={themeV2.hue.accent[mode() === "light" ? 800 : 200]}
|
||||
attributes={TextAttributes.BOLD}
|
||||
>
|
||||
<text fg={theme.hue.accent[mode() === "light" ? 800 : 200]} attributes={TextAttributes.BOLD}>
|
||||
{category}
|
||||
</text>
|
||||
}
|
||||
|
|
@ -695,8 +694,8 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||
backgroundColor={
|
||||
active()
|
||||
? actionFocused()
|
||||
? themeV2.background.surface.overlay
|
||||
: (option.bg ?? themeV2.background.action.primary.focused)
|
||||
? theme.background.surface.overlay
|
||||
: (option.bg ?? theme.background.action.primary.focused)
|
||||
: RGBA.fromInts(0, 0, 0, 0)
|
||||
}
|
||||
>
|
||||
|
|
@ -725,7 +724,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||
{(detail) => (
|
||||
<box paddingLeft={3} paddingRight={3}>
|
||||
<text
|
||||
fg={option.detailsColor ?? themeV2.text.subdued}
|
||||
fg={option.detailsColor ?? theme.text.subdued}
|
||||
wrapMode={option.detailsWrap ? "word" : "none"}
|
||||
>
|
||||
{option.detailsWrap
|
||||
|
|
@ -774,12 +773,12 @@ function Option(props: {
|
|||
activeColor?: RGBA
|
||||
onMouseOver?: () => void
|
||||
}) {
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const text = createMemo(() => {
|
||||
if (props.active && !props.muted) return props.activeColor ?? themeV2.text.action.primary.focused
|
||||
if (props.muted && (props.active || props.current)) return themeV2.text.subdued
|
||||
if (props.current) return themeV2.text.formfield.selected
|
||||
return themeV2.text.default
|
||||
if (props.active && !props.muted) return props.activeColor ?? theme.text.action.primary.focused
|
||||
if (props.muted && (props.active || props.current)) return theme.text.subdued
|
||||
if (props.current) return theme.text.formfield.selected
|
||||
return theme.text.default
|
||||
})
|
||||
|
||||
return (
|
||||
|
|
@ -809,14 +808,14 @@ function Option(props: {
|
|||
? Locale.truncateLeft(props.title, props.titleWidth ?? 61)
|
||||
: Locale.truncate(props.title, props.titleWidth ?? 61))}
|
||||
<Show when={props.description}>
|
||||
<span style={{ fg: props.active && !props.muted ? text() : themeV2.text.subdued }}>
|
||||
<span style={{ fg: props.active && !props.muted ? text() : theme.text.subdued }}>
|
||||
{" " + props.description}
|
||||
</span>
|
||||
</Show>
|
||||
</text>
|
||||
<Show when={props.footer}>
|
||||
<box flexShrink={0}>
|
||||
<text fg={props.active && !props.muted ? text() : themeV2.text.subdued}>{props.footer}</text>
|
||||
<text fg={props.active && !props.muted ? text() : theme.text.subdued}>{props.footer}</text>
|
||||
</box>
|
||||
</Show>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useRenderer, useTerminalDimensions } from "@opentui/solid"
|
||||
import { batch, createContext, createEffect, onCleanup, Show, useContext, type JSX, type ParentProps } from "solid-js"
|
||||
import { Keymap } from "../context/keymap"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { MouseButton, Renderable, RGBA } from "@opentui/core"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useToast } from "./toast"
|
||||
|
|
@ -16,7 +16,7 @@ export function Dialog(
|
|||
}>,
|
||||
) {
|
||||
const dimensions = useTerminalDimensions()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const theme = useThemes().contextual("elevated")
|
||||
const renderer = useRenderer()
|
||||
|
||||
let dismiss = false
|
||||
|
|
@ -59,7 +59,7 @@ export function Dialog(
|
|||
}}
|
||||
width={width()}
|
||||
maxWidth={dimensions().width - 2}
|
||||
backgroundColor={themeV2.background.default}
|
||||
backgroundColor={theme.background.default}
|
||||
paddingTop={1}
|
||||
>
|
||||
{props.children}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { createContext, useContext, type ParentProps, Show } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useThemes } from "../context/theme"
|
||||
import { useTerminalDimensions } from "@opentui/solid"
|
||||
import { SplitBorder } from "./border"
|
||||
import { TextAttributes } from "@opentui/core"
|
||||
|
|
@ -14,7 +14,7 @@ type ToastInput = Omit<ToastOptions, "duration"> & { duration?: number }
|
|||
|
||||
export function Toast() {
|
||||
const toast = useToast()
|
||||
const { themeV2 } = useTheme().contextual("overlay")
|
||||
const theme = useThemes().contextual("overlay")
|
||||
const dimensions = useTerminalDimensions()
|
||||
|
||||
return (
|
||||
|
|
@ -31,17 +31,17 @@ export function Toast() {
|
|||
paddingRight={2}
|
||||
paddingTop={1}
|
||||
paddingBottom={1}
|
||||
backgroundColor={themeV2.background.default}
|
||||
borderColor={themeV2.text.feedback[current().variant].default}
|
||||
backgroundColor={theme.background.default}
|
||||
borderColor={theme.text.feedback[current().variant].default}
|
||||
border={["left", "right"]}
|
||||
customBorderChars={SplitBorder.customBorderChars}
|
||||
>
|
||||
<Show when={current().title}>
|
||||
<text attributes={TextAttributes.BOLD} marginBottom={1} fg={themeV2.text.default}>
|
||||
<text attributes={TextAttributes.BOLD} marginBottom={1} fg={theme.text.default}>
|
||||
{current().title}
|
||||
</text>
|
||||
</Show>
|
||||
<text fg={themeV2.text.default} wrapMode="word" width="100%">
|
||||
<text fg={theme.text.default} wrapMode="word" width="100%">
|
||||
{current().message}
|
||||
</text>
|
||||
</box>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { testRender } from "@opentui/solid"
|
|||
import type { JSX } from "solid-js"
|
||||
import { onMount, type ParentProps } from "solid-js"
|
||||
import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
|
||||
import { ThemeProvider, useTheme } from "../../../src/context/theme"
|
||||
import { ThemeProvider, useTheme, useThemes } from "../../../src/context/theme"
|
||||
import type { Plugin } from "@opencode-ai/plugin/tui"
|
||||
import { ConfigProvider } from "../../../src/config"
|
||||
import {
|
||||
|
|
@ -130,7 +130,7 @@ describe("DiffViewerFileTree", () => {
|
|||
})
|
||||
|
||||
function ThemedDiffViewerFileTree(props: Omit<DiffViewerFileTreeProps, "context">) {
|
||||
return <DiffViewerFileTree {...props} context={{ theme: createPluginTheme(useTheme()) } as Plugin.Context} />
|
||||
return <DiffViewerFileTree {...props} context={{ theme: createPluginTheme(useTheme(), useThemes()) } as Plugin.Context} />
|
||||
}
|
||||
|
||||
async function renderFrame(component: () => JSX.Element) {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import type {
|
|||
Route,
|
||||
Slot,
|
||||
} from "@opencode-ai/plugin/tui/context"
|
||||
import { ThemeProvider, useTheme } from "../../../src/context/theme"
|
||||
import { ThemeProvider, useTheme, useThemes } from "../../../src/context/theme"
|
||||
import { ConfigProvider } from "../../../src/config"
|
||||
import { TuiKeybind } from "../../../src/config/keybind"
|
||||
import { Keymap } from "../../../src/context/keymap"
|
||||
|
|
@ -207,7 +207,7 @@ async function renderDiffViewer(vcsDiff: unknown[], height = 20, initialRoute?:
|
|||
|
||||
void diffViewerPlugin.setup(context)
|
||||
function Content() {
|
||||
theme = createPluginTheme(useTheme())
|
||||
theme = createPluginTheme(useTheme(), useThemes())
|
||||
const commandView = renderCommands?.({})
|
||||
if (current.type !== "plugin") commands.get("diff.open")?.run()
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { DEFAULT_THEME, selectTheme } from "@opencode-ai/theme/tui"
|
|||
import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
|
||||
import { DEFAULT_THEMES } from "../../../src/theme"
|
||||
import { ConfigProvider } from "../../../src/config"
|
||||
import { ThemeProvider, useTheme, type ThemeError } from "../../../src/context/theme"
|
||||
import { ThemeContextProvider, ThemeProvider, useTheme, useThemes, type ThemeError } from "../../../src/context/theme"
|
||||
|
||||
async function wait(fn: () => boolean) {
|
||||
const started = Date.now()
|
||||
|
|
@ -27,17 +27,17 @@ test("uses an available mode while retaining the pinned preference", async () =>
|
|||
darkOnly.theme.background = "#111111"
|
||||
darkOnly.theme.text = "#eeeeee"
|
||||
const native = { version: 2, dark: { text: { default: "#abcdef" } } } as const
|
||||
let theme: ReturnType<typeof useTheme> | undefined
|
||||
let themes: ReturnType<typeof useThemes> | undefined
|
||||
|
||||
function Probe() {
|
||||
const value = useTheme()
|
||||
theme = value
|
||||
const value = useThemes()
|
||||
themes = value
|
||||
return <text>{value.mode()}</text>
|
||||
}
|
||||
|
||||
function current() {
|
||||
if (!theme) throw new Error("Theme provider is not mounted")
|
||||
return theme
|
||||
if (!themes) throw new Error("Theme provider is not mounted")
|
||||
return themes
|
||||
}
|
||||
|
||||
const app = await testRender(
|
||||
|
|
@ -56,7 +56,7 @@ test("uses an available mode while retaining the pinned preference", async () =>
|
|||
app.renderer.start()
|
||||
|
||||
try {
|
||||
await wait(() => theme?.ready === true)
|
||||
await wait(() => themes?.ready === true)
|
||||
expect(current().mode()).toBe("light")
|
||||
expect(current().modes()).toEqual(["light"])
|
||||
expect(current().supports("dark")).toBeFalse()
|
||||
|
|
@ -72,7 +72,7 @@ test("uses an available mode while retaining the pinned preference", async () =>
|
|||
expect(current().set("native")).toBeTrue()
|
||||
await wait(() => current().selected === "native")
|
||||
expect(current().modes()).toEqual(["dark"])
|
||||
expect(current().themeV2.text.default.equals(RGBA.fromHex("#abcdef"))).toBeTrue()
|
||||
expect(current().current.text.default.equals(RGBA.fromHex("#abcdef"))).toBeTrue()
|
||||
} finally {
|
||||
app.renderer.destroy()
|
||||
}
|
||||
|
|
@ -83,13 +83,13 @@ test.each([
|
|||
["mode merging", { version: 2, light: { mergeMode: true } }],
|
||||
["token reference", { version: 2, light: { text: { default: "$missing" } } }],
|
||||
] as const)("falls back to OpenCode when configured V2 theme %s is invalid", async (_label, source) => {
|
||||
let theme: ReturnType<typeof useTheme> | undefined
|
||||
let themes: ReturnType<typeof useThemes> | undefined
|
||||
let failure: ThemeError | undefined
|
||||
let unsubscribe: (() => void) | undefined
|
||||
|
||||
function Probe() {
|
||||
const value = useTheme()
|
||||
theme = value
|
||||
const value = useThemes()
|
||||
themes = value
|
||||
unsubscribe = value.onError((error) => (failure = error))
|
||||
return <text>{value.selected}</text>
|
||||
}
|
||||
|
|
@ -107,8 +107,8 @@ test.each([
|
|||
app.renderer.start()
|
||||
|
||||
try {
|
||||
await wait(() => theme?.ready === true)
|
||||
expect(theme?.selected).toBe("opencode")
|
||||
await wait(() => themes?.ready === true)
|
||||
expect(themes?.selected).toBe("opencode")
|
||||
expect(failure?.name).toBe("invalid")
|
||||
expect(failure?.error).toBeInstanceOf(Error)
|
||||
expect(failure?.error.message.length).toBeGreaterThan(0)
|
||||
|
|
@ -118,17 +118,30 @@ test.each([
|
|||
}
|
||||
})
|
||||
|
||||
test("contextual themes fall back to a standalone theme's base view", async () => {
|
||||
test("contextual hooks resolve overrides and fall back to a standalone theme's base view", async () => {
|
||||
const standalone = {
|
||||
version: 2,
|
||||
standalone: true,
|
||||
dark: { hue: selectTheme(DEFAULT_THEME, "dark").hue },
|
||||
dark: {
|
||||
hue: selectTheme(DEFAULT_THEME, "dark").hue,
|
||||
"@context:elevated": { text: { default: "#abcdef" } },
|
||||
},
|
||||
} as const
|
||||
let themes: ReturnType<typeof useThemes> | undefined
|
||||
let theme: ReturnType<typeof useTheme> | undefined
|
||||
|
||||
function Probe() {
|
||||
function ContextProbe() {
|
||||
theme = useTheme()
|
||||
return <text>{theme.selected}</text>
|
||||
return <text>{theme.text.default.toString()}</text>
|
||||
}
|
||||
|
||||
function Probe() {
|
||||
themes = useThemes()
|
||||
return (
|
||||
<ThemeContextProvider context="elevated">
|
||||
<ContextProbe />
|
||||
</ThemeContextProvider>
|
||||
)
|
||||
}
|
||||
|
||||
const app = await testRender(
|
||||
|
|
@ -144,10 +157,12 @@ test("contextual themes fall back to a standalone theme's base view", async () =
|
|||
app.renderer.start()
|
||||
|
||||
try {
|
||||
await wait(() => theme?.ready === true)
|
||||
if (!theme) throw new Error("Theme provider is not mounted")
|
||||
expect(theme.contextual("elevated").themeV2.text.default).toBe(theme.themeV2.text.default)
|
||||
expect(theme.contextual("overlay").themeV2.background.default).toBe(theme.themeV2.background.default)
|
||||
await wait(() => themes?.ready === true)
|
||||
if (!themes) throw new Error("Theme provider is not mounted")
|
||||
if (!theme) throw new Error("Contextual theme is not mounted")
|
||||
expect(theme.text.default.equals(RGBA.fromHex("#abcdef"))).toBeTrue()
|
||||
expect(theme.text.default).toBe(themes.contextual("elevated").text.default)
|
||||
expect(themes.contextual("overlay").background.default).toBe(themes.current.background.default)
|
||||
} finally {
|
||||
app.renderer.destroy()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue