fix(tui): restore reverted prompt in v2
This commit is contained in:
parent
8f4b62eb49
commit
cdb8fc9960
3 changed files with 114 additions and 4 deletions
|
|
@ -72,6 +72,7 @@ import { usePathFormatter } from "../../context/path-format"
|
||||||
import { LocationProvider } from "../../context/location"
|
import { LocationProvider } from "../../context/location"
|
||||||
import { createSessionRows, type PartRef, type SessionRow } from "./rows"
|
import { createSessionRows, type PartRef, type SessionRow } from "./rows"
|
||||||
import { switchLabel } from "../../util/model"
|
import { switchLabel } from "../../util/model"
|
||||||
|
import { revertedPrompt } from "../../util/revert-prompt"
|
||||||
|
|
||||||
addDefaultParsers(parsers.parsers)
|
addDefaultParsers(parsers.parsers)
|
||||||
|
|
||||||
|
|
@ -398,17 +399,21 @@ export function Session() {
|
||||||
title: "Undo previous message",
|
title: "Undo previous message",
|
||||||
value: "session.undo",
|
value: "session.undo",
|
||||||
category: "Session",
|
category: "Session",
|
||||||
slash: { name: "undo" },
|
slash: { name: "undo", aliases: ["revert"] },
|
||||||
run: () => {
|
run: () => {
|
||||||
void (async () => {
|
void (async () => {
|
||||||
|
const current = prompt?.current
|
||||||
|
if (current && current.parts.length === 0 && ["/undo", "/revert"].includes(current.input.trim())) {
|
||||||
|
prompt?.reset()
|
||||||
|
}
|
||||||
const boundary = session()?.revert?.messageID
|
const boundary = session()?.revert?.messageID
|
||||||
const list = messages()
|
const list = messages()
|
||||||
let target: string | undefined
|
let target: SessionMessageUser | undefined
|
||||||
for (let i = list.length - 1; i >= 0; i--) {
|
for (let i = list.length - 1; i >= 0; i--) {
|
||||||
const message = list[i]
|
const message = list[i]
|
||||||
if (message.type !== "user" || !message.text.trim()) continue
|
if (message.type !== "user" || !message.text.trim()) continue
|
||||||
if (boundary && message.id >= boundary) continue
|
if (boundary && message.id >= boundary) continue
|
||||||
target = message.id
|
target = message
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if (!target) {
|
if (!target) {
|
||||||
|
|
@ -416,11 +421,15 @@ export function Session() {
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const error = await sdk.api.session.revertStage({ sessionID: route.sessionID, messageID: target }).then(
|
const error = await sdk.api.session.revertStage({ sessionID: route.sessionID, messageID: target.id }).then(
|
||||||
() => undefined,
|
() => undefined,
|
||||||
(error) => error,
|
(error) => error,
|
||||||
)
|
)
|
||||||
if (error) toast.show({ message: errorMessage(error), variant: "error", duration: 5000 })
|
if (error) toast.show({ message: errorMessage(error), variant: "error", duration: 5000 })
|
||||||
|
if (!error && prompt) {
|
||||||
|
const restored = revertedPrompt(prompt.current, target)
|
||||||
|
if (restored) prompt.set(restored)
|
||||||
|
}
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
})()
|
})()
|
||||||
},
|
},
|
||||||
|
|
|
||||||
40
packages/tui/src/util/revert-prompt.ts
Normal file
40
packages/tui/src/util/revert-prompt.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import type { SessionMessageUser } from "@opencode-ai/sdk/v2"
|
||||||
|
import type { PromptInfo } from "../prompt/history"
|
||||||
|
|
||||||
|
export function revertedPrompt(current: PromptInfo, message: SessionMessageUser): PromptInfo | undefined {
|
||||||
|
if (current.input || current.parts.length) return
|
||||||
|
|
||||||
|
return {
|
||||||
|
input: message.text,
|
||||||
|
parts: [
|
||||||
|
...(message.files ?? []).map((file) => ({
|
||||||
|
type: "file" as const,
|
||||||
|
mime: file.mime,
|
||||||
|
filename: file.name,
|
||||||
|
url: file.uri,
|
||||||
|
source: file.source
|
||||||
|
? {
|
||||||
|
type: "file" as const,
|
||||||
|
path: file.name ?? file.uri,
|
||||||
|
text: {
|
||||||
|
start: file.source.start,
|
||||||
|
end: file.source.end,
|
||||||
|
value: file.source.text,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
})),
|
||||||
|
...(message.agents ?? []).map((agent) => ({
|
||||||
|
type: "agent" as const,
|
||||||
|
name: agent.name,
|
||||||
|
source: agent.source
|
||||||
|
? {
|
||||||
|
start: agent.source.start,
|
||||||
|
end: agent.source.end,
|
||||||
|
value: agent.source.text,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
61
packages/tui/test/util/revert-prompt.test.ts
Normal file
61
packages/tui/test/util/revert-prompt.test.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { describe, expect, test } from "bun:test"
|
||||||
|
import type { SessionMessageUser } from "@opencode-ai/sdk/v2"
|
||||||
|
import { revertedPrompt } from "../../src/util/revert-prompt"
|
||||||
|
|
||||||
|
const message: SessionMessageUser = {
|
||||||
|
id: "message-1",
|
||||||
|
type: "user",
|
||||||
|
text: "Fix the tests",
|
||||||
|
time: { created: 1 },
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
uri: "file:///repo/test.ts",
|
||||||
|
mime: "text/typescript",
|
||||||
|
name: "test.ts",
|
||||||
|
source: { start: 0, end: 8, text: "@test.ts" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
agents: [{ name: "review", source: { start: 9, end: 16, text: "@review" } }],
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("reverted prompt", () => {
|
||||||
|
test("restores the reverted user message into an empty prompt", () => {
|
||||||
|
expect(revertedPrompt({ input: "", parts: [] }, message)).toEqual({
|
||||||
|
input: "Fix the tests",
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
type: "file",
|
||||||
|
mime: "text/typescript",
|
||||||
|
filename: "test.ts",
|
||||||
|
url: "file:///repo/test.ts",
|
||||||
|
source: {
|
||||||
|
type: "file",
|
||||||
|
path: "test.ts",
|
||||||
|
text: { start: 0, end: 8, value: "@test.ts" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "agent",
|
||||||
|
name: "review",
|
||||||
|
source: { start: 9, end: 16, value: "@review" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("preserves an existing text draft", () => {
|
||||||
|
expect(revertedPrompt({ input: "Keep this", parts: [] }, message)).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("preserves an existing attachment draft", () => {
|
||||||
|
expect(
|
||||||
|
revertedPrompt(
|
||||||
|
{
|
||||||
|
input: "",
|
||||||
|
parts: [{ type: "agent", name: "build" }],
|
||||||
|
},
|
||||||
|
message,
|
||||||
|
),
|
||||||
|
).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Add table
Add a link
Reference in a new issue