mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-23 18:55:37 -04:00
fix(tui): render submitted prompts optimistically
This commit is contained in:
@@ -37,7 +37,7 @@ import { usePromptStash } from "../../prompt/stash"
|
||||
import { DialogStash } from "../dialog-stash"
|
||||
import { type AutocompleteRef, Autocomplete } from "./autocomplete"
|
||||
import { useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid"
|
||||
import type { AssistantMessage, FilePart, SessionV2Info, UserMessage } from "@opencode-ai/sdk/v2"
|
||||
import type { AssistantMessage, FilePart, SessionMessageUser, SessionV2Info, UserMessage } from "@opencode-ai/sdk/v2"
|
||||
import { Locale } from "../../util/locale"
|
||||
import { errorMessage } from "../../util/error"
|
||||
import { createColors, createFrames } from "../../ui/spinner"
|
||||
@@ -62,7 +62,7 @@ export type PromptProps = {
|
||||
sessionID?: string
|
||||
visible?: boolean
|
||||
disabled?: boolean
|
||||
onSubmit?: () => void
|
||||
onSubmit?: (message?: SessionMessageUser) => void
|
||||
ref?: (ref: PromptRef | undefined) => void
|
||||
hint?: JSX.Element
|
||||
right?: JSX.Element
|
||||
@@ -1077,6 +1077,7 @@ export function Prompt(props: PromptProps) {
|
||||
]
|
||||
: []
|
||||
|
||||
let submittedUserMessage: SessionMessageUser | undefined
|
||||
if (store.mode === "shell") {
|
||||
move.startSubmit()
|
||||
void sdk.client.session.shell({
|
||||
@@ -1152,7 +1153,7 @@ export function Prompt(props: PromptProps) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
const error = await sdk.api.session
|
||||
const admitted = await sdk.api.session
|
||||
.prompt({
|
||||
sessionID,
|
||||
prompt: {
|
||||
@@ -1189,14 +1190,22 @@ export function Prompt(props: PromptProps) {
|
||||
},
|
||||
})
|
||||
.then(
|
||||
() => undefined,
|
||||
(error) => error,
|
||||
(result) => result,
|
||||
(error) => {
|
||||
toast.show({ title: "Failed to send prompt", message: errorMessage(error), variant: "error" })
|
||||
return undefined
|
||||
},
|
||||
)
|
||||
if (error) {
|
||||
toast.show({ title: "Failed to send prompt", message: errorMessage(error), variant: "error" })
|
||||
return false
|
||||
}
|
||||
if (!admitted) return false
|
||||
if (editorParts.length > 0) editor.markSelectionSent()
|
||||
submittedUserMessage = {
|
||||
id: admitted.id,
|
||||
type: "user",
|
||||
text: admitted.prompt.text,
|
||||
files: admitted.prompt.files ? [...admitted.prompt.files] : undefined,
|
||||
agents: admitted.prompt.agents ? [...admitted.prompt.agents] : undefined,
|
||||
time: { created: admitted.timeCreated },
|
||||
}
|
||||
}
|
||||
history.append({
|
||||
...store.prompt,
|
||||
@@ -1208,7 +1217,7 @@ export function Prompt(props: PromptProps) {
|
||||
parts: [],
|
||||
})
|
||||
setStore("extmarkToPartIndex", new Map())
|
||||
props.onSubmit?.()
|
||||
props.onSubmit?.(submittedUserMessage)
|
||||
|
||||
// temporary hack to make sure the message is sent
|
||||
if (!props.sessionID) {
|
||||
|
||||
@@ -241,6 +241,13 @@ export function Session() {
|
||||
const sdk = useSDK()
|
||||
const editor = useEditorContext()
|
||||
const rows = createSessionRows(() => route.sessionID)
|
||||
const [optimisticPrompts, setOptimisticPrompts] = createSignal<SessionMessageUser[]>([])
|
||||
const visibleOptimisticPrompts = createMemo(() => {
|
||||
const ids = new Set(messageIDs())
|
||||
return optimisticPrompts().filter((message) => !ids.has(message.id))
|
||||
})
|
||||
|
||||
createEffect(on(() => route.sessionID, () => setOptimisticPrompts([])))
|
||||
|
||||
createEffect(
|
||||
on(descendantSessionIDs, (sessionIDs) => {
|
||||
@@ -920,6 +927,13 @@ export function Session() {
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
<For each={visibleOptimisticPrompts()}>
|
||||
{(message) => (
|
||||
<box marginTop={1} flexShrink={0}>
|
||||
<UserMessage message={message} optimistic={true} />
|
||||
</box>
|
||||
)}
|
||||
</For>
|
||||
<Show when={session()?.revert?.messageID}>
|
||||
<RevertMessage
|
||||
count={
|
||||
@@ -960,7 +974,12 @@ export function Session() {
|
||||
visible={true}
|
||||
ref={bind}
|
||||
disabled={false}
|
||||
onSubmit={() => {
|
||||
onSubmit={(message) => {
|
||||
if (message)
|
||||
setOptimisticPrompts((messages) => [
|
||||
...messages.filter((item) => item.id !== message.id),
|
||||
message,
|
||||
])
|
||||
toBottom()
|
||||
}}
|
||||
sessionID={route.sessionID}
|
||||
@@ -1317,7 +1336,7 @@ function RevertMessage(props: {
|
||||
)
|
||||
}
|
||||
|
||||
function UserMessage(props: { message: SessionMessageUser }) {
|
||||
function UserMessage(props: { message: SessionMessageUser; optimistic?: boolean }) {
|
||||
const ctx = use()
|
||||
const local = useLocal()
|
||||
const files = createMemo(() => props.message.files ?? [])
|
||||
@@ -1343,6 +1362,7 @@ function UserMessage(props: { message: SessionMessageUser }) {
|
||||
setHover(false)
|
||||
}}
|
||||
onMouseUp={() => {
|
||||
if (props.optimistic) return
|
||||
if (renderer.getSelection()?.getSelectedText()) return
|
||||
dialog.replace(() => <DialogMessage messageID={props.message.id} sessionID={ctx.sessionID} />)
|
||||
}}
|
||||
@@ -1353,6 +1373,9 @@ function UserMessage(props: { message: SessionMessageUser }) {
|
||||
flexShrink={0}
|
||||
>
|
||||
<text fg={theme.text}>{props.message.text}</text>
|
||||
<Show when={props.optimistic}>
|
||||
<text fg={theme.textMuted}>optimistic prompt</text>
|
||||
</Show>
|
||||
<Show when={files().length}>
|
||||
<box
|
||||
flexDirection="row"
|
||||
|
||||
Reference in New Issue
Block a user