mirror of
https://github.com/sst/opencode.git
synced 2025-12-23 10:11:41 +00:00
tui: improve command dialog, shimmer animation, and message handling
This commit is contained in:
parent
0b5730d4dd
commit
f9bba494b5
5 changed files with 17 additions and 26 deletions
|
|
@ -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(() => <DialogCommand options={value.options} />)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ export function Prompt(props: PromptProps) {
|
|||
</Match>
|
||||
<Match when={true}>
|
||||
<text live>
|
||||
ctrl+k <span style={{ fg: Theme.textMuted }}>commands</span>
|
||||
ctrl+p <span style={{ fg: Theme.textMuted }}>commands</span>
|
||||
</text>
|
||||
</Match>
|
||||
</Switch>
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -138,13 +138,17 @@ export function Session() {
|
|||
flexGrow={1}
|
||||
>
|
||||
<For each={messages()}>
|
||||
{(message) => (
|
||||
{(message, index) => (
|
||||
<Switch>
|
||||
<Match when={message.role === "user"}>
|
||||
<UserMessage message={message as UserMessage} parts={sync.data.part[message.id] ?? []} />
|
||||
</Match>
|
||||
<Match when={message.role === "assistant"}>
|
||||
<AssistantMessage message={message as AssistantMessage} parts={sync.data.part[message.id] ?? []} />
|
||||
<AssistantMessage
|
||||
last={index() === messages().length - 1}
|
||||
message={message as AssistantMessage}
|
||||
parts={sync.data.part[message.id] ?? []}
|
||||
/>
|
||||
</Match>
|
||||
</Switch>
|
||||
)}
|
||||
|
|
@ -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[] }) {
|
|||
<text fg={Theme.textMuted}>{props.message.error?.data.message}</text>
|
||||
</box>
|
||||
</Show>
|
||||
<Show when={!props.message.time.completed}>
|
||||
<Show when={!props.message.time.completed || (props.last && props.message.finish === "tool-calls")}>
|
||||
<box
|
||||
paddingLeft={2}
|
||||
marginTop={1}
|
||||
|
|
@ -262,7 +266,7 @@ function resize(el: BoxRenderable) {
|
|||
const index = children.indexOf(el)
|
||||
const previous = children[index - 1]
|
||||
if (!previous) return
|
||||
if (previous.height > 1) {
|
||||
if (previous.height > 1 || previous.marginTop === 1) {
|
||||
el.marginTop = 1
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue