fix(tui): keep compact slash editable
This commit is contained in:
parent
fbce48f597
commit
25f92bba8d
3 changed files with 21 additions and 90 deletions
|
|
@ -18,7 +18,6 @@ const optimistic: Array<{
|
||||||
const optimisticSeeded: boolean[] = []
|
const optimisticSeeded: boolean[] = []
|
||||||
const storedSessions: Record<string, Array<{ id: string; title?: string }>> = {}
|
const storedSessions: Record<string, Array<{ id: string; title?: string }>> = {}
|
||||||
const promoted: Array<{ directory: string; sessionID: string }> = []
|
const promoted: Array<{ directory: string; sessionID: string }> = []
|
||||||
const sentSummaries: Array<{ sessionID: string; instructions?: string }> = []
|
|
||||||
const sentShell: string[] = []
|
const sentShell: string[] = []
|
||||||
const syncedDirectories: string[] = []
|
const syncedDirectories: string[] = []
|
||||||
|
|
||||||
|
|
@ -26,7 +25,7 @@ let params: { id?: string } = {}
|
||||||
let selected = "/repo/worktree-a"
|
let selected = "/repo/worktree-a"
|
||||||
let variant: string | undefined
|
let variant: string | undefined
|
||||||
|
|
||||||
let promptValue: Prompt = [{ type: "text", content: "ls", start: 0, end: 2 }]
|
const promptValue: Prompt = [{ type: "text", content: "ls", start: 0, end: 2 }]
|
||||||
|
|
||||||
const clientFor = (directory: string) => {
|
const clientFor = (directory: string) => {
|
||||||
createdClients.push(directory)
|
createdClients.push(directory)
|
||||||
|
|
@ -48,10 +47,6 @@ const clientFor = (directory: string) => {
|
||||||
prompt: async () => ({ data: undefined }),
|
prompt: async () => ({ data: undefined }),
|
||||||
promptAsync: async () => ({ data: undefined }),
|
promptAsync: async () => ({ data: undefined }),
|
||||||
command: async () => ({ data: undefined }),
|
command: async () => ({ data: undefined }),
|
||||||
summarize: async (input: { sessionID: string; instructions?: string }) => {
|
|
||||||
sentSummaries.push({ sessionID: input.sessionID, instructions: input.instructions })
|
|
||||||
return { data: undefined }
|
|
||||||
},
|
|
||||||
abort: async () => ({ data: undefined }),
|
abort: async () => ({ data: undefined }),
|
||||||
},
|
},
|
||||||
worktree: {
|
worktree: {
|
||||||
|
|
@ -213,8 +208,6 @@ beforeEach(() => {
|
||||||
optimistic.length = 0
|
optimistic.length = 0
|
||||||
optimisticSeeded.length = 0
|
optimisticSeeded.length = 0
|
||||||
promoted.length = 0
|
promoted.length = 0
|
||||||
promptValue = [{ type: "text", content: "ls", start: 0, end: 2 }]
|
|
||||||
sentSummaries.length = 0
|
|
||||||
params = {}
|
params = {}
|
||||||
sentShell.length = 0
|
sentShell.length = 0
|
||||||
syncedDirectories.length = 0
|
syncedDirectories.length = 0
|
||||||
|
|
@ -350,30 +343,4 @@ describe("prompt submit worktree selection", () => {
|
||||||
expect(optimisticSeeded).toEqual([true])
|
expect(optimisticSeeded).toEqual([true])
|
||||||
})
|
})
|
||||||
|
|
||||||
test("submits typed compact slash arguments as summarize instructions", async () => {
|
|
||||||
params = { id: "session-1" }
|
|
||||||
promptValue = [{ type: "text", content: "/compact keep unresolved TODOs", start: 0, end: 30 }]
|
|
||||||
|
|
||||||
const submit = createPromptSubmit({
|
|
||||||
info: () => ({ id: "session-1" }),
|
|
||||||
imageAttachments: () => [],
|
|
||||||
commentCount: () => 0,
|
|
||||||
autoAccept: () => false,
|
|
||||||
mode: () => "normal",
|
|
||||||
working: () => false,
|
|
||||||
editor: () => undefined,
|
|
||||||
queueScroll: () => undefined,
|
|
||||||
promptLength: (value) => value.reduce((sum, part) => sum + ("content" in part ? part.content.length : 0), 0),
|
|
||||||
addToHistory: () => undefined,
|
|
||||||
resetHistoryNavigation: () => undefined,
|
|
||||||
setMode: () => undefined,
|
|
||||||
setPopover: () => undefined,
|
|
||||||
onSubmit: () => undefined,
|
|
||||||
})
|
|
||||||
|
|
||||||
await submit.handleSubmit({ preventDefault: () => undefined } as unknown as Event)
|
|
||||||
|
|
||||||
expect(sentSummaries).toEqual([{ sessionID: "session-1", instructions: "keep unresolved TODOs" }])
|
|
||||||
expect(optimistic).toHaveLength(0)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -50,21 +50,6 @@ const draftText = (prompt: Prompt) => prompt.map((part) => ("content" in part ?
|
||||||
|
|
||||||
const draftImages = (prompt: Prompt) => prompt.filter((part): part is ImageAttachmentPart => part.type === "image")
|
const draftImages = (prompt: Prompt) => prompt.filter((part): part is ImageAttachmentPart => part.type === "image")
|
||||||
|
|
||||||
const slashCommand = (text: string) => {
|
|
||||||
if (!text.startsWith("/")) return undefined
|
|
||||||
return text.split("\n")[0].split(" ")[0].slice(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const slashArguments = (text: string) => {
|
|
||||||
const firstLineEnd = text.indexOf("\n")
|
|
||||||
const firstLine = firstLineEnd === -1 ? text : text.slice(0, firstLineEnd)
|
|
||||||
const [, ...firstLineArgs] = firstLine.split(" ")
|
|
||||||
const restOfInput = firstLineEnd === -1 ? "" : text.slice(firstLineEnd + 1)
|
|
||||||
return firstLineArgs.join(" ") + (restOfInput ? "\n" + restOfInput : "")
|
|
||||||
}
|
|
||||||
|
|
||||||
const isCompactSlash = (command: string | undefined) => command === "compact" || command === "summarize"
|
|
||||||
|
|
||||||
export async function sendFollowupDraft(input: FollowupSendInput) {
|
export async function sendFollowupDraft(input: FollowupSendInput) {
|
||||||
const text = draftText(input.draft.prompt)
|
const text = draftText(input.draft.prompt)
|
||||||
const images = draftImages(input.draft.prompt)
|
const images = draftImages(input.draft.prompt)
|
||||||
|
|
@ -86,27 +71,8 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const cmd = slashCommand(text)
|
const [head, ...tail] = text.split(" ")
|
||||||
if (isCompactSlash(cmd)) {
|
const cmd = head?.startsWith("/") ? head.slice(1) : undefined
|
||||||
setBusy()
|
|
||||||
try {
|
|
||||||
if (!(await wait())) {
|
|
||||||
setIdle()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const instructions = slashArguments(text).trim()
|
|
||||||
const payload = instructions
|
|
||||||
? { sessionID: input.draft.sessionID, ...input.draft.model, instructions }
|
|
||||||
: { sessionID: input.draft.sessionID, ...input.draft.model }
|
|
||||||
await input.client.session.summarize(payload)
|
|
||||||
return true
|
|
||||||
} catch (err) {
|
|
||||||
setIdle()
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cmd && input.sync.data.command.find((item) => item.name === cmd)) {
|
if (cmd && input.sync.data.command.find((item) => item.name === cmd)) {
|
||||||
setBusy()
|
setBusy()
|
||||||
try {
|
try {
|
||||||
|
|
@ -118,7 +84,7 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
|
||||||
await input.client.session.command({
|
await input.client.session.command({
|
||||||
sessionID: input.draft.sessionID,
|
sessionID: input.draft.sessionID,
|
||||||
command: cmd,
|
command: cmd,
|
||||||
arguments: slashArguments(text),
|
arguments: tail.join(" "),
|
||||||
agent: input.draft.agent,
|
agent: input.draft.agent,
|
||||||
model: `${input.draft.model.providerID}/${input.draft.model.modelID}`,
|
model: `${input.draft.model.providerID}/${input.draft.model.modelID}`,
|
||||||
variant: input.draft.variant,
|
variant: input.draft.variant,
|
||||||
|
|
@ -487,7 +453,8 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (text.startsWith("/")) {
|
if (text.startsWith("/")) {
|
||||||
const commandName = slashCommand(text) ?? ""
|
const [cmdName, ...args] = text.split(" ")
|
||||||
|
const commandName = cmdName.slice(1)
|
||||||
const customCommand = sync.data.command.find((c) => c.name === commandName)
|
const customCommand = sync.data.command.find((c) => c.name === commandName)
|
||||||
if (customCommand) {
|
if (customCommand) {
|
||||||
clearInput()
|
clearInput()
|
||||||
|
|
@ -495,7 +462,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
||||||
.command({
|
.command({
|
||||||
sessionID: session.id,
|
sessionID: session.id,
|
||||||
command: commandName,
|
command: commandName,
|
||||||
arguments: slashArguments(text),
|
arguments: args.join(" "),
|
||||||
agent,
|
agent,
|
||||||
model: `${model.providerID}/${model.modelID}`,
|
model: `${model.providerID}/${model.modelID}`,
|
||||||
variant,
|
variant,
|
||||||
|
|
@ -513,22 +480,6 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
||||||
description: formatServerError(err, language.t, language.t("common.requestFailed")),
|
description: formatServerError(err, language.t, language.t("common.requestFailed")),
|
||||||
})
|
})
|
||||||
restoreInput()
|
restoreInput()
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCompactSlash(commandName)) {
|
|
||||||
clearInput()
|
|
||||||
const instructions = slashArguments(text).trim()
|
|
||||||
const payload = instructions
|
|
||||||
? { sessionID: session.id, ...model, instructions }
|
|
||||||
: { sessionID: session.id, ...model }
|
|
||||||
client.session.summarize(payload).catch((err) => {
|
|
||||||
showToast({
|
|
||||||
title: language.t("prompt.toast.promptSendFailed.title"),
|
|
||||||
description: errorMessage(err),
|
|
||||||
})
|
|
||||||
restoreInput()
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -544,7 +544,20 @@ export function Autocomplete(props: {
|
||||||
)
|
)
|
||||||
|
|
||||||
const commands = createMemo((): AutocompleteOption[] => {
|
const commands = createMemo((): AutocompleteOption[] => {
|
||||||
const results: AutocompleteOption[] = [...slashes()]
|
const results: AutocompleteOption[] = slashes().map((command) => {
|
||||||
|
const name = command.display.trim().slice(1)
|
||||||
|
if (name !== "compact") return command
|
||||||
|
return {
|
||||||
|
...command,
|
||||||
|
onSelect: () => {
|
||||||
|
const newText = `/${name} `
|
||||||
|
const cursor = props.input().logicalCursor
|
||||||
|
props.input().deleteRange(0, 0, cursor.row, cursor.col)
|
||||||
|
props.input().insertText(newText)
|
||||||
|
props.input().cursorOffset = Bun.stringWidth(newText)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
for (const serverCommand of sync.data.command) {
|
for (const serverCommand of sync.data.command) {
|
||||||
if (serverCommand.source === "skill") continue
|
if (serverCommand.source === "skill") continue
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue