mirror of
https://github.com/sst/opencode.git
synced 2025-12-23 10:11:41 +00:00
feat(desktop): custom commands
This commit is contained in:
parent
5e37a902ce
commit
ff6864a7ca
2 changed files with 69 additions and 9 deletions
|
|
@ -61,6 +61,7 @@ interface SlashCommand {
|
|||
title: string
|
||||
description?: string
|
||||
keybind?: string
|
||||
type: "builtin" | "custom"
|
||||
}
|
||||
|
||||
export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
|
|
@ -208,8 +209,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||
})
|
||||
|
||||
// Get slash commands from registered commands (only those with explicit slash trigger)
|
||||
const slashCommands = createMemo<SlashCommand[]>(() =>
|
||||
command.options
|
||||
const slashCommands = createMemo<SlashCommand[]>(() => {
|
||||
const builtin = command.options
|
||||
.filter((opt) => !opt.disabled && !opt.id.startsWith("suggested.") && opt.slash)
|
||||
.map((opt) => ({
|
||||
id: opt.id,
|
||||
|
|
@ -217,15 +218,46 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||
title: opt.title,
|
||||
description: opt.description,
|
||||
keybind: opt.keybind,
|
||||
})),
|
||||
)
|
||||
type: "builtin" as const,
|
||||
}))
|
||||
|
||||
const custom = sync.data.command.map((cmd) => ({
|
||||
id: `custom.${cmd.name}`,
|
||||
trigger: cmd.name,
|
||||
title: cmd.name,
|
||||
description: cmd.description,
|
||||
type: "custom" as const,
|
||||
}))
|
||||
|
||||
return [...custom, ...builtin]
|
||||
})
|
||||
|
||||
const handleSlashSelect = (cmd: SlashCommand | undefined) => {
|
||||
if (!cmd) return
|
||||
// Since slash commands only trigger from start, just clear the input
|
||||
setStore("popover", null)
|
||||
|
||||
if (cmd.type === "custom") {
|
||||
// For custom commands, insert the command text so user can add arguments
|
||||
const text = `/${cmd.trigger} `
|
||||
editorRef.innerHTML = ""
|
||||
editorRef.textContent = text
|
||||
prompt.set([{ type: "text", content: text, start: 0, end: text.length }], text.length)
|
||||
// Set cursor at end
|
||||
requestAnimationFrame(() => {
|
||||
editorRef.focus()
|
||||
const range = document.createRange()
|
||||
const sel = window.getSelection()
|
||||
range.selectNodeContents(editorRef)
|
||||
range.collapse(false)
|
||||
sel?.removeAllRanges()
|
||||
sel?.addRange(range)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// For built-in commands, clear input and execute immediately
|
||||
editorRef.innerHTML = ""
|
||||
prompt.set([{ type: "text", content: "", start: 0, end: 0 }], 0)
|
||||
setStore("popover", null)
|
||||
command.trigger(cmd.id, "slash")
|
||||
}
|
||||
|
||||
|
|
@ -571,6 +603,23 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||
editorRef.innerHTML = ""
|
||||
prompt.set([{ type: "text", content: "", start: 0, end: 0 }], 0)
|
||||
|
||||
// Check if this is a custom command
|
||||
if (text.startsWith("/")) {
|
||||
const [cmdName, ...args] = text.split(" ")
|
||||
const commandName = cmdName.slice(1) // Remove leading "/"
|
||||
const customCommand = sync.data.command.find((c) => c.name === commandName)
|
||||
if (customCommand) {
|
||||
sdk.client.session.command({
|
||||
sessionID: existing.id,
|
||||
command: commandName,
|
||||
arguments: args.join(" "),
|
||||
agent: local.agent.current()!.name,
|
||||
model: `${local.model.current()!.provider.id}/${local.model.current()!.id}`,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
sdk.client.session.prompt({
|
||||
sessionID: existing.id,
|
||||
agent: local.agent.current()!.name,
|
||||
|
|
@ -641,9 +690,16 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||
<span class="text-14-regular text-text-weak truncate">{cmd.description}</span>
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={cmd.keybind}>
|
||||
<span class="text-12-regular text-text-subtle shrink-0">{formatKeybind(cmd.keybind!)}</span>
|
||||
</Show>
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
<Show when={cmd.type === "custom"}>
|
||||
<span class="text-11-regular text-text-subtle px-1.5 py-0.5 bg-surface-base rounded">
|
||||
custom
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={cmd.keybind}>
|
||||
<span class="text-12-regular text-text-subtle">{formatKeybind(cmd.keybind!)}</span>
|
||||
</Show>
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
type SessionStatus,
|
||||
type ProviderListResponse,
|
||||
type ProviderAuthResponse,
|
||||
type Command,
|
||||
createOpencodeClient,
|
||||
} from "@opencode-ai/sdk/v2/client"
|
||||
import { createStore, produce, reconcile } from "solid-js/store"
|
||||
|
|
@ -24,6 +25,7 @@ import { onMount } from "solid-js"
|
|||
type State = {
|
||||
ready: boolean
|
||||
agent: Agent[]
|
||||
command: Command[]
|
||||
project: string
|
||||
provider: ProviderListResponse
|
||||
config: Config
|
||||
|
|
@ -79,6 +81,7 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
|
|||
path: { state: "", config: "", worktree: "", directory: "", home: "" },
|
||||
ready: false,
|
||||
agent: [],
|
||||
command: [],
|
||||
session: [],
|
||||
session_status: {},
|
||||
session_diff: {},
|
||||
|
|
@ -118,6 +121,7 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
|
|||
provider: () => sdk.provider.list().then((x) => setStore("provider", x.data!)),
|
||||
path: () => sdk.path.get().then((x) => setStore("path", x.data!)),
|
||||
agent: () => sdk.app.agents().then((x) => setStore("agent", x.data ?? [])),
|
||||
command: () => sdk.command.list().then((x) => setStore("command", x.data ?? [])),
|
||||
session: () => loadSessions(directory),
|
||||
status: () => sdk.session.status().then((x) => setStore("session_status", x.data!)),
|
||||
config: () => sdk.config.get().then((x) => setStore("config", x.data!)),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue