From ec637aa21e65ede960d75b7ba9242ad0ae6bfcac Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Mon, 5 Jan 2026 12:52:49 -0600 Subject: fix(app): store image attachments --- packages/app/src/components/prompt-input.tsx | 50 ++++++++++++---------------- 1 file changed, 22 insertions(+), 28 deletions(-) (limited to 'packages/app/src/components') diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index d269976f3..fc4a3d1e6 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -165,6 +165,9 @@ export const PromptInput: Component = (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 = (props) => { savedPrompt: Prompt | null placeholder: number dragging: boolean - imageAttachments: ImageAttachmentPart[] mode: "normal" | "shell" applyingHistory: boolean }>({ @@ -181,7 +183,6 @@ export const PromptInput: Component = (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 = (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 = (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 = (props) => { cursorPosition = getCursorPosition(editorRef) } - renderEditor(currentParts) + renderEditor(inputParts) if (cursorPosition !== null) { setCursorPosition(editorRef, cursorPosition) @@ -638,11 +635,12 @@ export const PromptInput: Component = (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 = (props) => { setStore("savedPrompt", null) } - prompt.set(rawParts, cursorPosition) + prompt.set([...rawParts, ...images], cursorPosition) queueScroll() } @@ -784,16 +782,14 @@ export const PromptInput: Component = (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 = (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 = (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 = (props) => { - 0}> + 0}>
- + {(attachment) => (
= (props) => { "font-mono!": store.mode === "shell", }} /> - +
{store.mode === "shell" ? "Enter shell command..." @@ -1658,7 +1652,7 @@ export const PromptInput: Component = (props) => { >