fix(ui): unwrap pasted placeholders in slash commands (#235)
## What Fix slash command execution so `[pasted #N]` placeholders are resolved before calling `session.command`, matching normal prompt send behavior. ## Why When pasting long text into a slash command (e.g. `/some-command [pasted #1]`), the UI previously bypassed `resolvePastedPlaceholders(...)` for known slash commands and sent the literal placeholder text as command arguments. ## Changes - Resolve pasted placeholders (and other prompt placeholders handled by `resolvePastedPlaceholders`) in slash-command arguments before `executeCustomCommand(...)`. - Remove *consumed* pasted-text attachments (those referenced by placeholders in the slash-command args) so they don’t linger for the next prompt. Fixes #234. ## Notes - I attempted `npm run typecheck --workspace @codenomad/ui` locally but the workspace dependencies aren’t installed in this bot environment, so it fails with missing-module errors. CI should validate with a full install. -- Yours, [CodeNomadBot](https://github.com/NeuralNomadsAI/CodeNomad) Co-authored-by: Shantur Rathore <i@shantur.com>
This commit is contained in:
committed by
GitHub
parent
108cad82d0
commit
d15340a4b8
@@ -4,6 +4,7 @@ import UnifiedPicker from "./unified-picker"
|
|||||||
import ExpandButton from "./expand-button"
|
import ExpandButton from "./expand-button"
|
||||||
import { clearAttachments, removeAttachment } from "../stores/attachments"
|
import { clearAttachments, removeAttachment } from "../stores/attachments"
|
||||||
import { resolvePastedPlaceholders } from "../lib/prompt-placeholders"
|
import { resolvePastedPlaceholders } from "../lib/prompt-placeholders"
|
||||||
|
import { createPastedPlaceholderRegex, pastedDisplayCounterRegex } from "./prompt-input/attachmentPlaceholders"
|
||||||
import Kbd from "./kbd"
|
import Kbd from "./kbd"
|
||||||
import { getActiveInstance } from "../stores/instances"
|
import { getActiveInstance } from "../stores/instances"
|
||||||
import { agents, executeCustomCommand } from "../stores/sessions"
|
import { agents, executeCustomCommand } from "../stores/sessions"
|
||||||
@@ -13,12 +14,41 @@ import { useI18n } from "../lib/i18n"
|
|||||||
import { getLogger } from "../lib/logger"
|
import { getLogger } from "../lib/logger"
|
||||||
import { preferences } from "../stores/preferences"
|
import { preferences } from "../stores/preferences"
|
||||||
import type { ExpandState, PromptInputApi, PromptInputProps, PromptInsertMode, PromptMode } from "./prompt-input/types"
|
import type { ExpandState, PromptInputApi, PromptInputProps, PromptInsertMode, PromptMode } from "./prompt-input/types"
|
||||||
|
import type { Attachment } from "../types/attachment"
|
||||||
import { usePromptState } from "./prompt-input/usePromptState"
|
import { usePromptState } from "./prompt-input/usePromptState"
|
||||||
import { usePromptAttachments } from "./prompt-input/usePromptAttachments"
|
import { usePromptAttachments } from "./prompt-input/usePromptAttachments"
|
||||||
import { usePromptPicker } from "./prompt-input/usePromptPicker"
|
import { usePromptPicker } from "./prompt-input/usePromptPicker"
|
||||||
import { usePromptKeyDown } from "./prompt-input/usePromptKeyDown"
|
import { usePromptKeyDown } from "./prompt-input/usePromptKeyDown"
|
||||||
const log = getLogger("actions")
|
const log = getLogger("actions")
|
||||||
|
|
||||||
|
function getConsumedPastedTextAttachmentIds(text: string, attachments: Attachment[]): string[] {
|
||||||
|
if (!text || attachments.length === 0) return []
|
||||||
|
|
||||||
|
const usedCounters = new Set<string>()
|
||||||
|
for (const match of text.matchAll(createPastedPlaceholderRegex())) {
|
||||||
|
const counter = match?.[1]
|
||||||
|
if (counter) usedCounters.add(counter)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usedCounters.size === 0) return []
|
||||||
|
|
||||||
|
const consumed = new Set<string>()
|
||||||
|
|
||||||
|
for (const attachment of attachments) {
|
||||||
|
if (!attachment?.id) continue
|
||||||
|
if (attachment?.source?.type !== "text") continue
|
||||||
|
const display = attachment.display
|
||||||
|
if (typeof display !== "string") continue
|
||||||
|
const match = display.match(pastedDisplayCounterRegex)
|
||||||
|
if (!match?.[1]) continue
|
||||||
|
if (usedCounters.has(match[1])) {
|
||||||
|
consumed.add(attachment.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(consumed)
|
||||||
|
}
|
||||||
|
|
||||||
export default function PromptInput(props: PromptInputProps) {
|
export default function PromptInput(props: PromptInputProps) {
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const [, setIsFocused] = createSignal(false)
|
const [, setIsFocused] = createSignal(false)
|
||||||
@@ -246,7 +276,12 @@ export default function PromptInput(props: PromptInputProps) {
|
|||||||
commandName.length > 0 &&
|
commandName.length > 0 &&
|
||||||
getCommands(props.instanceId).some((cmd) => cmd.name === commandName)
|
getCommands(props.instanceId).some((cmd) => cmd.name === commandName)
|
||||||
|
|
||||||
const resolvedPrompt = isKnownSlashCommand ? text : resolvePastedPlaceholders(text, currentAttachments)
|
const resolvedCommandArgs = isKnownSlashCommand ? resolvePastedPlaceholders(commandArgs, currentAttachments) : ""
|
||||||
|
const resolvedPrompt = isKnownSlashCommand
|
||||||
|
? resolvedCommandArgs
|
||||||
|
? `${commandToken} ${resolvedCommandArgs}`
|
||||||
|
: commandToken
|
||||||
|
: resolvePastedPlaceholders(text, currentAttachments)
|
||||||
const historyEntry = resolvedPrompt
|
const historyEntry = resolvedPrompt
|
||||||
|
|
||||||
const refreshHistory = () => recordHistoryEntry(historyEntry)
|
const refreshHistory = () => recordHistoryEntry(historyEntry)
|
||||||
@@ -262,6 +297,10 @@ export default function PromptInput(props: PromptInputProps) {
|
|||||||
syncAttachmentCounters("")
|
syncAttachmentCounters("")
|
||||||
setIgnoredAtPositions(new Set<number>())
|
setIgnoredAtPositions(new Set<number>())
|
||||||
} else {
|
} else {
|
||||||
|
const consumedIds = getConsumedPastedTextAttachmentIds(commandArgs, currentAttachments)
|
||||||
|
for (const attachmentId of consumedIds) {
|
||||||
|
removeAttachment(props.instanceId, props.sessionId, attachmentId)
|
||||||
|
}
|
||||||
syncAttachmentCounters("")
|
syncAttachmentCounters("")
|
||||||
setIgnoredAtPositions(new Set<number>())
|
setIgnoredAtPositions(new Set<number>())
|
||||||
}
|
}
|
||||||
@@ -281,7 +320,7 @@ export default function PromptInput(props: PromptInputProps) {
|
|||||||
await props.onSend(resolvedPrompt, [])
|
await props.onSend(resolvedPrompt, [])
|
||||||
}
|
}
|
||||||
} else if (isKnownSlashCommand) {
|
} else if (isKnownSlashCommand) {
|
||||||
await executeCustomCommand(props.instanceId, props.sessionId, commandName, commandArgs)
|
await executeCustomCommand(props.instanceId, props.sessionId, commandName, resolvedCommandArgs)
|
||||||
} else {
|
} else {
|
||||||
await props.onSend(resolvedPrompt, currentAttachments)
|
await props.onSend(resolvedPrompt, currentAttachments)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user