mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-19 08:09:51 +00:00
prompt slot (#19563)
This commit is contained in:
parent
772059acb5
commit
38af99dcb4
9 changed files with 125 additions and 43 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
|
|
|
|||
|
|
@ -231,6 +231,7 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi {
|
|||
DialogConfirm: () => null,
|
||||
DialogPrompt: () => null,
|
||||
DialogSelect: () => null,
|
||||
Prompt: () => null,
|
||||
toast: () => {},
|
||||
dialog: {
|
||||
replace: () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue