fix(app): store image attachments
This commit is contained in:
@@ -165,6 +165,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
},
|
||||
)
|
||||
const working = createMemo(() => status()?.type !== "idle")
|
||||
const imageAttachments = createMemo(
|
||||
() => prompt.current().filter((part) => part.type === "image") as ImageAttachmentPart[],
|
||||
)
|
||||
|
||||
const [store, setStore] = createStore<{
|
||||
popover: "at" | "slash" | null
|
||||
@@ -172,7 +175,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
savedPrompt: Prompt | null
|
||||
placeholder: number
|
||||
dragging: boolean
|
||||
imageAttachments: ImageAttachmentPart[]
|
||||
mode: "normal" | "shell"
|
||||
applyingHistory: boolean
|
||||
}>({
|
||||
@@ -181,7 +183,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
savedPrompt: null,
|
||||
placeholder: Math.floor(Math.random() * PLACEHOLDERS.length),
|
||||
dragging: false,
|
||||
imageAttachments: [],
|
||||
mode: "normal",
|
||||
applyingHistory: false,
|
||||
})
|
||||
@@ -274,21 +275,16 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
mime: file.type,
|
||||
dataUrl,
|
||||
}
|
||||
setStore(
|
||||
produce((draft) => {
|
||||
draft.imageAttachments.push(attachment)
|
||||
}),
|
||||
)
|
||||
const cursorPosition = prompt.cursor() ?? getCursorPosition(editorRef)
|
||||
prompt.set([...prompt.current(), attachment], cursorPosition)
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
}
|
||||
|
||||
const removeImageAttachment = (id: string) => {
|
||||
setStore(
|
||||
produce((draft) => {
|
||||
draft.imageAttachments = draft.imageAttachments.filter((a) => a.id !== id)
|
||||
}),
|
||||
)
|
||||
const current = prompt.current()
|
||||
const next = current.filter((part) => part.type !== "image" || part.id !== id)
|
||||
prompt.set(next, prompt.cursor())
|
||||
}
|
||||
|
||||
const handlePaste = async (event: ClipboardEvent) => {
|
||||
@@ -538,8 +534,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
on(
|
||||
() => prompt.current(),
|
||||
(currentParts) => {
|
||||
const inputParts = currentParts.filter((part) => part.type !== "image") as Prompt
|
||||
const domParts = parseFromDOM()
|
||||
if (isNormalizedEditor() && isPromptEqual(currentParts, domParts)) return
|
||||
if (isNormalizedEditor() && isPromptEqual(inputParts, domParts)) return
|
||||
|
||||
const selection = window.getSelection()
|
||||
let cursorPosition: number | null = null
|
||||
@@ -547,7 +544,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
cursorPosition = getCursorPosition(editorRef)
|
||||
}
|
||||
|
||||
renderEditor(currentParts)
|
||||
renderEditor(inputParts)
|
||||
|
||||
if (cursorPosition !== null) {
|
||||
setCursorPosition(editorRef, cursorPosition)
|
||||
@@ -638,11 +635,12 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
|
||||
const handleInput = () => {
|
||||
const rawParts = parseFromDOM()
|
||||
const images = imageAttachments()
|
||||
const cursorPosition = getCursorPosition(editorRef)
|
||||
const rawText = rawParts.map((p) => ("content" in p ? p.content : "")).join("")
|
||||
const trimmed = rawText.replace(/\u200B/g, "").trim()
|
||||
const hasNonText = rawParts.some((part) => part.type !== "text")
|
||||
const shouldReset = trimmed.length === 0 && !hasNonText
|
||||
const shouldReset = trimmed.length === 0 && !hasNonText && images.length === 0
|
||||
|
||||
if (shouldReset) {
|
||||
setStore("popover", null)
|
||||
@@ -681,7 +679,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
setStore("savedPrompt", null)
|
||||
}
|
||||
|
||||
prompt.set(rawParts, cursorPosition)
|
||||
prompt.set([...rawParts, ...images], cursorPosition)
|
||||
queueScroll()
|
||||
}
|
||||
|
||||
@@ -784,16 +782,14 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
.map((p) => ("content" in p ? p.content : ""))
|
||||
.join("")
|
||||
.trim()
|
||||
if (!text) return
|
||||
const hasImages = prompt.some((part) => part.type === "image")
|
||||
if (!text && !hasImages) return
|
||||
|
||||
const entry = clonePromptParts(prompt)
|
||||
const currentHistory = mode === "shell" ? shellHistory : history
|
||||
const setCurrentHistory = mode === "shell" ? setShellHistory : setHistory
|
||||
const lastEntry = currentHistory.entries[0]
|
||||
if (lastEntry) {
|
||||
const lastText = lastEntry.map((p) => ("content" in p ? p.content : "")).join("")
|
||||
if (lastText === text) return
|
||||
}
|
||||
if (lastEntry && isPromptEqual(lastEntry, entry)) return
|
||||
|
||||
setCurrentHistory("entries", (entries) => [entry, ...entries].slice(0, MAX_HISTORY))
|
||||
}
|
||||
@@ -967,7 +963,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
|
||||
const currentPrompt = prompt.current()
|
||||
const text = currentPrompt.map((part) => ("content" in part ? part.content : "")).join("")
|
||||
const images = store.imageAttachments.slice()
|
||||
const images = imageAttachments().slice()
|
||||
const mode = store.mode
|
||||
|
||||
if (text.trim().length === 0 && images.length === 0) {
|
||||
@@ -1061,14 +1057,12 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
|
||||
const clearInput = () => {
|
||||
prompt.reset()
|
||||
setStore("imageAttachments", [])
|
||||
setStore("mode", "normal")
|
||||
setStore("popover", null)
|
||||
}
|
||||
|
||||
const restoreInput = () => {
|
||||
prompt.set(currentPrompt, promptLength(currentPrompt))
|
||||
setStore("imageAttachments", images)
|
||||
setStore("mode", mode)
|
||||
setStore("popover", null)
|
||||
requestAnimationFrame(() => {
|
||||
@@ -1471,9 +1465,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={store.imageAttachments.length > 0}>
|
||||
<Show when={imageAttachments().length > 0}>
|
||||
<div class="flex flex-wrap gap-2 px-3 pt-3">
|
||||
<For each={store.imageAttachments}>
|
||||
<For each={imageAttachments()}>
|
||||
{(attachment) => (
|
||||
<div class="relative group">
|
||||
<Show
|
||||
@@ -1525,7 +1519,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
"font-mono!": store.mode === "shell",
|
||||
}}
|
||||
/>
|
||||
<Show when={!prompt.dirty() && store.imageAttachments.length === 0}>
|
||||
<Show when={!prompt.dirty()}>
|
||||
<div class="absolute top-0 inset-x-0 px-5 py-3 pr-12 text-14-regular text-text-weak pointer-events-none whitespace-nowrap truncate">
|
||||
{store.mode === "shell"
|
||||
? "Enter shell command..."
|
||||
@@ -1658,7 +1652,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
>
|
||||
<IconButton
|
||||
type="submit"
|
||||
disabled={!prompt.dirty() && store.imageAttachments.length === 0 && !working()}
|
||||
disabled={!prompt.dirty() && !working()}
|
||||
icon={working() ? "stop" : "arrow-up"}
|
||||
variant="primary"
|
||||
class="h-6 w-4.5"
|
||||
|
||||
Reference in New Issue
Block a user