diff options
| author | Sebastian <[email protected]> | 2026-03-29 00:27:27 +0100 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-03-29 00:27:27 +0100 |
| commit | 38af99dcb47e92e4f25c7aa6344b5f8f9b766e1e (patch) | |
| tree | 299af567105120803426ffa200c11a08ab69f8ed /packages | |
| parent | 772059acb5555f958ef616a3ff00a2fd89b36933 (diff) | |
| download | opencode-38af99dcb47e92e4f25c7aa6344b5f8f9b766e1e.tar.gz opencode-38af99dcb47e92e4f25c7aa6344b5f8f9b766e1e.zip | |
prompt slot (#19563)
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/opencode/package.json | 4 | ||||
| -rw-r--r-- | packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx | 29 | ||||
| -rw-r--r-- | packages/opencode/src/cli/cmd/tui/plugin/api.tsx | 14 | ||||
| -rw-r--r-- | packages/opencode/src/cli/cmd/tui/routes/home.tsx | 27 | ||||
| -rw-r--r-- | packages/opencode/test/fixture/tui-plugin.ts | 1 | ||||
| -rw-r--r-- | packages/plugin/package.json | 8 | ||||
| -rw-r--r-- | packages/plugin/src/tui.ts | 17 |
7 files changed, 76 insertions, 24 deletions
diff --git a/packages/opencode/package.json b/packages/opencode/package.json index ca1ecc48a..5dca50be3 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -102,8 +102,8 @@ "@opencode-ai/sdk": "workspace:*", "@opencode-ai/util": "workspace:*", "@openrouter/ai-sdk-provider": "2.3.3", - "@opentui/core": "0.1.91", - "@opentui/solid": "0.1.91", + "@opentui/core": "0.1.92", + "@opentui/solid": "0.1.92", "@parcel/watcher": "2.5.1", "@pierre/diffs": "catalog:", "@solid-primitives/event-bus": "1.1.2", 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 d88747e4c..96563b884 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -45,6 +45,10 @@ export type PromptProps = { ref?: (ref: PromptRef) => void hint?: JSX.Element showPlaceholder?: boolean + placeholders?: { + normal?: string[] + shell?: string[] + } } export type PromptRef = { @@ -57,13 +61,16 @@ export type PromptRef = { submit(): void } -const PLACEHOLDERS = ["Fix a TODO in the codebase", "What is the tech stack of this project?", "Fix broken tests"] -const SHELL_PLACEHOLDERS = ["ls -la", "git status", "pwd"] const money = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", }) +function randomIndex(count: number) { + if (count <= 0) return 0 + return Math.floor(Math.random() * count) +} + export function Prompt(props: PromptProps) { let input: TextareaRenderable let anchor: BoxRenderable @@ -83,6 +90,8 @@ export function Prompt(props: PromptProps) { const renderer = useRenderer() const { theme, syntax } = useTheme() const kv = useKV() + const list = createMemo(() => props.placeholders?.normal ?? []) + const shell = createMemo(() => props.placeholders?.shell ?? []) function promptModelWarning() { toast.show({ @@ -152,7 +161,7 @@ export function Prompt(props: PromptProps) { interrupt: number placeholder: number }>({ - placeholder: Math.floor(Math.random() * PLACEHOLDERS.length), + placeholder: randomIndex(list().length), prompt: { input: "", parts: [], @@ -166,7 +175,7 @@ export function Prompt(props: PromptProps) { on( () => props.sessionID, () => { - setStore("placeholder", Math.floor(Math.random() * PLACEHOLDERS.length)) + setStore("placeholder", randomIndex(list().length)) }, { defer: true }, ), @@ -801,12 +810,14 @@ export function Prompt(props: PromptProps) { }) const placeholderText = createMemo(() => { - if (props.sessionID) return undefined + if (props.showPlaceholder === false) return undefined if (store.mode === "shell") { - const example = SHELL_PLACEHOLDERS[store.placeholder % SHELL_PLACEHOLDERS.length] + if (!shell().length) return undefined + const example = shell()[store.placeholder % shell().length] return `Run a command... "${example}"` } - return `Ask anything... "${PLACEHOLDERS[store.placeholder % PLACEHOLDERS.length]}"` + if (!list().length) return undefined + return `Ask anything... "${list()[store.placeholder % list().length]}"` }) const spinnerDef = createMemo(() => { @@ -922,7 +933,7 @@ export function Prompt(props: PromptProps) { } } if (e.name === "!" && input.visualCursor.offset === 0) { - setStore("placeholder", Math.floor(Math.random() * SHELL_PLACEHOLDERS.length)) + setStore("placeholder", randomIndex(shell().length)) setStore("mode", "shell") e.preventDefault() return @@ -1097,7 +1108,7 @@ export function Prompt(props: PromptProps) { /> </box> <box flexDirection="row" justifyContent="space-between"> - <Show when={status().type !== "idle"} fallback={<text />}> + <Show when={status().type !== "idle"} fallback={props.hint ?? <text />}> <box flexDirection="row" gap={1} diff --git a/packages/opencode/src/cli/cmd/tui/plugin/api.tsx b/packages/opencode/src/cli/cmd/tui/plugin/api.tsx index 2bfd96ac3..e5bd41b9d 100644 --- a/packages/opencode/src/cli/cmd/tui/plugin/api.tsx +++ b/packages/opencode/src/cli/cmd/tui/plugin/api.tsx @@ -14,6 +14,7 @@ import { DialogAlert } from "../ui/dialog-alert" import { DialogConfirm } from "../ui/dialog-confirm" import { DialogPrompt } from "../ui/dialog-prompt" import { DialogSelect, type DialogSelectOption as SelectOption } from "../ui/dialog-select" +import { Prompt } from "../component/prompt" import type { useToast } from "../ui/toast" import { Installation } from "@/installation" import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk/v2" @@ -287,6 +288,19 @@ export function createTuiApi(input: Input): TuiHostPluginApi { /> ) }, + Prompt(props) { + return ( + <Prompt + workspaceID={props.workspaceID} + visible={props.visible} + disabled={props.disabled} + onSubmit={props.onSubmit} + hint={props.hint} + showPlaceholder={props.showPlaceholder} + placeholders={props.placeholders} + /> + ) + }, toast(inputToast) { input.toast.show({ title: inputToast.title, diff --git a/packages/opencode/src/cli/cmd/tui/routes/home.tsx b/packages/opencode/src/cli/cmd/tui/routes/home.tsx index 07549c6c2..b63bf2d2d 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/home.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/home.tsx @@ -15,6 +15,10 @@ import { TuiPluginRuntime } from "../plugin" // TODO: what is the best way to do this? let once = false +const placeholder = { + normal: ["Fix a TODO in the codebase", "What is the tech stack of this project?", "Fix broken tests"], + shell: ["ls -la", "git status", "pwd"], +} export function Home() { const sync = useSync() @@ -49,11 +53,12 @@ export function Home() { </box> ) - let prompt: PromptRef + let prompt: PromptRef | undefined const args = useArgs() const local = useLocal() onMount(() => { if (once) return + if (!prompt) return if (route.initialPrompt) { prompt.set(route.initialPrompt) once = true @@ -69,6 +74,7 @@ export function Home() { () => sync.ready && local.model.ready, (ready) => { if (!ready) return + if (!prompt) return if (!args.prompt) return if (prompt.current?.input !== args.prompt) return prompt.submit() @@ -89,14 +95,17 @@ export function Home() { </box> <box height={1} minHeight={0} flexShrink={1} /> <box width="100%" maxWidth={75} zIndex={1000} paddingTop={1} flexShrink={0}> - <Prompt - ref={(r) => { - prompt = r - promptRef.set(r) - }} - hint={Hint} - workspaceID={route.workspaceID} - /> + <TuiPluginRuntime.Slot name="home_prompt" mode="replace" workspace_id={route.workspaceID}> + <Prompt + ref={(r) => { + prompt = r + promptRef.set(r) + }} + hint={Hint} + workspaceID={route.workspaceID} + placeholders={placeholder} + /> + </TuiPluginRuntime.Slot> </box> <TuiPluginRuntime.Slot name="home_bottom" /> <box flexGrow={1} minHeight={0} /> diff --git a/packages/opencode/test/fixture/tui-plugin.ts b/packages/opencode/test/fixture/tui-plugin.ts index c982d129f..7a34877e9 100644 --- a/packages/opencode/test/fixture/tui-plugin.ts +++ b/packages/opencode/test/fixture/tui-plugin.ts @@ -231,6 +231,7 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi { DialogConfirm: () => null, DialogPrompt: () => null, DialogSelect: () => null, + Prompt: () => null, toast: () => {}, dialog: { replace: () => { diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 46c6900c3..96fe5cd60 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -21,8 +21,8 @@ "zod": "catalog:" }, "peerDependencies": { - "@opentui/core": ">=0.1.91", - "@opentui/solid": ">=0.1.91" + "@opentui/core": ">=0.1.92", + "@opentui/solid": ">=0.1.92" }, "peerDependenciesMeta": { "@opentui/core": { @@ -33,8 +33,8 @@ } }, "devDependencies": { - "@opentui/core": "0.1.91", - "@opentui/solid": "0.1.91", + "@opentui/core": "0.1.92", + "@opentui/solid": "0.1.92", "@tsconfig/node22": "catalog:", "@types/node": "catalog:", "typescript": "catalog:", diff --git a/packages/plugin/src/tui.ts b/packages/plugin/src/tui.ts index cbb6f62b6..bbf349490 100644 --- a/packages/plugin/src/tui.ts +++ b/packages/plugin/src/tui.ts @@ -135,6 +135,19 @@ export type TuiDialogSelectProps<Value = unknown> = { current?: Value } +export type TuiPromptProps = { + workspaceID?: string + visible?: boolean + disabled?: boolean + onSubmit?: () => void + hint?: JSX.Element + showPlaceholder?: boolean + placeholders?: { + normal?: string[] + shell?: string[] + } +} + export type TuiToast = { variant?: "info" | "success" | "warning" | "error" title?: string @@ -279,6 +292,9 @@ export type TuiSidebarFileItem = { export type TuiSlotMap = { app: {} home_logo: {} + home_prompt: { + workspace_id?: string + } home_bottom: {} sidebar_title: { session_id: string @@ -386,6 +402,7 @@ export type TuiPluginApi = { DialogConfirm: (props: TuiDialogConfirmProps) => JSX.Element DialogPrompt: (props: TuiDialogPromptProps) => JSX.Element DialogSelect: <Value = unknown>(props: TuiDialogSelectProps<Value>) => JSX.Element + Prompt: (props: TuiPromptProps) => JSX.Element toast: (input: TuiToast) => void dialog: TuiDialogStack } |
