diff --git a/packages/ui/src/components/prompt-input.tsx b/packages/ui/src/components/prompt-input.tsx index bffd3404..4ed7d407 100644 --- a/packages/ui/src/components/prompt-input.tsx +++ b/packages/ui/src/components/prompt-input.tsx @@ -720,8 +720,24 @@ export default function PromptInput(props: PromptInputProps) { const filename = file.name const mime = file.type || "text/plain" - const attachment = createFileAttachment(path, filename, mime, undefined, props.instanceFolder) - addAttachment(props.instanceId, props.sessionId, attachment) + const createAndStoreAttachment = (previewUrl?: string) => { + const attachment = createFileAttachment(path, filename, mime, undefined, props.instanceFolder) + if (previewUrl && mime.startsWith("image/")) { + attachment.url = previewUrl + } + addAttachment(props.instanceId, props.sessionId, attachment) + } + + if (mime.startsWith("image/") && typeof FileReader !== "undefined") { + const reader = new FileReader() + reader.onload = () => { + const result = typeof reader.result === "string" ? reader.result : undefined + createAndStoreAttachment(result) + } + reader.readAsDataURL(file) + } else { + createAndStoreAttachment() + } } textareaRef?.focus() @@ -772,7 +788,7 @@ export default function PromptInput(props: PromptInputProps) { {(attachment) => { const isImage = attachment.mediaType.startsWith("image/") return ( -
+
+ +
+ {attachment.filename} +
+
) }} diff --git a/packages/ui/src/styles/messaging/prompt-input.css b/packages/ui/src/styles/messaging/prompt-input.css index 2bb34265..560c950d 100644 --- a/packages/ui/src/styles/messaging/prompt-input.css +++ b/packages/ui/src/styles/messaging/prompt-input.css @@ -103,6 +103,35 @@ ring-color: var(--attachment-chip-ring); } +.attachment-chip-image { + position: relative; +} + +.attachment-chip-preview { + display: none; + position: absolute; + bottom: calc(100% + 6px); + left: 0; + padding: 8px; + background-color: var(--surface-base); + border: 1px solid var(--border-base); + border-radius: 10px; + box-shadow: 0 16px 40px rgba(15, 23, 42, 0.25); + z-index: 20; +} + +.attachment-chip-preview img { + display: block; + max-width: 320px; + max-height: 320px; + border-radius: 8px; + object-fit: contain; +} + +.attachment-chip-image:hover .attachment-chip-preview { + display: block; +} + .attachment-remove { @apply ml-0.5 flex h-4 w-4 items-center justify-center rounded transition-colors; }