From c2b876c3338390a2a856ae983c0d4ca351fa6f03 Mon Sep 17 00:00:00 2001 From: italic-jinxin <106428113+italic-jinxin@users.noreply.github.com> Date: Fri, 13 Mar 2026 23:02:15 +0800 Subject: [PATCH] feat: add image+text support --- src/channels/web/static/app.js | 41 ++++++++++++++++++++++++++++--- src/channels/web/static/style.css | 16 ++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/src/channels/web/static/app.js b/src/channels/web/static/app.js index de1f83b6..685a83f4 100644 --- a/src/channels/web/static/app.js +++ b/src/channels/web/static/app.js @@ -438,7 +438,8 @@ function sendMessage() { const content = input.value.trim(); if (!content && stagedImages.length === 0) return; - addMessage('user', content || '(images attached)'); + const imagesToDisplay = stagedImages.slice(); // snapshot before clearing + addMessage('user', content, imagesToDisplay); input.value = ''; autoResizeTextarea(input); input.focus(); @@ -544,6 +545,24 @@ document.getElementById('chat-input').addEventListener('paste', (e) => { } }); +document.getElementById('chat-input').addEventListener('dragover', (e) => { + const hasImage = Array.from(e.dataTransfer.items || []).some( + item => item.kind === 'file' && item.type.startsWith('image/') + ); + if (hasImage) { + e.preventDefault(); + e.dataTransfer.dropEffect = 'copy'; + } +}); + +document.getElementById('chat-input').addEventListener('drop', (e) => { + const files = Array.from(e.dataTransfer.files || []).filter(f => f.type.startsWith('image/')); + if (files.length > 0) { + e.preventDefault(); + handleImageFiles(files); + } +}); + function addGeneratedImage(dataUrl, path) { const container = document.getElementById('chat-messages'); const card = document.createElement('div'); @@ -702,12 +721,28 @@ function copyCodeBlock(btn) { }); } -function addMessage(role, content) { +function addMessage(role, content, images) { const container = document.getElementById('chat-messages'); const div = document.createElement('div'); div.className = 'message ' + role; if (role === 'user') { - div.textContent = content; + if (images && images.length > 0) { + const imgStrip = document.createElement('div'); + imgStrip.className = 'message-images'; + images.forEach(img => { + const thumb = document.createElement('img'); + thumb.className = 'message-image-thumb'; + thumb.src = img.dataUrl; + thumb.alt = 'Attached image'; + imgStrip.appendChild(thumb); + }); + div.appendChild(imgStrip); + } + if (content) { + const textDiv = document.createElement('div'); + textDiv.textContent = content; + div.appendChild(textDiv); + } } else { div.setAttribute('data-raw', content); div.innerHTML = renderMarkdown(content); diff --git a/src/channels/web/static/style.css b/src/channels/web/static/style.css index a0985ce3..8b50f87f 100644 --- a/src/channels/web/static/style.css +++ b/src/channels/web/static/style.css @@ -3871,6 +3871,22 @@ mark { background: #c33; } +/* Images in user message bubbles */ +.message.user .message-images { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-bottom: 4px; +} + +.message.user .message-image-thumb { + max-width: 240px; + max-height: 200px; + border-radius: 8px; + object-fit: cover; + display: block; +} + /* Generated Image */ .generated-image-card { max-width: 512px;