Compare commits
6 Commits
v0.10.3-de
...
v0.10.3-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9f281a69d | ||
|
|
36baac06b8 | ||
|
|
3678214e69 | ||
|
|
338e3d9d38 | ||
|
|
0c0f397db0 | ||
|
|
da70cc9944 |
@@ -3,10 +3,15 @@ import { Component, JSX } from "solid-js"
|
||||
interface HintRowProps {
|
||||
children: JSX.Element
|
||||
class?: string
|
||||
ariaHidden?: boolean
|
||||
}
|
||||
|
||||
const HintRow: Component<HintRowProps> = (props) => {
|
||||
return <span class={`text-xs text-muted ${props.class || ""}`}>{props.children}</span>
|
||||
return (
|
||||
<span aria-hidden={props.ariaHidden} class={`text-xs text-muted ${props.class || ""}`}>
|
||||
{props.children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default HintRow
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Show, type Accessor, type Component } from "solid-js"
|
||||
import type { SessionThread } from "../../../stores/session-state"
|
||||
import type { Session } from "../../../types/session"
|
||||
import type { KeyboardShortcut } from "../../../lib/keyboard-registry"
|
||||
import { keyboardRegistry, type KeyboardShortcut } from "../../../lib/keyboard-registry"
|
||||
import type { DrawerViewState } from "./types"
|
||||
|
||||
import { PlusSquare, Search } from "lucide-solid"
|
||||
@@ -13,7 +13,6 @@ import InfoOutlinedIcon from "@suid/icons-material/InfoOutlined"
|
||||
|
||||
import SessionList from "../../session-list"
|
||||
import KeyboardHint from "../../keyboard-hint"
|
||||
import Kbd from "../../kbd"
|
||||
import WorktreeSelector from "../../worktree-selector"
|
||||
import AgentSelector from "../../agent-selector"
|
||||
import ModelSelector from "../../model-selector"
|
||||
@@ -166,11 +165,17 @@ const SessionSidebar: Component<SessionSidebarProps> = (props) => (
|
||||
|
||||
<ThinkingSelector instanceId={props.instanceId} currentModel={activeSession().model} />
|
||||
|
||||
<div class="session-sidebar-selector-hints" aria-hidden="true">
|
||||
<Kbd shortcut="cmd+shift+a" />
|
||||
<Kbd shortcut="cmd+shift+m" />
|
||||
<Kbd shortcut="cmd+shift+t" />
|
||||
</div>
|
||||
<KeyboardHint
|
||||
class="session-sidebar-selector-hints"
|
||||
ariaHidden={true}
|
||||
shortcuts={[
|
||||
keyboardRegistry.get("open-agent-selector"),
|
||||
keyboardRegistry.get("focus-model"),
|
||||
keyboardRegistry.get("focus-variant"),
|
||||
].filter((shortcut): shortcut is KeyboardShortcut => Boolean(shortcut))}
|
||||
separator=" "
|
||||
showDescription={false}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component, JSX, For } from "solid-js"
|
||||
import useMediaQuery from "@suid/material/useMediaQuery"
|
||||
import { isMac } from "../lib/keyboard-utils"
|
||||
|
||||
interface KbdProps {
|
||||
@@ -27,6 +28,9 @@ const SPECIAL_KEY_LABELS: Record<string, string> = {
|
||||
}
|
||||
|
||||
const Kbd: Component<KbdProps> = (props) => {
|
||||
const desktopQuery = useMediaQuery("(min-width: 1280px)")
|
||||
if (!desktopQuery()) return null
|
||||
|
||||
const parts = () => {
|
||||
if (props.children) return [{ text: props.children, isModifier: false }]
|
||||
if (!props.shortcut) return []
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import { Component, For } from "solid-js"
|
||||
import { formatShortcut, isMac } from "../lib/keyboard-utils"
|
||||
import useMediaQuery from "@suid/material/useMediaQuery"
|
||||
import type { KeyboardShortcut } from "../lib/keyboard-registry"
|
||||
import Kbd from "./kbd"
|
||||
import HintRow from "./hint-row"
|
||||
|
||||
const KeyboardHint: Component<{
|
||||
shortcuts: KeyboardShortcut[]
|
||||
separator?: string
|
||||
separator?: string | null
|
||||
showDescription?: boolean
|
||||
class?: string
|
||||
ariaHidden?: boolean
|
||||
}> = (props) => {
|
||||
// Centralize layout gating here so call sites don't need to.
|
||||
// We only show keyboard hint UI on desktop layouts.
|
||||
const desktopQuery = useMediaQuery("(min-width: 1280px)")
|
||||
|
||||
function buildShortcutString(shortcut: KeyboardShortcut): string {
|
||||
const parts: string[] = []
|
||||
|
||||
@@ -26,12 +32,14 @@ const KeyboardHint: Component<{
|
||||
return parts.join("+")
|
||||
}
|
||||
|
||||
if (!desktopQuery()) return null
|
||||
|
||||
return (
|
||||
<HintRow>
|
||||
<HintRow class={props.class} ariaHidden={props.ariaHidden}>
|
||||
<For each={props.shortcuts}>
|
||||
{(shortcut, i) => (
|
||||
<>
|
||||
{i() > 0 && <span class="mx-1">{props.separator || "•"}</span>}
|
||||
{i() > 0 && props.separator !== null && <span class="mx-1">{props.separator ?? "•"}</span>}
|
||||
{props.showDescription !== false && <span class="mr-1">{shortcut.description}</span>}
|
||||
<Kbd shortcut={buildShortcutString(shortcut)} />
|
||||
</>
|
||||
|
||||
@@ -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 UnifiedPicker from "./unified-picker"
|
||||
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 Kbd from "./kbd"
|
||||
import { getActiveInstance } from "../stores/instances"
|
||||
@@ -63,6 +63,7 @@ export default function PromptInput(props: PromptInputProps) {
|
||||
handleDrop,
|
||||
syncAttachmentCounters,
|
||||
handleExpandTextAttachment,
|
||||
handleRemoveAttachment,
|
||||
} = usePromptAttachments({
|
||||
instanceId: () => props.instanceId,
|
||||
sessionId: () => props.sessionId,
|
||||
@@ -87,6 +88,9 @@ export default function PromptInput(props: PromptInputProps) {
|
||||
if (!attachment) return
|
||||
handleExpandTextAttachment(attachment)
|
||||
},
|
||||
removeAttachment: (attachmentId: string) => {
|
||||
handleRemoveAttachment(attachmentId)
|
||||
},
|
||||
setPromptText: (text: string, opts?: { focus?: boolean }) => {
|
||||
const textarea = textareaRef
|
||||
if (textarea) {
|
||||
@@ -166,10 +170,7 @@ export default function PromptInput(props: PromptInputProps) {
|
||||
setAtPosition(null)
|
||||
setSearchQuery("")
|
||||
|
||||
const instanceId = props.instanceId
|
||||
const sessionId = props.sessionId
|
||||
const currentAttachments = untrack(() => getAttachments(instanceId, sessionId))
|
||||
syncAttachmentCounters(prompt(), currentAttachments)
|
||||
syncAttachmentCounters(prompt())
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
@@ -238,10 +239,10 @@ export default function PromptInput(props: PromptInputProps) {
|
||||
// Ignore attachments for slash commands, but keep them for next prompt.
|
||||
if (!isKnownSlashCommand) {
|
||||
clearAttachments(props.instanceId, props.sessionId)
|
||||
syncAttachmentCounters("", [])
|
||||
syncAttachmentCounters("")
|
||||
setIgnoredAtPositions(new Set<number>())
|
||||
} else {
|
||||
syncAttachmentCounters("", currentAttachments)
|
||||
syncAttachmentCounters("")
|
||||
setIgnoredAtPositions(new Set<number>())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import type { Attachment } from "../../types/attachment"
|
||||
|
||||
export function formatPastedPlaceholder(value: string | number) {
|
||||
return `[pasted #${value}]`
|
||||
}
|
||||
@@ -9,27 +7,27 @@ export function formatImagePlaceholder(value: string | number) {
|
||||
}
|
||||
|
||||
export function createPastedPlaceholderRegex() {
|
||||
return /\[pasted #(\d+)\]/g
|
||||
return /\[\s*pasted\s*#\s*(\d+)\s*\]/gi
|
||||
}
|
||||
|
||||
export function createImagePlaceholderRegex() {
|
||||
return /\[Image #(\d+)\]/g
|
||||
return /\[\s*Image\s*#\s*(\d+)\s*\]/gi
|
||||
}
|
||||
|
||||
export function createMentionRegex() {
|
||||
return /@(\S+)/g
|
||||
}
|
||||
|
||||
export const pastedDisplayCounterRegex = /pasted #(\d+)/
|
||||
export const imageDisplayCounterRegex = /Image #(\d+)/
|
||||
export const bracketedImageDisplayCounterRegex = /\[Image #(\d+)\]/
|
||||
export const pastedDisplayCounterRegex = /pasted #(\d+)/i
|
||||
export const imageDisplayCounterRegex = /Image #(\d+)/i
|
||||
export const bracketedImageDisplayCounterRegex = /\[\s*Image\s*#\s*(\d+)\s*\]/i
|
||||
|
||||
export function parseCounter(value: string) {
|
||||
const parsed = Number.parseInt(value, 10)
|
||||
return Number.isNaN(parsed) ? null : parsed
|
||||
}
|
||||
|
||||
export function findHighestAttachmentCounters(currentPrompt: string, sessionAttachments: Attachment[]) {
|
||||
export function findHighestAttachmentCounters(currentPrompt: string) {
|
||||
let highestPaste = 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())) {
|
||||
const parsed = parseCounter(match[1])
|
||||
if (parsed !== null) {
|
||||
|
||||
@@ -8,6 +8,7 @@ export type PromptInsertMode = "quote" | "code"
|
||||
export interface PromptInputApi {
|
||||
insertSelection(text: string, mode: PromptInsertMode): void
|
||||
expandTextAttachment(attachmentId: string): void
|
||||
removeAttachment(attachmentId: string): void
|
||||
setPromptText(text: string, opts?: { focus?: boolean }): 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 { createFileAttachment, createTextAttachment } from "../../types/attachment"
|
||||
import type { Attachment } from "../../types/attachment"
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
findHighestAttachmentCounters,
|
||||
formatImagePlaceholder,
|
||||
formatPastedPlaceholder,
|
||||
imageDisplayCounterRegex,
|
||||
pastedDisplayCounterRegex,
|
||||
} from "./attachmentPlaceholders"
|
||||
|
||||
@@ -23,7 +24,7 @@ type PromptAttachments = {
|
||||
attachments: Accessor<Attachment[]>
|
||||
pasteCount: Accessor<number>
|
||||
imageCount: Accessor<number>
|
||||
syncAttachmentCounters: (promptText: string, sessionAttachments: Attachment[]) => void
|
||||
syncAttachmentCounters: (promptText: string) => void
|
||||
|
||||
handlePaste: (e: ClipboardEvent) => Promise<void>
|
||||
isDragging: Accessor<boolean>
|
||||
@@ -41,45 +42,106 @@ export function usePromptAttachments(options: PromptAttachmentsOptions): PromptA
|
||||
const [pasteCount, setPasteCount] = createSignal(0)
|
||||
const [imageCount, setImageCount] = createSignal(0)
|
||||
|
||||
function syncAttachmentCounters(currentPrompt: string, sessionAttachments: Attachment[]) {
|
||||
const { highestPaste, highestImage } = findHighestAttachmentCounters(currentPrompt, sessionAttachments)
|
||||
function syncAttachmentCounters(currentPrompt: string) {
|
||||
const { highestPaste, highestImage } = findHighestAttachmentCounters(currentPrompt)
|
||||
setPasteCount(highestPaste)
|
||||
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) {
|
||||
const currentAttachments = attachments()
|
||||
const attachment = currentAttachments.find((a) => a.id === attachmentId)
|
||||
|
||||
// Always remove from store.
|
||||
removeAttachment(options.instanceId(), options.sessionId(), attachmentId)
|
||||
|
||||
if (attachment) {
|
||||
const currentPrompt = options.prompt()
|
||||
let newPrompt = currentPrompt
|
||||
if (!attachment) return
|
||||
|
||||
if (attachment.source.type === "file") {
|
||||
if (attachment.mediaType.startsWith("image/")) {
|
||||
const imageMatch = attachment.display.match(bracketedImageDisplayCounterRegex)
|
||||
if (imageMatch) {
|
||||
const placeholder = formatImagePlaceholder(imageMatch[1])
|
||||
newPrompt = currentPrompt.replace(placeholder, "").replace(/\s+/g, " ").trim()
|
||||
}
|
||||
} else {
|
||||
const filename = attachment.filename
|
||||
newPrompt = currentPrompt.replace(`@${filename}`, "").replace(/\s+/g, " ").trim()
|
||||
const currentPrompt = options.prompt()
|
||||
let nextPrompt = currentPrompt
|
||||
|
||||
if (attachment.source.type === "file") {
|
||||
if (attachment.mediaType.startsWith("image/")) {
|
||||
const imageMatch =
|
||||
attachment.display.match(bracketedImageDisplayCounterRegex) || attachment.display.match(imageDisplayCounterRegex)
|
||||
if (imageMatch) {
|
||||
nextPrompt = removeTokenFromPrompt(currentPrompt, createLooseImagePlaceholderRegex(imageMatch[1]))
|
||||
}
|
||||
} else if (attachment.source.type === "agent") {
|
||||
const agentName = attachment.filename
|
||||
newPrompt = currentPrompt.replace(`@${agentName}`, "").replace(/\s+/g, " ").trim()
|
||||
} else if (attachment.source.type === "text") {
|
||||
const placeholderMatch = attachment.display.match(pastedDisplayCounterRegex)
|
||||
if (placeholderMatch) {
|
||||
const placeholder = formatPastedPlaceholder(placeholderMatch[1])
|
||||
newPrompt = currentPrompt.replace(placeholder, "").replace(/\s+/g, " ").trim()
|
||||
} else {
|
||||
// For file mentions we insert `@<path>`, but the chip might display `@<filename>`.
|
||||
const candidates = [attachment.source.path, attachment.filename]
|
||||
for (const candidate of candidates) {
|
||||
if (!candidate) continue
|
||||
const mentionRegex = new RegExp(`@${escapeRegExp(candidate)}(?=\\s|$)`, "i")
|
||||
nextPrompt = removeTokenFromPrompt(nextPrompt, mentionRegex)
|
||||
}
|
||||
}
|
||||
} 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()
|
||||
if (!blob) continue
|
||||
|
||||
const count = imageCount() + 1
|
||||
const { highestImage } = findHighestAttachmentCounters(options.prompt())
|
||||
const count = highestImage + 1
|
||||
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()
|
||||
reader.onload = () => {
|
||||
const base64Data = (reader.result as string).split(",")[1]
|
||||
const display = formatImagePlaceholder(count)
|
||||
const filename = `image-${count}.png`
|
||||
|
||||
const attachment = createFileAttachment(
|
||||
@@ -160,24 +241,8 @@ export function usePromptAttachments(options: PromptAttachmentsOptions): PromptA
|
||||
options.instanceFolder(),
|
||||
)
|
||||
attachment.url = `data:image/png;base64,${base64Data}`
|
||||
attachment.display = display
|
||||
attachment.display = placeholder
|
||||
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)
|
||||
|
||||
@@ -196,7 +261,8 @@ export function usePromptAttachments(options: PromptAttachmentsOptions): PromptA
|
||||
if (isLongPaste) {
|
||||
e.preventDefault()
|
||||
|
||||
const count = pasteCount() + 1
|
||||
const { highestPaste } = findHighestAttachmentCounters(options.prompt())
|
||||
const count = highestPaste + 1
|
||||
setPasteCount(count)
|
||||
|
||||
const summary = lineCount > 1 ? `${lineCount} lines` : `${charCount} chars`
|
||||
@@ -204,14 +270,12 @@ export function usePromptAttachments(options: PromptAttachmentsOptions): PromptA
|
||||
const filename = `paste-${count}.txt`
|
||||
|
||||
const attachment = createTextAttachment(pastedText, display, filename)
|
||||
addAttachment(options.instanceId(), options.sessionId(), attachment)
|
||||
|
||||
const placeholder = formatPastedPlaceholder(count)
|
||||
const textarea = options.getTextarea()
|
||||
if (textarea) {
|
||||
const start = textarea.selectionStart
|
||||
const end = textarea.selectionEnd
|
||||
const currentText = options.prompt()
|
||||
const placeholder = formatPastedPlaceholder(count)
|
||||
const newText = currentText.substring(0, start) + placeholder + currentText.substring(end)
|
||||
options.setPrompt(newText)
|
||||
|
||||
@@ -220,7 +284,11 @@ export function usePromptAttachments(options: PromptAttachmentsOptions): PromptA
|
||||
textarea.setSelectionRange(newCursorPos, newCursorPos)
|
||||
textarea.focus()
|
||||
}, 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}>
|
||||
<PromptAttachmentsBar
|
||||
attachments={attachments()}
|
||||
onRemoveAttachment={(attachmentId) => removeAttachment(props.instanceId, props.sessionId, attachmentId)}
|
||||
onExpandTextAttachment={(attachmentId) => promptInputApi?.expandTextAttachment(attachmentId)}
|
||||
/>
|
||||
</Show>
|
||||
<Show when={attachments().length > 0}>
|
||||
<PromptAttachmentsBar
|
||||
attachments={attachments()}
|
||||
onRemoveAttachment={(attachmentId) => {
|
||||
if (promptInputApi) {
|
||||
promptInputApi.removeAttachment(attachmentId)
|
||||
return
|
||||
}
|
||||
removeAttachment(props.instanceId, props.sessionId, attachmentId)
|
||||
}}
|
||||
onExpandTextAttachment={(attachmentId) => promptInputApi?.expandTextAttachment(attachmentId)}
|
||||
/>
|
||||
</Show>
|
||||
|
||||
<PromptInput
|
||||
instanceId={props.instanceId}
|
||||
|
||||
Reference in New Issue
Block a user