From f9bba494b5de7d016143f76e4aa242a34a887a67 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Wed, 24 Sep 2025 01:15:58 -0400 Subject: [PATCH] tui: improve command dialog, shimmer animation, and message handling --- .../src/cli/cmd/tui/component/dialog-command.tsx | 2 +- .../opencode/src/cli/cmd/tui/component/prompt.tsx | 4 ++-- packages/opencode/src/cli/cmd/tui/session.tsx | 14 +++++++++----- packages/opencode/src/cli/cmd/tui/ui/shimmer.tsx | 8 ++++---- packages/opencode/src/session/prompt.ts | 15 +-------------- 5 files changed, 17 insertions(+), 26 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-command.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-command.tsx index 987f51571..21364d4a8 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-command.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-command.tsx @@ -56,7 +56,7 @@ export function CommandProvider(props: ParentProps) { const dialog = useDialog() useKeyboard((evt) => { - if (evt.name === "k" && evt.ctrl) { + if (evt.name === "p" && evt.ctrl) { dialog.replace(() => ) return } diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt.tsx index a4172ef33..ae4de24e6 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt.tsx @@ -207,7 +207,7 @@ export function Prompt(props: PromptProps) { - ctrl+k commands + ctrl+p commands @@ -421,7 +421,7 @@ function Autocomplete(props: { return store.visible }, onInput(value: string) { - if (value.length <= store.index) hide() + if (store.visible && value.length <= store.index) hide() }, onKeyDown(e: ParsedKey) { if (store.visible) { diff --git a/packages/opencode/src/cli/cmd/tui/session.tsx b/packages/opencode/src/cli/cmd/tui/session.tsx index e284ff038..8605e11dc 100644 --- a/packages/opencode/src/cli/cmd/tui/session.tsx +++ b/packages/opencode/src/cli/cmd/tui/session.tsx @@ -138,13 +138,17 @@ export function Session() { flexGrow={1} > - {(message) => ( + {(message, index) => ( - + )} @@ -193,7 +197,7 @@ function UserMessage(props: { message: UserMessage; parts: Part[] }) { ) } -function AssistantMessage(props: { message: AssistantMessage; parts: Part[] }) { +function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; last: boolean }) { const local = useLocal() return ( <> @@ -221,7 +225,7 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[] }) { {props.message.error?.data.message} - + 1) { + if (previous.height > 1 || previous.marginTop === 1) { el.marginTop = 1 return } diff --git a/packages/opencode/src/cli/cmd/tui/ui/shimmer.tsx b/packages/opencode/src/cli/cmd/tui/ui/shimmer.tsx index 40e87951b..d6e38fc63 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/shimmer.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/shimmer.tsx @@ -7,11 +7,11 @@ export type ShimmerProps = { color: string } -const DURATION = 200 +const DURATION = 2_500 export function Shimmer(props: ShimmerProps) { const timeline = createComponentTimeline({ - duration: (props.text.length + 1) * DURATION, + duration: DURATION, loop: true, }) const characters = props.text.split("") @@ -23,12 +23,12 @@ export function Shimmer(props: ShimmerProps) { { shimmer: 0.4 }, { shimmer: 1 }, { - duration: DURATION, + duration: DURATION / (props.text.length + 1), ease: "linear", alternate: true, loop: 2, }, - (i * DURATION) / 2, + (i * (DURATION / (props.text.length + 1))) / 2, ), ) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 01c61e3f6..420eb104d 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1008,12 +1008,6 @@ export namespace SessionPrompt { throw value.error case "start-step": - await Session.updatePart({ - id: Identifier.ascending("part"), - messageID: assistantMsg.id, - sessionID: assistantMsg.sessionID, - type: "step-start", - }) snapshot = await Snapshot.track() break @@ -1021,14 +1015,6 @@ export namespace SessionPrompt { const usage = Session.getUsage(input.model, value.usage, value.providerMetadata) assistantMsg.cost += usage.cost assistantMsg.tokens = usage.tokens - await Session.updatePart({ - id: Identifier.ascending("part"), - messageID: assistantMsg.id, - sessionID: assistantMsg.sessionID, - type: "step-finish", - tokens: usage.tokens, - cost: usage.cost, - }) await Session.updateMessage(assistantMsg) if (snapshot) { const patch = await Snapshot.patch(snapshot) @@ -1095,6 +1081,7 @@ export namespace SessionPrompt { log.error("process", { error: e, }) + assistantMsg.finish = "error" switch (true) { case e instanceof DOMException && e.name === "AbortError": assistantMsg.error = new MessageV2.AbortedError(