|
|
|
|
@@ -19,35 +19,14 @@ import { showToast } from "@opencode-ai/ui/toast"
|
|
|
|
|
import { findLast } from "@opencode-ai/util/array"
|
|
|
|
|
import { extractPromptFromParts } from "@/utils/prompt"
|
|
|
|
|
import { UserMessage } from "@opencode-ai/sdk/v2"
|
|
|
|
|
import { combineCommandSections } from "@/pages/session/helpers"
|
|
|
|
|
import { canAddSelectionContext } from "@/pages/session/session-command-helpers"
|
|
|
|
|
|
|
|
|
|
export type SessionCommandContext = {
|
|
|
|
|
command: ReturnType<typeof useCommand>
|
|
|
|
|
dialog: ReturnType<typeof useDialog>
|
|
|
|
|
file: ReturnType<typeof useFile>
|
|
|
|
|
language: ReturnType<typeof useLanguage>
|
|
|
|
|
local: ReturnType<typeof useLocal>
|
|
|
|
|
permission: ReturnType<typeof usePermission>
|
|
|
|
|
prompt: ReturnType<typeof usePrompt>
|
|
|
|
|
sdk: ReturnType<typeof useSDK>
|
|
|
|
|
sync: ReturnType<typeof useSync>
|
|
|
|
|
terminal: ReturnType<typeof useTerminal>
|
|
|
|
|
layout: ReturnType<typeof useLayout>
|
|
|
|
|
params: ReturnType<typeof useParams>
|
|
|
|
|
navigate: ReturnType<typeof useNavigate>
|
|
|
|
|
tabs: () => ReturnType<ReturnType<typeof useLayout>["tabs"]>
|
|
|
|
|
view: () => ReturnType<ReturnType<typeof useLayout>["view"]>
|
|
|
|
|
info: () => { revert?: { messageID?: string }; share?: { url?: string } } | undefined
|
|
|
|
|
status: () => { type: string }
|
|
|
|
|
userMessages: () => UserMessage[]
|
|
|
|
|
visibleUserMessages: () => UserMessage[]
|
|
|
|
|
activeMessage: () => UserMessage | undefined
|
|
|
|
|
showAllFiles: () => void
|
|
|
|
|
navigateMessageByOffset: (offset: number) => void
|
|
|
|
|
setExpanded: (id: string, fn: (open: boolean | undefined) => boolean) => void
|
|
|
|
|
setActiveMessage: (message: UserMessage | undefined) => void
|
|
|
|
|
addSelectionToContext: (path: string, selection: FileSelection) => void
|
|
|
|
|
focusInput: () => void
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -58,45 +37,88 @@ const withCategory = (category: string) => {
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const useSessionCommands = (input: SessionCommandContext) => {
|
|
|
|
|
const sessionCommand = withCategory(input.language.t("command.category.session"))
|
|
|
|
|
const fileCommand = withCategory(input.language.t("command.category.file"))
|
|
|
|
|
const contextCommand = withCategory(input.language.t("command.category.context"))
|
|
|
|
|
const viewCommand = withCategory(input.language.t("command.category.view"))
|
|
|
|
|
const terminalCommand = withCategory(input.language.t("command.category.terminal"))
|
|
|
|
|
const modelCommand = withCategory(input.language.t("command.category.model"))
|
|
|
|
|
const mcpCommand = withCategory(input.language.t("command.category.mcp"))
|
|
|
|
|
const agentCommand = withCategory(input.language.t("command.category.agent"))
|
|
|
|
|
const permissionsCommand = withCategory(input.language.t("command.category.permissions"))
|
|
|
|
|
export const useSessionCommands = (args: SessionCommandContext) => {
|
|
|
|
|
const command = useCommand()
|
|
|
|
|
const dialog = useDialog()
|
|
|
|
|
const file = useFile()
|
|
|
|
|
const language = useLanguage()
|
|
|
|
|
const local = useLocal()
|
|
|
|
|
const permission = usePermission()
|
|
|
|
|
const prompt = usePrompt()
|
|
|
|
|
const sdk = useSDK()
|
|
|
|
|
const sync = useSync()
|
|
|
|
|
const terminal = useTerminal()
|
|
|
|
|
const layout = useLayout()
|
|
|
|
|
const params = useParams()
|
|
|
|
|
const navigate = useNavigate()
|
|
|
|
|
|
|
|
|
|
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
|
|
|
|
const tabs = createMemo(() => layout.tabs(sessionKey))
|
|
|
|
|
const view = createMemo(() => layout.view(sessionKey))
|
|
|
|
|
const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
|
|
|
|
|
const idle = { type: "idle" as const }
|
|
|
|
|
const status = createMemo(() => sync.data.session_status[params.id ?? ""] ?? idle)
|
|
|
|
|
const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : []))
|
|
|
|
|
const userMessages = createMemo(() => messages().filter((m) => m.role === "user") as UserMessage[])
|
|
|
|
|
const visibleUserMessages = createMemo(() => {
|
|
|
|
|
const revert = info()?.revert?.messageID
|
|
|
|
|
if (!revert) return userMessages()
|
|
|
|
|
return userMessages().filter((m) => m.id < revert)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const selectionPreview = (path: string, selection: FileSelection) => {
|
|
|
|
|
const content = file.get(path)?.content?.content
|
|
|
|
|
if (!content) return undefined
|
|
|
|
|
const start = Math.max(1, Math.min(selection.startLine, selection.endLine))
|
|
|
|
|
const end = Math.max(selection.startLine, selection.endLine)
|
|
|
|
|
const lines = content.split("\n").slice(start - 1, end)
|
|
|
|
|
if (lines.length === 0) return undefined
|
|
|
|
|
return lines.slice(0, 2).join("\n")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const addSelectionToContext = (path: string, selection: FileSelection) => {
|
|
|
|
|
const preview = selectionPreview(path, selection)
|
|
|
|
|
prompt.context.add({ type: "file", path, selection, preview })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sessionCommand = withCategory(language.t("command.category.session"))
|
|
|
|
|
const fileCommand = withCategory(language.t("command.category.file"))
|
|
|
|
|
const contextCommand = withCategory(language.t("command.category.context"))
|
|
|
|
|
const viewCommand = withCategory(language.t("command.category.view"))
|
|
|
|
|
const terminalCommand = withCategory(language.t("command.category.terminal"))
|
|
|
|
|
const modelCommand = withCategory(language.t("command.category.model"))
|
|
|
|
|
const mcpCommand = withCategory(language.t("command.category.mcp"))
|
|
|
|
|
const agentCommand = withCategory(language.t("command.category.agent"))
|
|
|
|
|
const permissionsCommand = withCategory(language.t("command.category.permissions"))
|
|
|
|
|
|
|
|
|
|
const sessionCommands = createMemo(() => [
|
|
|
|
|
sessionCommand({
|
|
|
|
|
id: "session.new",
|
|
|
|
|
title: input.language.t("command.session.new"),
|
|
|
|
|
title: language.t("command.session.new"),
|
|
|
|
|
keybind: "mod+shift+s",
|
|
|
|
|
slash: "new",
|
|
|
|
|
onSelect: () => input.navigate(`/${input.params.dir}/session`),
|
|
|
|
|
onSelect: () => navigate(`/${params.dir}/session`),
|
|
|
|
|
}),
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
const fileCommands = createMemo(() => [
|
|
|
|
|
fileCommand({
|
|
|
|
|
id: "file.open",
|
|
|
|
|
title: input.language.t("command.file.open"),
|
|
|
|
|
description: input.language.t("palette.search.placeholder"),
|
|
|
|
|
title: language.t("command.file.open"),
|
|
|
|
|
description: language.t("palette.search.placeholder"),
|
|
|
|
|
keybind: "mod+p",
|
|
|
|
|
slash: "open",
|
|
|
|
|
onSelect: () => input.dialog.show(() => <DialogSelectFile onOpenFile={input.showAllFiles} />),
|
|
|
|
|
onSelect: () => dialog.show(() => <DialogSelectFile onOpenFile={args.showAllFiles} />),
|
|
|
|
|
}),
|
|
|
|
|
fileCommand({
|
|
|
|
|
id: "tab.close",
|
|
|
|
|
title: input.language.t("command.tab.close"),
|
|
|
|
|
title: language.t("command.tab.close"),
|
|
|
|
|
keybind: "mod+w",
|
|
|
|
|
disabled: !input.tabs().active(),
|
|
|
|
|
disabled: !tabs().active(),
|
|
|
|
|
onSelect: () => {
|
|
|
|
|
const active = input.tabs().active()
|
|
|
|
|
const active = tabs().active()
|
|
|
|
|
if (!active) return
|
|
|
|
|
input.tabs().close(active)
|
|
|
|
|
tabs().close(active)
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
])
|
|
|
|
|
@@ -104,30 +126,30 @@ export const useSessionCommands = (input: SessionCommandContext) => {
|
|
|
|
|
const contextCommands = createMemo(() => [
|
|
|
|
|
contextCommand({
|
|
|
|
|
id: "context.addSelection",
|
|
|
|
|
title: input.language.t("command.context.addSelection"),
|
|
|
|
|
description: input.language.t("command.context.addSelection.description"),
|
|
|
|
|
title: language.t("command.context.addSelection"),
|
|
|
|
|
description: language.t("command.context.addSelection.description"),
|
|
|
|
|
keybind: "mod+shift+l",
|
|
|
|
|
disabled: !canAddSelectionContext({
|
|
|
|
|
active: input.tabs().active(),
|
|
|
|
|
pathFromTab: input.file.pathFromTab,
|
|
|
|
|
selectedLines: input.file.selectedLines,
|
|
|
|
|
active: tabs().active(),
|
|
|
|
|
pathFromTab: file.pathFromTab,
|
|
|
|
|
selectedLines: file.selectedLines,
|
|
|
|
|
}),
|
|
|
|
|
onSelect: () => {
|
|
|
|
|
const active = input.tabs().active()
|
|
|
|
|
const active = tabs().active()
|
|
|
|
|
if (!active) return
|
|
|
|
|
const path = input.file.pathFromTab(active)
|
|
|
|
|
const path = file.pathFromTab(active)
|
|
|
|
|
if (!path) return
|
|
|
|
|
|
|
|
|
|
const range = input.file.selectedLines(path) as SelectedLineRange | null | undefined
|
|
|
|
|
const range = file.selectedLines(path) as SelectedLineRange | null | undefined
|
|
|
|
|
if (!range) {
|
|
|
|
|
showToast({
|
|
|
|
|
title: input.language.t("toast.context.noLineSelection.title"),
|
|
|
|
|
description: input.language.t("toast.context.noLineSelection.description"),
|
|
|
|
|
title: language.t("toast.context.noLineSelection.title"),
|
|
|
|
|
description: language.t("toast.context.noLineSelection.description"),
|
|
|
|
|
})
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
input.addSelectionToContext(path, selectionFromLines(range))
|
|
|
|
|
addSelectionToContext(path, selectionFromLines(range))
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
])
|
|
|
|
|
@@ -135,50 +157,50 @@ export const useSessionCommands = (input: SessionCommandContext) => {
|
|
|
|
|
const viewCommands = createMemo(() => [
|
|
|
|
|
viewCommand({
|
|
|
|
|
id: "terminal.toggle",
|
|
|
|
|
title: input.language.t("command.terminal.toggle"),
|
|
|
|
|
title: language.t("command.terminal.toggle"),
|
|
|
|
|
keybind: "ctrl+`",
|
|
|
|
|
slash: "terminal",
|
|
|
|
|
onSelect: () => input.view().terminal.toggle(),
|
|
|
|
|
onSelect: () => view().terminal.toggle(),
|
|
|
|
|
}),
|
|
|
|
|
viewCommand({
|
|
|
|
|
id: "review.toggle",
|
|
|
|
|
title: input.language.t("command.review.toggle"),
|
|
|
|
|
title: language.t("command.review.toggle"),
|
|
|
|
|
keybind: "mod+shift+r",
|
|
|
|
|
onSelect: () => input.view().reviewPanel.toggle(),
|
|
|
|
|
onSelect: () => view().reviewPanel.toggle(),
|
|
|
|
|
}),
|
|
|
|
|
viewCommand({
|
|
|
|
|
id: "fileTree.toggle",
|
|
|
|
|
title: input.language.t("command.fileTree.toggle"),
|
|
|
|
|
title: language.t("command.fileTree.toggle"),
|
|
|
|
|
keybind: "mod+\\",
|
|
|
|
|
onSelect: () => input.layout.fileTree.toggle(),
|
|
|
|
|
onSelect: () => layout.fileTree.toggle(),
|
|
|
|
|
}),
|
|
|
|
|
viewCommand({
|
|
|
|
|
id: "input.focus",
|
|
|
|
|
title: input.language.t("command.input.focus"),
|
|
|
|
|
title: language.t("command.input.focus"),
|
|
|
|
|
keybind: "ctrl+l",
|
|
|
|
|
onSelect: () => input.focusInput(),
|
|
|
|
|
onSelect: () => args.focusInput(),
|
|
|
|
|
}),
|
|
|
|
|
terminalCommand({
|
|
|
|
|
id: "terminal.new",
|
|
|
|
|
title: input.language.t("command.terminal.new"),
|
|
|
|
|
description: input.language.t("command.terminal.new.description"),
|
|
|
|
|
title: language.t("command.terminal.new"),
|
|
|
|
|
description: language.t("command.terminal.new.description"),
|
|
|
|
|
keybind: "ctrl+alt+t",
|
|
|
|
|
onSelect: () => {
|
|
|
|
|
if (input.terminal.all().length > 0) input.terminal.new()
|
|
|
|
|
input.view().terminal.open()
|
|
|
|
|
if (terminal.all().length > 0) terminal.new()
|
|
|
|
|
view().terminal.open()
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
viewCommand({
|
|
|
|
|
id: "steps.toggle",
|
|
|
|
|
title: input.language.t("command.steps.toggle"),
|
|
|
|
|
description: input.language.t("command.steps.toggle.description"),
|
|
|
|
|
title: language.t("command.steps.toggle"),
|
|
|
|
|
description: language.t("command.steps.toggle.description"),
|
|
|
|
|
keybind: "mod+e",
|
|
|
|
|
slash: "steps",
|
|
|
|
|
disabled: !input.params.id,
|
|
|
|
|
disabled: !params.id,
|
|
|
|
|
onSelect: () => {
|
|
|
|
|
const msg = input.activeMessage()
|
|
|
|
|
const msg = args.activeMessage()
|
|
|
|
|
if (!msg) return
|
|
|
|
|
input.setExpanded(msg.id, (open: boolean | undefined) => !open)
|
|
|
|
|
args.setExpanded(msg.id, (open: boolean | undefined) => !open)
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
])
|
|
|
|
|
@@ -186,61 +208,61 @@ export const useSessionCommands = (input: SessionCommandContext) => {
|
|
|
|
|
const messageCommands = createMemo(() => [
|
|
|
|
|
sessionCommand({
|
|
|
|
|
id: "message.previous",
|
|
|
|
|
title: input.language.t("command.message.previous"),
|
|
|
|
|
description: input.language.t("command.message.previous.description"),
|
|
|
|
|
title: language.t("command.message.previous"),
|
|
|
|
|
description: language.t("command.message.previous.description"),
|
|
|
|
|
keybind: "mod+arrowup",
|
|
|
|
|
disabled: !input.params.id,
|
|
|
|
|
onSelect: () => input.navigateMessageByOffset(-1),
|
|
|
|
|
disabled: !params.id,
|
|
|
|
|
onSelect: () => args.navigateMessageByOffset(-1),
|
|
|
|
|
}),
|
|
|
|
|
sessionCommand({
|
|
|
|
|
id: "message.next",
|
|
|
|
|
title: input.language.t("command.message.next"),
|
|
|
|
|
description: input.language.t("command.message.next.description"),
|
|
|
|
|
title: language.t("command.message.next"),
|
|
|
|
|
description: language.t("command.message.next.description"),
|
|
|
|
|
keybind: "mod+arrowdown",
|
|
|
|
|
disabled: !input.params.id,
|
|
|
|
|
onSelect: () => input.navigateMessageByOffset(1),
|
|
|
|
|
disabled: !params.id,
|
|
|
|
|
onSelect: () => args.navigateMessageByOffset(1),
|
|
|
|
|
}),
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
const agentCommands = createMemo(() => [
|
|
|
|
|
modelCommand({
|
|
|
|
|
id: "model.choose",
|
|
|
|
|
title: input.language.t("command.model.choose"),
|
|
|
|
|
description: input.language.t("command.model.choose.description"),
|
|
|
|
|
title: language.t("command.model.choose"),
|
|
|
|
|
description: language.t("command.model.choose.description"),
|
|
|
|
|
keybind: "mod+'",
|
|
|
|
|
slash: "model",
|
|
|
|
|
onSelect: () => input.dialog.show(() => <DialogSelectModel />),
|
|
|
|
|
onSelect: () => dialog.show(() => <DialogSelectModel />),
|
|
|
|
|
}),
|
|
|
|
|
mcpCommand({
|
|
|
|
|
id: "mcp.toggle",
|
|
|
|
|
title: input.language.t("command.mcp.toggle"),
|
|
|
|
|
description: input.language.t("command.mcp.toggle.description"),
|
|
|
|
|
title: language.t("command.mcp.toggle"),
|
|
|
|
|
description: language.t("command.mcp.toggle.description"),
|
|
|
|
|
keybind: "mod+;",
|
|
|
|
|
slash: "mcp",
|
|
|
|
|
onSelect: () => input.dialog.show(() => <DialogSelectMcp />),
|
|
|
|
|
onSelect: () => dialog.show(() => <DialogSelectMcp />),
|
|
|
|
|
}),
|
|
|
|
|
agentCommand({
|
|
|
|
|
id: "agent.cycle",
|
|
|
|
|
title: input.language.t("command.agent.cycle"),
|
|
|
|
|
description: input.language.t("command.agent.cycle.description"),
|
|
|
|
|
title: language.t("command.agent.cycle"),
|
|
|
|
|
description: language.t("command.agent.cycle.description"),
|
|
|
|
|
keybind: "mod+.",
|
|
|
|
|
slash: "agent",
|
|
|
|
|
onSelect: () => input.local.agent.move(1),
|
|
|
|
|
onSelect: () => local.agent.move(1),
|
|
|
|
|
}),
|
|
|
|
|
agentCommand({
|
|
|
|
|
id: "agent.cycle.reverse",
|
|
|
|
|
title: input.language.t("command.agent.cycle.reverse"),
|
|
|
|
|
description: input.language.t("command.agent.cycle.reverse.description"),
|
|
|
|
|
title: language.t("command.agent.cycle.reverse"),
|
|
|
|
|
description: language.t("command.agent.cycle.reverse.description"),
|
|
|
|
|
keybind: "shift+mod+.",
|
|
|
|
|
onSelect: () => input.local.agent.move(-1),
|
|
|
|
|
onSelect: () => local.agent.move(-1),
|
|
|
|
|
}),
|
|
|
|
|
modelCommand({
|
|
|
|
|
id: "model.variant.cycle",
|
|
|
|
|
title: input.language.t("command.model.variant.cycle"),
|
|
|
|
|
description: input.language.t("command.model.variant.cycle.description"),
|
|
|
|
|
title: language.t("command.model.variant.cycle"),
|
|
|
|
|
description: language.t("command.model.variant.cycle.description"),
|
|
|
|
|
keybind: "shift+mod+d",
|
|
|
|
|
onSelect: () => {
|
|
|
|
|
input.local.model.variant.cycle()
|
|
|
|
|
local.model.variant.cycle()
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
])
|
|
|
|
|
@@ -249,22 +271,22 @@ export const useSessionCommands = (input: SessionCommandContext) => {
|
|
|
|
|
permissionsCommand({
|
|
|
|
|
id: "permissions.autoaccept",
|
|
|
|
|
title:
|
|
|
|
|
input.params.id && input.permission.isAutoAccepting(input.params.id, input.sdk.directory)
|
|
|
|
|
? input.language.t("command.permissions.autoaccept.disable")
|
|
|
|
|
: input.language.t("command.permissions.autoaccept.enable"),
|
|
|
|
|
params.id && permission.isAutoAccepting(params.id, sdk.directory)
|
|
|
|
|
? language.t("command.permissions.autoaccept.disable")
|
|
|
|
|
: language.t("command.permissions.autoaccept.enable"),
|
|
|
|
|
keybind: "mod+shift+a",
|
|
|
|
|
disabled: !input.params.id || !input.permission.permissionsEnabled(),
|
|
|
|
|
disabled: !params.id || !permission.permissionsEnabled(),
|
|
|
|
|
onSelect: () => {
|
|
|
|
|
const sessionID = input.params.id
|
|
|
|
|
const sessionID = params.id
|
|
|
|
|
if (!sessionID) return
|
|
|
|
|
input.permission.toggleAutoAccept(sessionID, input.sdk.directory)
|
|
|
|
|
permission.toggleAutoAccept(sessionID, sdk.directory)
|
|
|
|
|
showToast({
|
|
|
|
|
title: input.permission.isAutoAccepting(sessionID, input.sdk.directory)
|
|
|
|
|
? input.language.t("toast.permissions.autoaccept.on.title")
|
|
|
|
|
: input.language.t("toast.permissions.autoaccept.off.title"),
|
|
|
|
|
description: input.permission.isAutoAccepting(sessionID, input.sdk.directory)
|
|
|
|
|
? input.language.t("toast.permissions.autoaccept.on.description")
|
|
|
|
|
: input.language.t("toast.permissions.autoaccept.off.description"),
|
|
|
|
|
title: permission.isAutoAccepting(sessionID, sdk.directory)
|
|
|
|
|
? language.t("toast.permissions.autoaccept.on.title")
|
|
|
|
|
: language.t("toast.permissions.autoaccept.off.title"),
|
|
|
|
|
description: permission.isAutoAccepting(sessionID, sdk.directory)
|
|
|
|
|
? language.t("toast.permissions.autoaccept.on.description")
|
|
|
|
|
: language.t("toast.permissions.autoaccept.off.description"),
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
@@ -273,71 +295,71 @@ export const useSessionCommands = (input: SessionCommandContext) => {
|
|
|
|
|
const sessionActionCommands = createMemo(() => [
|
|
|
|
|
sessionCommand({
|
|
|
|
|
id: "session.undo",
|
|
|
|
|
title: input.language.t("command.session.undo"),
|
|
|
|
|
description: input.language.t("command.session.undo.description"),
|
|
|
|
|
title: language.t("command.session.undo"),
|
|
|
|
|
description: language.t("command.session.undo.description"),
|
|
|
|
|
slash: "undo",
|
|
|
|
|
disabled: !input.params.id || input.visibleUserMessages().length === 0,
|
|
|
|
|
disabled: !params.id || visibleUserMessages().length === 0,
|
|
|
|
|
onSelect: async () => {
|
|
|
|
|
const sessionID = input.params.id
|
|
|
|
|
const sessionID = params.id
|
|
|
|
|
if (!sessionID) return
|
|
|
|
|
if (input.status()?.type !== "idle") {
|
|
|
|
|
await input.sdk.client.session.abort({ sessionID }).catch(() => {})
|
|
|
|
|
if (status()?.type !== "idle") {
|
|
|
|
|
await sdk.client.session.abort({ sessionID }).catch(() => {})
|
|
|
|
|
}
|
|
|
|
|
const revert = input.info()?.revert?.messageID
|
|
|
|
|
const message = findLast(input.userMessages(), (x) => !revert || x.id < revert)
|
|
|
|
|
const revert = info()?.revert?.messageID
|
|
|
|
|
const message = findLast(userMessages(), (x) => !revert || x.id < revert)
|
|
|
|
|
if (!message) return
|
|
|
|
|
await input.sdk.client.session.revert({ sessionID, messageID: message.id })
|
|
|
|
|
const parts = input.sync.data.part[message.id]
|
|
|
|
|
await sdk.client.session.revert({ sessionID, messageID: message.id })
|
|
|
|
|
const parts = sync.data.part[message.id]
|
|
|
|
|
if (parts) {
|
|
|
|
|
const restored = extractPromptFromParts(parts, { directory: input.sdk.directory })
|
|
|
|
|
input.prompt.set(restored)
|
|
|
|
|
const restored = extractPromptFromParts(parts, { directory: sdk.directory })
|
|
|
|
|
prompt.set(restored)
|
|
|
|
|
}
|
|
|
|
|
const priorMessage = findLast(input.userMessages(), (x) => x.id < message.id)
|
|
|
|
|
input.setActiveMessage(priorMessage)
|
|
|
|
|
const priorMessage = findLast(userMessages(), (x) => x.id < message.id)
|
|
|
|
|
args.setActiveMessage(priorMessage)
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
sessionCommand({
|
|
|
|
|
id: "session.redo",
|
|
|
|
|
title: input.language.t("command.session.redo"),
|
|
|
|
|
description: input.language.t("command.session.redo.description"),
|
|
|
|
|
title: language.t("command.session.redo"),
|
|
|
|
|
description: language.t("command.session.redo.description"),
|
|
|
|
|
slash: "redo",
|
|
|
|
|
disabled: !input.params.id || !input.info()?.revert?.messageID,
|
|
|
|
|
disabled: !params.id || !info()?.revert?.messageID,
|
|
|
|
|
onSelect: async () => {
|
|
|
|
|
const sessionID = input.params.id
|
|
|
|
|
const sessionID = params.id
|
|
|
|
|
if (!sessionID) return
|
|
|
|
|
const revertMessageID = input.info()?.revert?.messageID
|
|
|
|
|
const revertMessageID = info()?.revert?.messageID
|
|
|
|
|
if (!revertMessageID) return
|
|
|
|
|
const nextMessage = input.userMessages().find((x) => x.id > revertMessageID)
|
|
|
|
|
const nextMessage = userMessages().find((x) => x.id > revertMessageID)
|
|
|
|
|
if (!nextMessage) {
|
|
|
|
|
await input.sdk.client.session.unrevert({ sessionID })
|
|
|
|
|
input.prompt.reset()
|
|
|
|
|
const lastMsg = findLast(input.userMessages(), (x) => x.id >= revertMessageID)
|
|
|
|
|
input.setActiveMessage(lastMsg)
|
|
|
|
|
await sdk.client.session.unrevert({ sessionID })
|
|
|
|
|
prompt.reset()
|
|
|
|
|
const lastMsg = findLast(userMessages(), (x) => x.id >= revertMessageID)
|
|
|
|
|
args.setActiveMessage(lastMsg)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
await input.sdk.client.session.revert({ sessionID, messageID: nextMessage.id })
|
|
|
|
|
const priorMsg = findLast(input.userMessages(), (x) => x.id < nextMessage.id)
|
|
|
|
|
input.setActiveMessage(priorMsg)
|
|
|
|
|
await sdk.client.session.revert({ sessionID, messageID: nextMessage.id })
|
|
|
|
|
const priorMsg = findLast(userMessages(), (x) => x.id < nextMessage.id)
|
|
|
|
|
args.setActiveMessage(priorMsg)
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
sessionCommand({
|
|
|
|
|
id: "session.compact",
|
|
|
|
|
title: input.language.t("command.session.compact"),
|
|
|
|
|
description: input.language.t("command.session.compact.description"),
|
|
|
|
|
title: language.t("command.session.compact"),
|
|
|
|
|
description: language.t("command.session.compact.description"),
|
|
|
|
|
slash: "compact",
|
|
|
|
|
disabled: !input.params.id || input.visibleUserMessages().length === 0,
|
|
|
|
|
disabled: !params.id || visibleUserMessages().length === 0,
|
|
|
|
|
onSelect: async () => {
|
|
|
|
|
const sessionID = input.params.id
|
|
|
|
|
const sessionID = params.id
|
|
|
|
|
if (!sessionID) return
|
|
|
|
|
const model = input.local.model.current()
|
|
|
|
|
const model = local.model.current()
|
|
|
|
|
if (!model) {
|
|
|
|
|
showToast({
|
|
|
|
|
title: input.language.t("toast.model.none.title"),
|
|
|
|
|
description: input.language.t("toast.model.none.description"),
|
|
|
|
|
title: language.t("toast.model.none.title"),
|
|
|
|
|
description: language.t("toast.model.none.description"),
|
|
|
|
|
})
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
await input.sdk.client.session.summarize({
|
|
|
|
|
await sdk.client.session.summarize({
|
|
|
|
|
sessionID,
|
|
|
|
|
modelID: model.id,
|
|
|
|
|
providerID: model.provider.id,
|
|
|
|
|
@@ -346,29 +368,27 @@ export const useSessionCommands = (input: SessionCommandContext) => {
|
|
|
|
|
}),
|
|
|
|
|
sessionCommand({
|
|
|
|
|
id: "session.fork",
|
|
|
|
|
title: input.language.t("command.session.fork"),
|
|
|
|
|
description: input.language.t("command.session.fork.description"),
|
|
|
|
|
title: language.t("command.session.fork"),
|
|
|
|
|
description: language.t("command.session.fork.description"),
|
|
|
|
|
slash: "fork",
|
|
|
|
|
disabled: !input.params.id || input.visibleUserMessages().length === 0,
|
|
|
|
|
onSelect: () => input.dialog.show(() => <DialogFork />),
|
|
|
|
|
disabled: !params.id || visibleUserMessages().length === 0,
|
|
|
|
|
onSelect: () => dialog.show(() => <DialogFork />),
|
|
|
|
|
}),
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
const shareCommands = createMemo(() => {
|
|
|
|
|
if (input.sync.data.config.share === "disabled") return []
|
|
|
|
|
if (sync.data.config.share === "disabled") return []
|
|
|
|
|
return [
|
|
|
|
|
sessionCommand({
|
|
|
|
|
id: "session.share",
|
|
|
|
|
title: input.info()?.share?.url
|
|
|
|
|
? input.language.t("session.share.copy.copyLink")
|
|
|
|
|
: input.language.t("command.session.share"),
|
|
|
|
|
description: input.info()?.share?.url
|
|
|
|
|
? input.language.t("toast.session.share.success.description")
|
|
|
|
|
: input.language.t("command.session.share.description"),
|
|
|
|
|
title: info()?.share?.url ? language.t("session.share.copy.copyLink") : language.t("command.session.share"),
|
|
|
|
|
description: info()?.share?.url
|
|
|
|
|
? language.t("toast.session.share.success.description")
|
|
|
|
|
: language.t("command.session.share.description"),
|
|
|
|
|
slash: "share",
|
|
|
|
|
disabled: !input.params.id,
|
|
|
|
|
disabled: !params.id,
|
|
|
|
|
onSelect: async () => {
|
|
|
|
|
if (!input.params.id) return
|
|
|
|
|
if (!params.id) return
|
|
|
|
|
|
|
|
|
|
const write = (value: string) => {
|
|
|
|
|
const body = typeof document === "undefined" ? undefined : document.body
|
|
|
|
|
@@ -398,7 +418,7 @@ export const useSessionCommands = (input: SessionCommandContext) => {
|
|
|
|
|
const ok = await write(url)
|
|
|
|
|
if (!ok) {
|
|
|
|
|
showToast({
|
|
|
|
|
title: input.language.t("toast.session.share.copyFailed.title"),
|
|
|
|
|
title: language.t("toast.session.share.copyFailed.title"),
|
|
|
|
|
variant: "error",
|
|
|
|
|
})
|
|
|
|
|
return
|
|
|
|
|
@@ -406,27 +426,27 @@ export const useSessionCommands = (input: SessionCommandContext) => {
|
|
|
|
|
|
|
|
|
|
showToast({
|
|
|
|
|
title: existing
|
|
|
|
|
? input.language.t("session.share.copy.copied")
|
|
|
|
|
: input.language.t("toast.session.share.success.title"),
|
|
|
|
|
description: input.language.t("toast.session.share.success.description"),
|
|
|
|
|
? language.t("session.share.copy.copied")
|
|
|
|
|
: language.t("toast.session.share.success.title"),
|
|
|
|
|
description: language.t("toast.session.share.success.description"),
|
|
|
|
|
variant: "success",
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const existing = input.info()?.share?.url
|
|
|
|
|
const existing = info()?.share?.url
|
|
|
|
|
if (existing) {
|
|
|
|
|
await copy(existing, true)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const url = await input.sdk.client.session
|
|
|
|
|
.share({ sessionID: input.params.id })
|
|
|
|
|
const url = await sdk.client.session
|
|
|
|
|
.share({ sessionID: params.id })
|
|
|
|
|
.then((res) => res.data?.share?.url)
|
|
|
|
|
.catch(() => undefined)
|
|
|
|
|
if (!url) {
|
|
|
|
|
showToast({
|
|
|
|
|
title: input.language.t("toast.session.share.failed.title"),
|
|
|
|
|
description: input.language.t("toast.session.share.failed.description"),
|
|
|
|
|
title: language.t("toast.session.share.failed.title"),
|
|
|
|
|
description: language.t("toast.session.share.failed.description"),
|
|
|
|
|
variant: "error",
|
|
|
|
|
})
|
|
|
|
|
return
|
|
|
|
|
@@ -437,25 +457,25 @@ export const useSessionCommands = (input: SessionCommandContext) => {
|
|
|
|
|
}),
|
|
|
|
|
sessionCommand({
|
|
|
|
|
id: "session.unshare",
|
|
|
|
|
title: input.language.t("command.session.unshare"),
|
|
|
|
|
description: input.language.t("command.session.unshare.description"),
|
|
|
|
|
title: language.t("command.session.unshare"),
|
|
|
|
|
description: language.t("command.session.unshare.description"),
|
|
|
|
|
slash: "unshare",
|
|
|
|
|
disabled: !input.params.id || !input.info()?.share?.url,
|
|
|
|
|
disabled: !params.id || !info()?.share?.url,
|
|
|
|
|
onSelect: async () => {
|
|
|
|
|
if (!input.params.id) return
|
|
|
|
|
await input.sdk.client.session
|
|
|
|
|
.unshare({ sessionID: input.params.id })
|
|
|
|
|
if (!params.id) return
|
|
|
|
|
await sdk.client.session
|
|
|
|
|
.unshare({ sessionID: params.id })
|
|
|
|
|
.then(() =>
|
|
|
|
|
showToast({
|
|
|
|
|
title: input.language.t("toast.session.unshare.success.title"),
|
|
|
|
|
description: input.language.t("toast.session.unshare.success.description"),
|
|
|
|
|
title: language.t("toast.session.unshare.success.title"),
|
|
|
|
|
description: language.t("toast.session.unshare.success.description"),
|
|
|
|
|
variant: "success",
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.catch(() =>
|
|
|
|
|
showToast({
|
|
|
|
|
title: input.language.t("toast.session.unshare.failed.title"),
|
|
|
|
|
description: input.language.t("toast.session.unshare.failed.description"),
|
|
|
|
|
title: language.t("toast.session.unshare.failed.title"),
|
|
|
|
|
description: language.t("toast.session.unshare.failed.description"),
|
|
|
|
|
variant: "error",
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
@@ -464,8 +484,8 @@ export const useSessionCommands = (input: SessionCommandContext) => {
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
input.command.register("session", () =>
|
|
|
|
|
combineCommandSections([
|
|
|
|
|
command.register("session", () =>
|
|
|
|
|
[
|
|
|
|
|
sessionCommands(),
|
|
|
|
|
fileCommands(),
|
|
|
|
|
contextCommands(),
|
|
|
|
|
@@ -475,6 +495,6 @@ export const useSessionCommands = (input: SessionCommandContext) => {
|
|
|
|
|
permissionCommands(),
|
|
|
|
|
sessionActionCommands(),
|
|
|
|
|
shareCommands(),
|
|
|
|
|
]),
|
|
|
|
|
].flatMap((section) => section),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|