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 @@
+
+
+
+
+
+