|
|
@@ -29,6 +29,63 @@ import { Session as SessionApi } from "@/session"
|
|
|
import { TuiEvent } from "./event"
|
|
|
import { KVProvider, useKV } from "./context/kv"
|
|
|
|
|
|
+async function getTerminalBackgroundColor(): Promise<"dark" | "light"> {
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ let timeout: NodeJS.Timeout
|
|
|
+
|
|
|
+ const cleanup = () => {
|
|
|
+ process.stdin.setRawMode(false)
|
|
|
+ process.stdin.removeListener("data", handler)
|
|
|
+ clearTimeout(timeout)
|
|
|
+ }
|
|
|
+
|
|
|
+ const handler = (data: Buffer) => {
|
|
|
+ const str = data.toString()
|
|
|
+ const match = str.match(/\x1b]11;([^\x07\x1b]+)/)
|
|
|
+ if (match) {
|
|
|
+ cleanup()
|
|
|
+ const color = match[1]
|
|
|
+ // Parse RGB values from color string
|
|
|
+ // Formats: rgb:RR/GG/BB or #RRGGBB or rgb(R,G,B)
|
|
|
+ let r = 0,
|
|
|
+ g = 0,
|
|
|
+ b = 0
|
|
|
+
|
|
|
+ if (color.startsWith("rgb:")) {
|
|
|
+ const parts = color.substring(4).split("/")
|
|
|
+ r = parseInt(parts[0], 16) >> 8 // Convert 16-bit to 8-bit
|
|
|
+ g = parseInt(parts[1], 16) >> 8 // Convert 16-bit to 8-bit
|
|
|
+ b = parseInt(parts[2], 16) >> 8 // Convert 16-bit to 8-bit
|
|
|
+ } else if (color.startsWith("#")) {
|
|
|
+ r = parseInt(color.substring(1, 3), 16)
|
|
|
+ g = parseInt(color.substring(3, 5), 16)
|
|
|
+ b = parseInt(color.substring(5, 7), 16)
|
|
|
+ } else if (color.startsWith("rgb(")) {
|
|
|
+ const parts = color.substring(4, color.length - 1).split(",")
|
|
|
+ r = parseInt(parts[0])
|
|
|
+ g = parseInt(parts[1])
|
|
|
+ b = parseInt(parts[2])
|
|
|
+ }
|
|
|
+
|
|
|
+ // Calculate luminance using relative luminance formula
|
|
|
+ const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255
|
|
|
+
|
|
|
+ // Determine if dark or light based on luminance threshold
|
|
|
+ resolve(luminance > 0.5 ? "light" : "dark")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ process.stdin.setRawMode(true)
|
|
|
+ process.stdin.on("data", handler)
|
|
|
+ process.stdout.write("\x1b]11;?\x07")
|
|
|
+
|
|
|
+ timeout = setTimeout(() => {
|
|
|
+ cleanup()
|
|
|
+ resolve("dark")
|
|
|
+ }, 1000)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
export function tui(input: {
|
|
|
url: string
|
|
|
sessionID?: string
|
|
|
@@ -38,7 +95,9 @@ export function tui(input: {
|
|
|
onExit?: () => Promise<void>
|
|
|
}) {
|
|
|
// promise to prevent immediate exit
|
|
|
- return new Promise<void>((resolve) => {
|
|
|
+ return new Promise<void>(async (resolve) => {
|
|
|
+ const mode = await getTerminalBackgroundColor()
|
|
|
+
|
|
|
const routeData: Route | undefined = input.sessionID
|
|
|
? {
|
|
|
type: "session",
|
|
|
@@ -65,8 +124,12 @@ export function tui(input: {
|
|
|
<RouteProvider data={routeData}>
|
|
|
<SDKProvider url={input.url}>
|
|
|
<SyncProvider>
|
|
|
- <ThemeProvider>
|
|
|
- <LocalProvider initialModel={input.model} initialAgent={input.agent} initialPrompt={input.prompt}>
|
|
|
+ <ThemeProvider mode={mode}>
|
|
|
+ <LocalProvider
|
|
|
+ initialModel={input.model}
|
|
|
+ initialAgent={input.agent}
|
|
|
+ initialPrompt={input.prompt}
|
|
|
+ >
|
|
|
<KeybindProvider>
|
|
|
<DialogProvider>
|
|
|
<CommandProvider>
|
|
|
@@ -109,7 +172,7 @@ function App() {
|
|
|
const sync = useSync()
|
|
|
const toast = useToast()
|
|
|
const [sessionExists, setSessionExists] = createSignal(false)
|
|
|
- const { theme } = useTheme()
|
|
|
+ const { theme, mode, setMode } = useTheme()
|
|
|
const exit = useExit()
|
|
|
|
|
|
useKeyboard(async (evt) => {
|
|
|
@@ -238,6 +301,14 @@ function App() {
|
|
|
},
|
|
|
category: "System",
|
|
|
},
|
|
|
+ {
|
|
|
+ title: `Switch to ${mode() === "dark" ? "light" : "dark"} mode`,
|
|
|
+ value: "theme.switch_mode",
|
|
|
+ onSelect: () => {
|
|
|
+ setMode(mode() === "dark" ? "light" : "dark")
|
|
|
+ },
|
|
|
+ category: "System",
|
|
|
+ },
|
|
|
{
|
|
|
title: "Help",
|
|
|
value: "help.show",
|
|
|
@@ -251,7 +322,7 @@ function App() {
|
|
|
value: "app.exit",
|
|
|
onSelect: exit,
|
|
|
category: "System",
|
|
|
- }
|
|
|
+ },
|
|
|
])
|
|
|
|
|
|
createEffect(() => {
|
|
|
@@ -335,7 +406,9 @@ function App() {
|
|
|
paddingRight={1}
|
|
|
>
|
|
|
<text fg={theme.textMuted}>open</text>
|
|
|
- <text attributes={TextAttributes.BOLD}>code </text>
|
|
|
+ <text fg={theme.text} attributes={TextAttributes.BOLD}>
|
|
|
+ code{" "}
|
|
|
+ </text>
|
|
|
<text fg={theme.textMuted}>v{Installation.VERSION}</text>
|
|
|
</box>
|
|
|
<box paddingLeft={1} paddingRight={1}>
|