fix(ui): keep prompt attachments in sync
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
import { createSignal, Show, onMount, onCleanup, createEffect, on, untrack } from "solid-js"
|
import { createSignal, Show, onMount, onCleanup, createEffect, on } from "solid-js"
|
||||||
import { ArrowBigUp, ArrowBigDown } from "lucide-solid"
|
import { ArrowBigUp, ArrowBigDown } from "lucide-solid"
|
||||||
import UnifiedPicker from "./unified-picker"
|
import UnifiedPicker from "./unified-picker"
|
||||||
import ExpandButton from "./expand-button"
|
import ExpandButton from "./expand-button"
|
||||||
import { getAttachments, clearAttachments, removeAttachment } from "../stores/attachments"
|
import { clearAttachments, removeAttachment } from "../stores/attachments"
|
||||||
import { resolvePastedPlaceholders } from "../lib/prompt-placeholders"
|
import { resolvePastedPlaceholders } from "../lib/prompt-placeholders"
|
||||||
import Kbd from "./kbd"
|
import Kbd from "./kbd"
|
||||||
import { getActiveInstance } from "../stores/instances"
|
import { getActiveInstance } from "../stores/instances"
|
||||||
@@ -63,6 +63,7 @@ export default function PromptInput(props: PromptInputProps) {
|
|||||||
handleDrop,
|
handleDrop,
|
||||||
syncAttachmentCounters,
|
syncAttachmentCounters,
|
||||||
handleExpandTextAttachment,
|
handleExpandTextAttachment,
|
||||||
|
handleRemoveAttachment,
|
||||||
} = usePromptAttachments({
|
} = usePromptAttachments({
|
||||||
instanceId: () => props.instanceId,
|
instanceId: () => props.instanceId,
|
||||||
sessionId: () => props.sessionId,
|
sessionId: () => props.sessionId,
|
||||||
@@ -87,6 +88,9 @@ export default function PromptInput(props: PromptInputProps) {
|
|||||||
if (!attachment) return
|
if (!attachment) return
|
||||||
handleExpandTextAttachment(attachment)
|
handleExpandTextAttachment(attachment)
|
||||||
},
|
},
|
||||||
|
removeAttachment: (attachmentId: string) => {
|
||||||
|
handleRemoveAttachment(attachmentId)
|
||||||
|
},
|
||||||
setPromptText: (text: string, opts?: { focus?: boolean }) => {
|
setPromptText: (text: string, opts?: { focus?: boolean }) => {
|
||||||
const textarea = textareaRef
|
const textarea = textareaRef
|
||||||
if (textarea) {
|
if (textarea) {
|
||||||
@@ -166,10 +170,7 @@ export default function PromptInput(props: PromptInputProps) {
|
|||||||
setAtPosition(null)
|
setAtPosition(null)
|
||||||
setSearchQuery("")
|
setSearchQuery("")
|
||||||
|
|
||||||
const instanceId = props.instanceId
|
syncAttachmentCounters(prompt())
|
||||||
const sessionId = props.sessionId
|
|
||||||
const currentAttachments = untrack(() => getAttachments(instanceId, sessionId))
|
|
||||||
syncAttachmentCounters(prompt(), currentAttachments)
|
|
||||||
},
|
},
|
||||||
{ defer: true },
|
{ defer: true },
|
||||||
),
|
),
|
||||||
@@ -238,10 +239,10 @@ export default function PromptInput(props: PromptInputProps) {
|
|||||||
// Ignore attachments for slash commands, but keep them for next prompt.
|
// Ignore attachments for slash commands, but keep them for next prompt.
|
||||||
if (!isKnownSlashCommand) {
|
if (!isKnownSlashCommand) {
|
||||||
clearAttachments(props.instanceId, props.sessionId)
|
clearAttachments(props.instanceId, props.sessionId)
|
||||||
syncAttachmentCounters("", [])
|
syncAttachmentCounters("")
|
||||||
setIgnoredAtPositions(new Set<number>())
|
setIgnoredAtPositions(new Set<number>())
|
||||||
} else {
|
} else {
|
||||||
syncAttachmentCounters("", currentAttachments)
|
syncAttachmentCounters("")
|
||||||
setIgnoredAtPositions(new Set<number>())
|
setIgnoredAtPositions(new Set<number>())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import type { Attachment } from "../../types/attachment"
|
|
||||||
|
|
||||||
export function formatPastedPlaceholder(value: string | number) {
|
export function formatPastedPlaceholder(value: string | number) {
|
||||||
return `[pasted #${value}]`
|
return `[pasted #${value}]`
|
||||||
}
|
}
|
||||||
@@ -9,27 +7,27 @@ export function formatImagePlaceholder(value: string | number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createPastedPlaceholderRegex() {
|
export function createPastedPlaceholderRegex() {
|
||||||
return /\[pasted #(\d+)\]/g
|
return /\[\s*pasted\s*#\s*(\d+)\s*\]/gi
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createImagePlaceholderRegex() {
|
export function createImagePlaceholderRegex() {
|
||||||
return /\[Image #(\d+)\]/g
|
return /\[\s*Image\s*#\s*(\d+)\s*\]/gi
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createMentionRegex() {
|
export function createMentionRegex() {
|
||||||
return /@(\S+)/g
|
return /@(\S+)/g
|
||||||
}
|
}
|
||||||
|
|
||||||
export const pastedDisplayCounterRegex = /pasted #(\d+)/
|
export const pastedDisplayCounterRegex = /pasted #(\d+)/i
|
||||||
export const imageDisplayCounterRegex = /Image #(\d+)/
|
export const imageDisplayCounterRegex = /Image #(\d+)/i
|
||||||
export const bracketedImageDisplayCounterRegex = /\[Image #(\d+)\]/
|
export const bracketedImageDisplayCounterRegex = /\[\s*Image\s*#\s*(\d+)\s*\]/i
|
||||||
|
|
||||||
export function parseCounter(value: string) {
|
export function parseCounter(value: string) {
|
||||||
const parsed = Number.parseInt(value, 10)
|
const parsed = Number.parseInt(value, 10)
|
||||||
return Number.isNaN(parsed) ? null : parsed
|
return Number.isNaN(parsed) ? null : parsed
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findHighestAttachmentCounters(currentPrompt: string, sessionAttachments: Attachment[]) {
|
export function findHighestAttachmentCounters(currentPrompt: string) {
|
||||||
let highestPaste = 0
|
let highestPaste = 0
|
||||||
let highestImage = 0
|
let highestImage = 0
|
||||||
|
|
||||||
@@ -40,27 +38,6 @@ export function findHighestAttachmentCounters(currentPrompt: string, sessionAtta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const attachment of sessionAttachments) {
|
|
||||||
if (attachment.source.type === "text") {
|
|
||||||
const placeholderMatch = attachment.display.match(pastedDisplayCounterRegex)
|
|
||||||
if (placeholderMatch) {
|
|
||||||
const parsed = parseCounter(placeholderMatch[1])
|
|
||||||
if (parsed !== null) {
|
|
||||||
highestPaste = Math.max(highestPaste, parsed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (attachment.source.type === "file" && attachment.mediaType.startsWith("image/")) {
|
|
||||||
const imageMatch = attachment.display.match(imageDisplayCounterRegex)
|
|
||||||
if (imageMatch) {
|
|
||||||
const parsed = parseCounter(imageMatch[1])
|
|
||||||
if (parsed !== null) {
|
|
||||||
highestImage = Math.max(highestImage, parsed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const match of currentPrompt.matchAll(createImagePlaceholderRegex())) {
|
for (const match of currentPrompt.matchAll(createImagePlaceholderRegex())) {
|
||||||
const parsed = parseCounter(match[1])
|
const parsed = parseCounter(match[1])
|
||||||
if (parsed !== null) {
|
if (parsed !== null) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export type PromptInsertMode = "quote" | "code"
|
|||||||
export interface PromptInputApi {
|
export interface PromptInputApi {
|
||||||
insertSelection(text: string, mode: PromptInsertMode): void
|
insertSelection(text: string, mode: PromptInsertMode): void
|
||||||
expandTextAttachment(attachmentId: string): void
|
expandTextAttachment(attachmentId: string): void
|
||||||
|
removeAttachment(attachmentId: string): void
|
||||||
setPromptText(text: string, opts?: { focus?: boolean }): void
|
setPromptText(text: string, opts?: { focus?: boolean }): void
|
||||||
focus(): void
|
focus(): void
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createSignal, type Accessor } from "solid-js"
|
import { createEffect, createSignal, type Accessor } from "solid-js"
|
||||||
import { addAttachment, getAttachments, removeAttachment } from "../../stores/attachments"
|
import { addAttachment, getAttachments, removeAttachment } from "../../stores/attachments"
|
||||||
import { createFileAttachment, createTextAttachment } from "../../types/attachment"
|
import { createFileAttachment, createTextAttachment } from "../../types/attachment"
|
||||||
import type { Attachment } from "../../types/attachment"
|
import type { Attachment } from "../../types/attachment"
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
findHighestAttachmentCounters,
|
findHighestAttachmentCounters,
|
||||||
formatImagePlaceholder,
|
formatImagePlaceholder,
|
||||||
formatPastedPlaceholder,
|
formatPastedPlaceholder,
|
||||||
|
imageDisplayCounterRegex,
|
||||||
pastedDisplayCounterRegex,
|
pastedDisplayCounterRegex,
|
||||||
} from "./attachmentPlaceholders"
|
} from "./attachmentPlaceholders"
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ type PromptAttachments = {
|
|||||||
attachments: Accessor<Attachment[]>
|
attachments: Accessor<Attachment[]>
|
||||||
pasteCount: Accessor<number>
|
pasteCount: Accessor<number>
|
||||||
imageCount: Accessor<number>
|
imageCount: Accessor<number>
|
||||||
syncAttachmentCounters: (promptText: string, sessionAttachments: Attachment[]) => void
|
syncAttachmentCounters: (promptText: string) => void
|
||||||
|
|
||||||
handlePaste: (e: ClipboardEvent) => Promise<void>
|
handlePaste: (e: ClipboardEvent) => Promise<void>
|
||||||
isDragging: Accessor<boolean>
|
isDragging: Accessor<boolean>
|
||||||
@@ -41,45 +42,106 @@ export function usePromptAttachments(options: PromptAttachmentsOptions): PromptA
|
|||||||
const [pasteCount, setPasteCount] = createSignal(0)
|
const [pasteCount, setPasteCount] = createSignal(0)
|
||||||
const [imageCount, setImageCount] = createSignal(0)
|
const [imageCount, setImageCount] = createSignal(0)
|
||||||
|
|
||||||
function syncAttachmentCounters(currentPrompt: string, sessionAttachments: Attachment[]) {
|
function syncAttachmentCounters(currentPrompt: string) {
|
||||||
const { highestPaste, highestImage } = findHighestAttachmentCounters(currentPrompt, sessionAttachments)
|
const { highestPaste, highestImage } = findHighestAttachmentCounters(currentPrompt)
|
||||||
setPasteCount(highestPaste)
|
setPasteCount(highestPaste)
|
||||||
setImageCount(highestImage)
|
setImageCount(highestImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
||||||
|
|
||||||
|
function removeTokenFromPrompt(currentPrompt: string, tokenRegex: RegExp) {
|
||||||
|
const next = currentPrompt.replace(tokenRegex, "")
|
||||||
|
if (next === currentPrompt) return currentPrompt
|
||||||
|
|
||||||
|
return next
|
||||||
|
.replace(/[ \t]{2,}/g, " ")
|
||||||
|
.replace(/[ \t]+\n/g, "\n")
|
||||||
|
.replace(/\n[ \t]+/g, "\n")
|
||||||
|
.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
const createLooseImagePlaceholderRegex = (counter: string | number) =>
|
||||||
|
new RegExp(`\\[\\s*Image\\s*#\\s*${counter}\\s*\\]`, "i")
|
||||||
|
const createLoosePastedPlaceholderRegex = (counter: string | number) =>
|
||||||
|
new RegExp(`\\[\\s*pasted\\s*#\\s*${counter}\\s*\\]`, "i")
|
||||||
|
|
||||||
|
// Keep placeholder-backed attachments in sync with prompt text.
|
||||||
|
// If the placeholder token disappears from the prompt, the attachment should disappear too.
|
||||||
|
createEffect(() => {
|
||||||
|
const currentPrompt = options.prompt()
|
||||||
|
const currentAttachments = attachments()
|
||||||
|
|
||||||
|
const toRemove: string[] = []
|
||||||
|
|
||||||
|
for (const attachment of currentAttachments) {
|
||||||
|
if (attachment.source.type === "text") {
|
||||||
|
const match = attachment.display.match(pastedDisplayCounterRegex)
|
||||||
|
if (!match) continue
|
||||||
|
const counter = match[1]
|
||||||
|
if (!createLoosePastedPlaceholderRegex(counter).test(currentPrompt)) {
|
||||||
|
toRemove.push(attachment.id)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attachment.source.type === "file" && attachment.mediaType.startsWith("image/")) {
|
||||||
|
const match =
|
||||||
|
attachment.display.match(bracketedImageDisplayCounterRegex) || attachment.display.match(imageDisplayCounterRegex)
|
||||||
|
if (!match) continue
|
||||||
|
const counter = match[1]
|
||||||
|
if (!createLooseImagePlaceholderRegex(counter).test(currentPrompt)) {
|
||||||
|
toRemove.push(attachment.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const attachmentId of toRemove) {
|
||||||
|
removeAttachment(options.instanceId(), options.sessionId(), attachmentId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function handleRemoveAttachment(attachmentId: string) {
|
function handleRemoveAttachment(attachmentId: string) {
|
||||||
const currentAttachments = attachments()
|
const currentAttachments = attachments()
|
||||||
const attachment = currentAttachments.find((a) => a.id === attachmentId)
|
const attachment = currentAttachments.find((a) => a.id === attachmentId)
|
||||||
|
|
||||||
|
// Always remove from store.
|
||||||
removeAttachment(options.instanceId(), options.sessionId(), attachmentId)
|
removeAttachment(options.instanceId(), options.sessionId(), attachmentId)
|
||||||
|
|
||||||
if (attachment) {
|
if (!attachment) return
|
||||||
const currentPrompt = options.prompt()
|
|
||||||
let newPrompt = currentPrompt
|
|
||||||
|
|
||||||
if (attachment.source.type === "file") {
|
const currentPrompt = options.prompt()
|
||||||
if (attachment.mediaType.startsWith("image/")) {
|
let nextPrompt = currentPrompt
|
||||||
const imageMatch = attachment.display.match(bracketedImageDisplayCounterRegex)
|
|
||||||
if (imageMatch) {
|
if (attachment.source.type === "file") {
|
||||||
const placeholder = formatImagePlaceholder(imageMatch[1])
|
if (attachment.mediaType.startsWith("image/")) {
|
||||||
newPrompt = currentPrompt.replace(placeholder, "").replace(/\s+/g, " ").trim()
|
const imageMatch =
|
||||||
}
|
attachment.display.match(bracketedImageDisplayCounterRegex) || attachment.display.match(imageDisplayCounterRegex)
|
||||||
} else {
|
if (imageMatch) {
|
||||||
const filename = attachment.filename
|
nextPrompt = removeTokenFromPrompt(currentPrompt, createLooseImagePlaceholderRegex(imageMatch[1]))
|
||||||
newPrompt = currentPrompt.replace(`@${filename}`, "").replace(/\s+/g, " ").trim()
|
|
||||||
}
|
}
|
||||||
} else if (attachment.source.type === "agent") {
|
} else {
|
||||||
const agentName = attachment.filename
|
// For file mentions we insert `@<path>`, but the chip might display `@<filename>`.
|
||||||
newPrompt = currentPrompt.replace(`@${agentName}`, "").replace(/\s+/g, " ").trim()
|
const candidates = [attachment.source.path, attachment.filename]
|
||||||
} else if (attachment.source.type === "text") {
|
for (const candidate of candidates) {
|
||||||
const placeholderMatch = attachment.display.match(pastedDisplayCounterRegex)
|
if (!candidate) continue
|
||||||
if (placeholderMatch) {
|
const mentionRegex = new RegExp(`@${escapeRegExp(candidate)}(?=\\s|$)`, "i")
|
||||||
const placeholder = formatPastedPlaceholder(placeholderMatch[1])
|
nextPrompt = removeTokenFromPrompt(nextPrompt, mentionRegex)
|
||||||
newPrompt = currentPrompt.replace(placeholder, "").replace(/\s+/g, " ").trim()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (attachment.source.type === "agent") {
|
||||||
|
const agentName = attachment.filename
|
||||||
|
const mentionRegex = new RegExp(`@${escapeRegExp(agentName)}(?=\\s|$)`, "i")
|
||||||
|
nextPrompt = removeTokenFromPrompt(currentPrompt, mentionRegex)
|
||||||
|
} else if (attachment.source.type === "text") {
|
||||||
|
const placeholderMatch = attachment.display.match(pastedDisplayCounterRegex)
|
||||||
|
if (placeholderMatch) {
|
||||||
|
nextPrompt = removeTokenFromPrompt(currentPrompt, createLoosePastedPlaceholderRegex(placeholderMatch[1]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
options.setPrompt(newPrompt)
|
if (nextPrompt !== currentPrompt) {
|
||||||
|
options.setPrompt(nextPrompt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,13 +205,32 @@ export function usePromptAttachments(options: PromptAttachmentsOptions): PromptA
|
|||||||
const blob = item.getAsFile()
|
const blob = item.getAsFile()
|
||||||
if (!blob) continue
|
if (!blob) continue
|
||||||
|
|
||||||
const count = imageCount() + 1
|
const { highestImage } = findHighestAttachmentCounters(options.prompt())
|
||||||
|
const count = highestImage + 1
|
||||||
setImageCount(count)
|
setImageCount(count)
|
||||||
|
|
||||||
|
const placeholder = formatImagePlaceholder(count)
|
||||||
|
const textarea = options.getTextarea()
|
||||||
|
|
||||||
|
if (textarea) {
|
||||||
|
const start = textarea.selectionStart
|
||||||
|
const end = textarea.selectionEnd
|
||||||
|
const currentText = options.prompt()
|
||||||
|
const newText = currentText.substring(0, start) + placeholder + currentText.substring(end)
|
||||||
|
options.setPrompt(newText)
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const newCursorPos = start + placeholder.length
|
||||||
|
textarea.setSelectionRange(newCursorPos, newCursorPos)
|
||||||
|
textarea.focus()
|
||||||
|
}, 0)
|
||||||
|
} else {
|
||||||
|
options.setPrompt(options.prompt() + placeholder)
|
||||||
|
}
|
||||||
|
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
const base64Data = (reader.result as string).split(",")[1]
|
const base64Data = (reader.result as string).split(",")[1]
|
||||||
const display = formatImagePlaceholder(count)
|
|
||||||
const filename = `image-${count}.png`
|
const filename = `image-${count}.png`
|
||||||
|
|
||||||
const attachment = createFileAttachment(
|
const attachment = createFileAttachment(
|
||||||
@@ -160,24 +241,8 @@ export function usePromptAttachments(options: PromptAttachmentsOptions): PromptA
|
|||||||
options.instanceFolder(),
|
options.instanceFolder(),
|
||||||
)
|
)
|
||||||
attachment.url = `data:image/png;base64,${base64Data}`
|
attachment.url = `data:image/png;base64,${base64Data}`
|
||||||
attachment.display = display
|
attachment.display = placeholder
|
||||||
addAttachment(options.instanceId(), options.sessionId(), attachment)
|
addAttachment(options.instanceId(), options.sessionId(), attachment)
|
||||||
|
|
||||||
const textarea = options.getTextarea()
|
|
||||||
if (textarea) {
|
|
||||||
const start = textarea.selectionStart
|
|
||||||
const end = textarea.selectionEnd
|
|
||||||
const currentText = options.prompt()
|
|
||||||
const placeholder = formatImagePlaceholder(count)
|
|
||||||
const newText = currentText.substring(0, start) + placeholder + currentText.substring(end)
|
|
||||||
options.setPrompt(newText)
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
const newCursorPos = start + placeholder.length
|
|
||||||
textarea.setSelectionRange(newCursorPos, newCursorPos)
|
|
||||||
textarea.focus()
|
|
||||||
}, 0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
reader.readAsDataURL(blob)
|
reader.readAsDataURL(blob)
|
||||||
|
|
||||||
@@ -196,7 +261,8 @@ export function usePromptAttachments(options: PromptAttachmentsOptions): PromptA
|
|||||||
if (isLongPaste) {
|
if (isLongPaste) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
const count = pasteCount() + 1
|
const { highestPaste } = findHighestAttachmentCounters(options.prompt())
|
||||||
|
const count = highestPaste + 1
|
||||||
setPasteCount(count)
|
setPasteCount(count)
|
||||||
|
|
||||||
const summary = lineCount > 1 ? `${lineCount} lines` : `${charCount} chars`
|
const summary = lineCount > 1 ? `${lineCount} lines` : `${charCount} chars`
|
||||||
@@ -204,14 +270,12 @@ export function usePromptAttachments(options: PromptAttachmentsOptions): PromptA
|
|||||||
const filename = `paste-${count}.txt`
|
const filename = `paste-${count}.txt`
|
||||||
|
|
||||||
const attachment = createTextAttachment(pastedText, display, filename)
|
const attachment = createTextAttachment(pastedText, display, filename)
|
||||||
addAttachment(options.instanceId(), options.sessionId(), attachment)
|
const placeholder = formatPastedPlaceholder(count)
|
||||||
|
|
||||||
const textarea = options.getTextarea()
|
const textarea = options.getTextarea()
|
||||||
if (textarea) {
|
if (textarea) {
|
||||||
const start = textarea.selectionStart
|
const start = textarea.selectionStart
|
||||||
const end = textarea.selectionEnd
|
const end = textarea.selectionEnd
|
||||||
const currentText = options.prompt()
|
const currentText = options.prompt()
|
||||||
const placeholder = formatPastedPlaceholder(count)
|
|
||||||
const newText = currentText.substring(0, start) + placeholder + currentText.substring(end)
|
const newText = currentText.substring(0, start) + placeholder + currentText.substring(end)
|
||||||
options.setPrompt(newText)
|
options.setPrompt(newText)
|
||||||
|
|
||||||
@@ -220,7 +284,11 @@ export function usePromptAttachments(options: PromptAttachmentsOptions): PromptA
|
|||||||
textarea.setSelectionRange(newCursorPos, newCursorPos)
|
textarea.setSelectionRange(newCursorPos, newCursorPos)
|
||||||
textarea.focus()
|
textarea.focus()
|
||||||
}, 0)
|
}, 0)
|
||||||
|
} else {
|
||||||
|
options.setPrompt(options.prompt() + placeholder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addAttachment(options.instanceId(), options.sessionId(), attachment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -299,13 +299,19 @@ export const SessionView: Component<SessionViewProps> = (props) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
<Show when={attachments().length > 0}>
|
<Show when={attachments().length > 0}>
|
||||||
<PromptAttachmentsBar
|
<PromptAttachmentsBar
|
||||||
attachments={attachments()}
|
attachments={attachments()}
|
||||||
onRemoveAttachment={(attachmentId) => removeAttachment(props.instanceId, props.sessionId, attachmentId)}
|
onRemoveAttachment={(attachmentId) => {
|
||||||
onExpandTextAttachment={(attachmentId) => promptInputApi?.expandTextAttachment(attachmentId)}
|
if (promptInputApi) {
|
||||||
/>
|
promptInputApi.removeAttachment(attachmentId)
|
||||||
</Show>
|
return
|
||||||
|
}
|
||||||
|
removeAttachment(props.instanceId, props.sessionId, attachmentId)
|
||||||
|
}}
|
||||||
|
onExpandTextAttachment={(attachmentId) => promptInputApi?.expandTextAttachment(attachmentId)}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
|
|
||||||
<PromptInput
|
<PromptInput
|
||||||
instanceId={props.instanceId}
|
instanceId={props.instanceId}
|
||||||
|
|||||||
Reference in New Issue
Block a user