diff --git a/internal/service/static/main.css b/internal/service/static/main.css index 8e0b8bb..d477337 100644 --- a/internal/service/static/main.css +++ b/internal/service/static/main.css @@ -95,7 +95,7 @@ body { /* ==================== */ .chat { - flex: 1 1 0; + flex: 1 1 100%; min-height: 0; overflow-y: auto; overscroll-behavior: contain; @@ -1242,3 +1242,32 @@ body { .message__placeholder { color: var(--color-text-muted); } + +/* Image attachment pills in user messages */ +.message__image-pills { + display: flex; + flex-wrap: wrap; + gap: var(--s-2); + padding: 0 var(--s-1) var(--s-2); +} + +.message__image-pill { + display: flex; + flex: 1 1 100%; + min-width: 0; + align-items: center; + gap: 0.25rem; + padding: 0.125rem 0.375rem; + font-size: var(--s-1); + background: var(--color-gray1); + border-radius: var(--radius); + color: var(--color-text); +} + +.message__image-pill-name { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/internal/service/static/main.js b/internal/service/static/main.js index 1f4fff3..0a553a2 100644 --- a/internal/service/static/main.js +++ b/internal/service/static/main.js @@ -959,6 +959,7 @@ class Odidere { imageParts.push({ type: ContentTypeImageURL, image_url: { url: dataURL }, + filename: file.name, }); } else { const content = await file.text(); diff --git a/internal/service/static/render.js b/internal/service/static/render.js index d293d2b..10bafd4 100644 --- a/internal/service/static/render.js +++ b/internal/service/static/render.js @@ -34,6 +34,7 @@ export class Renderer { 'tpl-assistant-message', ); this.$tplAttachmentChip = document.getElementById('tpl-attachment-chip'); + this.$tplImagePill = document.getElementById('tpl-image-pill'); this.$tplDebugRow = document.getElementById('tpl-debug-row'); this.$tplErrorMessage = document.getElementById('tpl-error-message'); this.$tplUserMessage = document.getElementById('tpl-user-message'); @@ -132,6 +133,8 @@ export class Renderer { const content = extractText(message.content); $msg.querySelector('.message__content').textContent = content; + this._renderImagePills($msg, message); + const $dl = $msg.querySelector('.message__debug-list'); if (message.meta?.language) this._appendDebugRow($dl, 'Language', message.meta.language); @@ -512,6 +515,8 @@ export class Renderer { $msg.querySelector('.message__content').textContent = content; $msg.dataset.copyText = content; + this._renderImagePills($msg, message); + const $dl = $msg.querySelector('.message__debug-list'); if (meta?.language) this._appendDebugRow($dl, 'Language', meta.language); if (meta?.voice) this._appendDebugRow($dl, 'Voice', meta.voice); @@ -670,6 +675,24 @@ export class Renderer { this.$chat.insertBefore($msg, this.$chatLoading); } + _renderImagePills($msg, message) { + if (!Array.isArray(message.content)) return; + const imageParts = message.content.filter((p) => p.type === 'image_url'); + if (imageParts.length === 0) return; + + const $wrapper = document.createElement('div'); + $wrapper.className = 'message__image-pills'; + for (const part of imageParts) { + const $pill = this.$tplImagePill.content.cloneNode(true); + $pill.querySelector('.message__image-pill-name').textContent = + part.filename || 'Image'; + $wrapper.appendChild($pill.firstElementChild); + } + $msg + .querySelector('.message__body') + .insertBefore($wrapper, $msg.querySelector('.message__actions')); + } + _populateDebugPanel($dl, meta) { if (meta?.voice) this._appendDebugRow($dl, 'Voice', meta.voice); if (meta?.usage) { diff --git a/internal/service/templates/static/templates.gohtml b/internal/service/templates/static/templates.gohtml index f6bb369..0d38a48 100644 --- a/internal/service/templates/static/templates.gohtml +++ b/internal/service/templates/static/templates.gohtml @@ -152,6 +152,12 @@ + +