diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 2e08e66a4a2d..037957593053 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -21,7 +21,7 @@ import { usePromptStash } from "./stash" import { DialogStash } from "../dialog-stash" import { type AutocompleteRef, Autocomplete } from "./autocomplete" import { useCommandDialog } from "../dialog-command" -import { useRenderer, type JSX } from "@opentui/solid" +import { useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid" import * as Editor from "@tui/util/editor" import { useExit } from "../../context/exit" import * as Clipboard from "../../util/clipboard" @@ -42,6 +42,7 @@ import { DialogSkill } from "../dialog-skill" import { DialogWorkspaceCreate, restoreWorkspaceSession } from "../dialog-workspace-create" import { DialogWorkspaceUnavailable } from "../dialog-workspace-unavailable" import { useArgs } from "@tui/context/args" +import { useDirectory } from "../../context/directory" export type PromptProps = { sessionID?: string @@ -79,6 +80,11 @@ function randomIndex(count: number) { return Math.floor(Math.random() * count) } +function truncateStart(input: string, max: number) { + if (input.length <= max) return input + return "…" + input.slice(-(max - 1)) +} + function fadeColor(color: RGBA, alpha: number) { return RGBA.fromValues(color.r, color.g, color.b, color.a * alpha) } @@ -97,6 +103,8 @@ export function Prompt(props: PromptProps) { const route = useRoute() const project = useProject() const sync = useSync() + const directory = useDirectory() + const dimensions = useTerminalDimensions() const dialog = useDialog() const toast = useToast() const status = createMemo(() => sync.data.session_status?.[props.sessionID ?? ""] ?? { type: "idle" }) @@ -174,6 +182,7 @@ export function Prompt(props: PromptProps) { cost: cost > 0 ? money.format(cost) : undefined, } }) + const directoryLabel = createMemo(() => truncateStart(directory(), Math.max(18, dimensions().width - 24))) const [store, setStore] = createStore<{ prompt: PromptInfo @@ -1249,111 +1258,125 @@ export function Prompt(props: PromptProps) { } /> - - }> - - - - [⋯]}> - - - - - {(() => { - const retry = createMemo(() => { - const s = status() - if (s.type !== "retry") return - return s - }) - const message = createMemo(() => { - const r = retry() - if (!r) return - if (r.message.includes("exceeded your current quota") && r.message.includes("gemini")) - return "gemini is way too hot right now" - if (r.message.length > 80) return r.message.slice(0, 80) + "..." - return r.message - }) - const isTruncated = createMemo(() => { - const r = retry() - if (!r) return false - return r.message.length > 120 - }) - const [seconds, setSeconds] = createSignal(0) - onMount(() => { - const timer = setInterval(() => { - const next = retry()?.next - if (next) setSeconds(Math.round((next - Date.now()) / 1000)) - }, 1000) - - onCleanup(() => { - clearInterval(timer) + + + }> + + + + [⋯]}> + + + + + {(() => { + const retry = createMemo(() => { + const s = status() + if (s.type !== "retry") return + return s }) - }) - const handleMessageClick = () => { - const r = retry() - if (!r) return - if (isTruncated()) { - void DialogAlert.show(dialog, "Retry Error", r.message) + const message = createMemo(() => { + const r = retry() + if (!r) return + if (r.message.includes("exceeded your current quota") && r.message.includes("gemini")) + return "gemini is way too hot right now" + if (r.message.length > 80) return r.message.slice(0, 80) + "..." + return r.message + }) + const isTruncated = createMemo(() => { + const r = retry() + if (!r) return false + return r.message.length > 120 + }) + const [seconds, setSeconds] = createSignal(0) + onMount(() => { + const timer = setInterval(() => { + const next = retry()?.next + if (next) setSeconds(Math.round((next - Date.now()) / 1000)) + }, 1000) + + onCleanup(() => { + clearInterval(timer) + }) + }) + const handleMessageClick = () => { + const r = retry() + if (!r) return + if (isTruncated()) { + void DialogAlert.show(dialog, "Retry Error", r.message) + } } - } - const retryText = () => { - const r = retry() - if (!r) return "" - const baseMessage = message() - const truncatedHint = isTruncated() ? " (click to expand)" : "" - const duration = formatDuration(seconds()) - const retryInfo = ` [retrying ${duration ? `in ${duration} ` : ""}attempt #${r.attempt}]` - return baseMessage + truncatedHint + retryInfo - } + const retryText = () => { + const r = retry() + if (!r) return "" + const baseMessage = message() + const truncatedHint = isTruncated() ? " (click to expand)" : "" + const duration = formatDuration(seconds()) + const retryInfo = ` [retrying ${duration ? `in ${duration} ` : ""}attempt #${r.attempt}]` + return baseMessage + truncatedHint + retryInfo + } - return ( - - - {retryText()} - - - ) - })()} + return ( + + + {retryText()} + + + ) + })()} + + 0 ? theme.primary : theme.text} wrapMode="none" flexShrink={0}> + esc{" "} + 0 ? theme.primary : theme.textMuted }}> + {store.interrupt > 0 ? "again to interrupt" : "interrupt"} + + - 0 ? theme.primary : theme.text}> - esc{" "} - 0 ? theme.primary : theme.textMuted }}> - {store.interrupt > 0 ? "again to interrupt" : "interrupt"} - - - - + + - + + - - - {(item) => ( - - {[item().context, item().cost].filter(Boolean).join(" · ")} + + + + + {directoryLabel()} - )} - - - - {keybind.print("agent_cycle")} agents + + + + + + {(item) => ( + + {[item().context, item().cost].filter(Boolean).join(" · ")} + + )} + + + + {keybind.print("agent_cycle")} agents + + + + + {keybind.print("command_list")} commands - - - - {keybind.print("command_list")} commands - + + - + esc exit shell mode diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx index b468d851b0c9..e4abd2b3c3b3 100644 --- a/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx +++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx @@ -1,6 +1,5 @@ import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from "@opencode-ai/plugin/tui" import { createMemo, Show } from "solid-js" -import { Global } from "@/global" const id = "internal:sidebar-footer" @@ -13,16 +12,6 @@ function View(props: { api: TuiPluginApi }) { ) const done = createMemo(() => props.api.kv.get("dismissed_getting_started", false)) const show = createMemo(() => !has() && !done()) - const path = createMemo(() => { - const dir = props.api.state.path.directory || process.cwd() - const out = dir.replace(Global.Path.home, "~") - const text = props.api.state.vcs?.branch ? out + ":" + props.api.state.vcs.branch : out - const list = text.split("/") - return { - parent: list.slice(0, -1).join("/"), - name: list.at(-1) ?? "", - } - }) return ( @@ -59,10 +48,6 @@ function View(props: { api: TuiPluginApi }) { - - {path().parent}/ - {path().name} - Open