refactor(tui): model editable slash commands

This commit is contained in:
Shoubhit Dash 2026-05-21 17:12:57 +05:30
parent b5c6763d11
commit c7a17aea4b
4 changed files with 24 additions and 32 deletions

View file

@ -553,11 +553,10 @@ export function Autocomplete(props: {
const commands = createMemo((): AutocompleteOption[] => {
const results: AutocompleteOption[] = slashes().map((command) => {
const name = command.display.trim().slice(1)
if (name !== "compact") return command
if (!command.input) return command
return {
...command,
onSelect: () => insertSlashCommand(name),
onSelect: () => insertSlashCommand(command.name),
}
})

View file

@ -1132,6 +1132,8 @@ export function Prompt(props: PromptProps) {
]
: []
const slash = parseSlashCommand(inputText)
if (store.mode === "shell") {
void sdk.client.session.shell({
sessionID,
@ -1143,34 +1145,20 @@ export function Prompt(props: PromptProps) {
command: inputText,
})
setStore("mode", "normal")
} else if (isCompactSlash(inputText)) {
const instructions = slashArguments(inputText).trim()
const payload: Parameters<typeof sdk.client.session.summarize>[0] & { $body_instructions?: string } = {
} else if (slash && (slash.name === "compact" || slash.name === "summarize")) {
const instructions = slash.arguments.trim()
const payload = {
sessionID,
modelID: selectedModel.modelID,
providerID: selectedModel.providerID,
...(instructions ? { $body_instructions: instructions } : {}),
}
} satisfies Parameters<typeof sdk.client.session.summarize>[0] & { $body_instructions?: string }
void sdk.client.session.summarize(payload)
} else if (
inputText.startsWith("/") &&
iife(() => {
const firstLine = inputText.split("\n")[0]
const command = firstLine.split(" ")[0].slice(1)
return sync.data.command.some((x) => x.name === command)
})
) {
// Parse command from first line, preserve multi-line content in arguments
const firstLineEnd = inputText.indexOf("\n")
const firstLine = firstLineEnd === -1 ? inputText : inputText.slice(0, firstLineEnd)
const [command, ...firstLineArgs] = firstLine.split(" ")
const restOfInput = firstLineEnd === -1 ? "" : inputText.slice(firstLineEnd + 1)
const args = firstLineArgs.join(" ") + (restOfInput ? "\n" + restOfInput : "")
} else if (slash && sync.data.command.some((x) => x.name === slash.name)) {
void sdk.client.session.command({
sessionID,
command: command.slice(1),
arguments: args,
command: slash.name,
arguments: slash.arguments,
agent: agent.name,
model: `${selectedModel.providerID}/${selectedModel.modelID}`,
messageID,
@ -1230,17 +1218,16 @@ export function Prompt(props: PromptProps) {
return true
}
function isCompactSlash(text: string) {
const command = text.split("\n")[0].split(" ")[0].slice(1)
return text.startsWith("/") && (command === "compact" || command === "summarize")
}
function slashArguments(text: string) {
function parseSlashCommand(text: string) {
if (!text.startsWith("/")) return
const firstLineEnd = text.indexOf("\n")
const firstLine = firstLineEnd === -1 ? text : text.slice(0, firstLineEnd)
const [, ...firstLineArgs] = firstLine.split(" ")
const [command, ...firstLineArgs] = firstLine.split(" ")
const restOfInput = firstLineEnd === -1 ? "" : text.slice(firstLineEnd + 1)
return firstLineArgs.join(" ") + (restOfInput ? "\n" + restOfInput : "")
return {
name: command.slice(1),
arguments: firstLineArgs.join(" ") + (restOfInput ? "\n" + restOfInput : ""),
}
}
const exit = useExit()

View file

@ -25,9 +25,11 @@ export { useBindings, useKeymapSelector }
export type OpenTuiKeymap = ReturnType<typeof useKeymap>
type OpencodeModeStack = ReturnType<typeof createOpencodeModeStack>
type CommandSlashEntry = {
name: string
display: string
description?: string
aliases?: string[]
input?: boolean
onSelect: () => void
}
type Command = ReturnType<OpenTuiKeymap["getCommands"]>[number]
@ -256,6 +258,7 @@ export function useCommandSlashes(): Accessor<readonly CommandSlashEntry[]> {
if (typeof slashName !== "string" || !slashName) return []
const slashAliases = entry.command.slashAliases
return {
name: slashName,
display: `/${slashName}`,
description:
typeof entry.command.desc === "string"
@ -266,6 +269,7 @@ export function useCommandSlashes(): Accessor<readonly CommandSlashEntry[]> {
aliases: Array.isArray(slashAliases)
? slashAliases.filter((alias): alias is string => typeof alias === "string").map((alias) => `/${alias}`)
: undefined,
input: entry.command.slashInput === true,
onSelect: () => keymap.dispatchCommand(entry.command.name),
}
}),

View file

@ -546,6 +546,7 @@ export function Session() {
slash: {
name: "compact",
aliases: ["summarize"],
input: true,
},
run: () => {
const selectedModel = local.model.current()
@ -1046,6 +1047,7 @@ export function Session() {
desc: "description" in command ? command.description : undefined,
slashName: "slash" in command ? command.slash?.name : undefined,
slashAliases: "slash" in command ? command.slash?.aliases : undefined,
slashInput: "slash" in command ? command.slash?.input : undefined,
...command,
})),
)