diff --git a/packages/opencode/src/cli/cmd/tui/component/hoverable-label.tsx b/packages/opencode/src/cli/cmd/tui/component/hoverable-label.tsx new file mode 100644 index 00000000000..73944c3adcf --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/component/hoverable-label.tsx @@ -0,0 +1,51 @@ +import { RGBA } from "@opentui/core" +import { createSignal, createUniqueId, onCleanup, type JSX } from "solid-js" +import { useTheme, selectedForeground } from "@tui/context/theme" + +const [activeId, setActiveId] = createSignal(null) + +type HoverableLabelProps = { + children: (hover: boolean, hoverFg: () => RGBA) => JSX.Element + onClick?: () => void + disabled?: boolean +} + +export function HoverableLabel(props: HoverableLabelProps) { + const id = createUniqueId() + const { theme } = useTheme() + const hoverFg = () => selectedForeground(theme) + + const isHovered = () => activeId() === id && !props.disabled + + onCleanup(() => { + if (activeId() !== id) return + setActiveId(null) + }) + + const handleMouseOver = () => { + if (props.disabled) return + setActiveId(id) + } + + const handleMouseOut = () => { + if (activeId() !== id) return + setActiveId(null) + } + + const handleClick = () => { + if (props.disabled) return + setActiveId(null) + props.onClick?.() + } + + return ( + + {props.children(isHovered(), hoverFg)} + + ) +} 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 9494b81cb10..fc9c696ef75 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -1,4 +1,4 @@ -import { BoxRenderable, TextareaRenderable, MouseEvent, PasteEvent, t, dim, fg, type KeyBinding } from "@opentui/core" +import { BoxRenderable, TextareaRenderable, MouseEvent, PasteEvent, type KeyBinding } from "@opentui/core" import { createEffect, createMemo, type JSX, onMount, createSignal, onCleanup, Show, Switch, Match } from "solid-js" import "opentui-spinner/solid" import { useLocal } from "@tui/context/local" @@ -16,7 +16,7 @@ import { usePromptStash } from "./stash" import { DialogStash } from "../dialog-stash" import { type AutocompleteRef, Autocomplete } from "./autocomplete" import { useCommandDialog } from "../dialog-command" -import { useRenderer } from "@opentui/solid" +import { useRenderer, useTerminalDimensions } from "@opentui/solid" import { Editor } from "@tui/util/editor" import { useExit } from "../../context/exit" import { Clipboard } from "../../util/clipboard" @@ -29,6 +29,9 @@ import { useDialog } from "@tui/ui/dialog" import { DialogProvider as DialogProviderConnect } from "../dialog-provider" import { DialogAlert } from "../../ui/dialog-alert" import { useToast } from "../../ui/toast" +import { DialogAgent } from "../dialog-agent" +import { DialogModel } from "../dialog-model" +import { HoverableLabel } from "../hoverable-label" export type PromptProps = { sessionID?: string @@ -123,6 +126,9 @@ export function Prompt(props: PromptProps) { const stash = usePromptStash() const command = useCommandDialog() const renderer = useRenderer() + const dimensions = useTerminalDimensions() + const tall = createMemo(() => dimensions().height > 40) + const wide = createMemo(() => dimensions().width > 120) const { theme, syntax } = useTheme() function promptModelWarning() { @@ -946,19 +952,29 @@ export function Prompt(props: PromptProps) { cursorColor={theme.text} syntaxStyle={syntax()} /> - - - {store.mode === "shell" ? "Shell" : Locale.titlecase(local.agent.current().name)}{" "} - - - - - {local.model.parsed().model} - - {local.model.parsed().provider} - - - + + + dialog.replace(() => )}> + {(hover, hoverFg) => ( + + {store.mode === "shell" ? "Shell" : Locale.titlecase(local.agent.current().name)}{" "} + + )} + + + dialog.replace(() => )}> + {(hover, hoverFg) => ( + + + {local.model.parsed().model} + + {local.model.parsed().provider} + + )} + + + + - - - - - - {keybind.print("agent_cycle")} switch agent - - - {keybind.print("command_list")} commands - - - + + + + dialog.replace(() => )}> + {(hover, hoverFg) => ( + + {store.mode === "shell" ? "Shell" : Locale.titlecase(local.agent.current().name)}{" "} + + )} + + + dialog.replace(() => )}> + {(hover, hoverFg) => ( + + + {local.model.parsed().model} + + {local.model.parsed().provider} + + )} + + + + + + + + + + dialog.replace(() => )}> + {(hover, hoverFg) => ( + + {keybind.print("agent_cycle")}{" "} + switch agent + + )} + + + - esc exit shell mode + {keybind.print("sidebar_toggle")} sidebar - - - - + + command.show()}> + {(hover, hoverFg) => ( + + {keybind.print("command_list")}{" "} + commands + + )} + + + + + esc exit shell mode + + + +