fix(tui): render submitted prompts optimistically
This commit is contained in:
parent
e1fafa2e54
commit
4c80946b4b
2 changed files with 44 additions and 12 deletions
|
|
@ -37,7 +37,7 @@ import { usePromptStash } from "../../prompt/stash"
|
||||||
import { DialogStash } from "../dialog-stash"
|
import { DialogStash } from "../dialog-stash"
|
||||||
import { type AutocompleteRef, Autocomplete } from "./autocomplete"
|
import { type AutocompleteRef, Autocomplete } from "./autocomplete"
|
||||||
import { useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid"
|
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 { Locale } from "../../util/locale"
|
||||||
import { errorMessage } from "../../util/error"
|
import { errorMessage } from "../../util/error"
|
||||||
import { createColors, createFrames } from "../../ui/spinner"
|
import { createColors, createFrames } from "../../ui/spinner"
|
||||||
|
|
@ -62,7 +62,7 @@ export type PromptProps = {
|
||||||
sessionID?: string
|
sessionID?: string
|
||||||
visible?: boolean
|
visible?: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
onSubmit?: () => void
|
onSubmit?: (message?: SessionMessageUser) => void
|
||||||
ref?: (ref: PromptRef | undefined) => void
|
ref?: (ref: PromptRef | undefined) => void
|
||||||
hint?: JSX.Element
|
hint?: JSX.Element
|
||||||
right?: JSX.Element
|
right?: JSX.Element
|
||||||
|
|
@ -1077,6 +1077,7 @@ export function Prompt(props: PromptProps) {
|
||||||
]
|
]
|
||||||
: []
|
: []
|
||||||
|
|
||||||
|
let submittedUserMessage: SessionMessageUser | undefined
|
||||||
if (store.mode === "shell") {
|
if (store.mode === "shell") {
|
||||||
move.startSubmit()
|
move.startSubmit()
|
||||||
void sdk.client.session.shell({
|
void sdk.client.session.shell({
|
||||||
|
|
@ -1152,7 +1153,7 @@ export function Prompt(props: PromptProps) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const error = await sdk.api.session
|
const admitted = await sdk.api.session
|
||||||
.prompt({
|
.prompt({
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: {
|
prompt: {
|
||||||
|
|
@ -1189,14 +1190,22 @@ export function Prompt(props: PromptProps) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
() => undefined,
|
(result) => result,
|
||||||
(error) => error,
|
(error) => {
|
||||||
|
toast.show({ title: "Failed to send prompt", message: errorMessage(error), variant: "error" })
|
||||||
|
return undefined
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if (error) {
|
if (!admitted) return false
|
||||||
toast.show({ title: "Failed to send prompt", message: errorMessage(error), variant: "error" })
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (editorParts.length > 0) editor.markSelectionSent()
|
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({
|
history.append({
|
||||||
...store.prompt,
|
...store.prompt,
|
||||||
|
|
@ -1208,7 +1217,7 @@ export function Prompt(props: PromptProps) {
|
||||||
parts: [],
|
parts: [],
|
||||||
})
|
})
|
||||||
setStore("extmarkToPartIndex", new Map())
|
setStore("extmarkToPartIndex", new Map())
|
||||||
props.onSubmit?.()
|
props.onSubmit?.(submittedUserMessage)
|
||||||
|
|
||||||
// temporary hack to make sure the message is sent
|
// temporary hack to make sure the message is sent
|
||||||
if (!props.sessionID) {
|
if (!props.sessionID) {
|
||||||
|
|
|
||||||
|
|
@ -241,6 +241,13 @@ export function Session() {
|
||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
const editor = useEditorContext()
|
const editor = useEditorContext()
|
||||||
const rows = createSessionRows(() => route.sessionID)
|
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(
|
createEffect(
|
||||||
on(descendantSessionIDs, (sessionIDs) => {
|
on(descendantSessionIDs, (sessionIDs) => {
|
||||||
|
|
@ -920,6 +927,13 @@ export function Session() {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
|
<For each={visibleOptimisticPrompts()}>
|
||||||
|
{(message) => (
|
||||||
|
<box marginTop={1} flexShrink={0}>
|
||||||
|
<UserMessage message={message} optimistic={true} />
|
||||||
|
</box>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
<Show when={session()?.revert?.messageID}>
|
<Show when={session()?.revert?.messageID}>
|
||||||
<RevertMessage
|
<RevertMessage
|
||||||
count={
|
count={
|
||||||
|
|
@ -960,7 +974,12 @@ export function Session() {
|
||||||
visible={true}
|
visible={true}
|
||||||
ref={bind}
|
ref={bind}
|
||||||
disabled={false}
|
disabled={false}
|
||||||
onSubmit={() => {
|
onSubmit={(message) => {
|
||||||
|
if (message)
|
||||||
|
setOptimisticPrompts((messages) => [
|
||||||
|
...messages.filter((item) => item.id !== message.id),
|
||||||
|
message,
|
||||||
|
])
|
||||||
toBottom()
|
toBottom()
|
||||||
}}
|
}}
|
||||||
sessionID={route.sessionID}
|
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 ctx = use()
|
||||||
const local = useLocal()
|
const local = useLocal()
|
||||||
const files = createMemo(() => props.message.files ?? [])
|
const files = createMemo(() => props.message.files ?? [])
|
||||||
|
|
@ -1343,6 +1362,7 @@ function UserMessage(props: { message: SessionMessageUser }) {
|
||||||
setHover(false)
|
setHover(false)
|
||||||
}}
|
}}
|
||||||
onMouseUp={() => {
|
onMouseUp={() => {
|
||||||
|
if (props.optimistic) return
|
||||||
if (renderer.getSelection()?.getSelectedText()) return
|
if (renderer.getSelection()?.getSelectedText()) return
|
||||||
dialog.replace(() => <DialogMessage messageID={props.message.id} sessionID={ctx.sessionID} />)
|
dialog.replace(() => <DialogMessage messageID={props.message.id} sessionID={ctx.sessionID} />)
|
||||||
}}
|
}}
|
||||||
|
|
@ -1353,6 +1373,9 @@ function UserMessage(props: { message: SessionMessageUser }) {
|
||||||
flexShrink={0}
|
flexShrink={0}
|
||||||
>
|
>
|
||||||
<text fg={theme.text}>{props.message.text}</text>
|
<text fg={theme.text}>{props.message.text}</text>
|
||||||
|
<Show when={props.optimistic}>
|
||||||
|
<text fg={theme.textMuted}>optimistic prompt</text>
|
||||||
|
</Show>
|
||||||
<Show when={files().length}>
|
<Show when={files().length}>
|
||||||
<box
|
<box
|
||||||
flexDirection="row"
|
flexDirection="row"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue