attachments fixes and polishing

frdel committed Jul 10, 2025 at 13:12 UTC bb7388549a422f4fd4788cd2a2489abc4bad6af9
4 files changed +32 -56
webui/components/chat/attachments/attachmentsStore.js
+7 -14
@@ -207,6 +207,13 @@ const model = {
207 }
208 },
209
210 + // Update handleFileUpload to use the attachments store
211 + handleFileUpload(event) {
212 + const files = event.target.files;
213 + this.handleFiles(files);
214 + event.target.value = ""; // clear uploader selection to fix issue where same file is ignored the second time
215 + },
216 +
217 // File handling logic (moved from index.js)
218 handleFiles(files) {
219 console.log("handleFiles called with", files.length, "files");
@@ -265,20 +272,6 @@ const model = {
272 return `/a0/tmp/uploads/${encodeURIComponent(filename)}`;
273 },
274
268 - // // Get file metadata from server (for device sync and enhanced UI)
269 - // async getFileMetadata(filename) {
270 - // try {
271 - // const response = await fetch(`/image_get?path=/a0/tmp/uploads/${encodeURIComponent(filename)}&metadata=true`);
272 - // if (response.ok) {
273 - // return await response.json();
274 - // }
275 - // return null;
276 - // } catch (error) {
277 - // console.error('Failed to get file metadata:', error);
278 - // return null;
279 - // }
280 - // },
281 -
275 // Check if file is an image based on extension
276 isImageFile(filename) {
277 const imageExtensions = ["jpg", "jpeg", "png", "gif", "bmp", "webp", "svg"];
webui/index.html
+1 -1
@@ -393,7 +393,7 @@
393 d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z" />
394 </svg>
395 </label>
396 - <input type="file" id="file-input" accept="*" multiple style="display: none" @change="window.handleFileUpload($event)">
396 + <input type="file" id="file-input" accept="*" multiple style="display: none" @change="$store.chatAttachments.handleFileUpload($event)">
397
398 <div x-show="showTooltip" class="tooltip">
399 Add attachments to the message
webui/index.js
+20 -37
@@ -2,6 +2,7 @@ import * as msgs from "./js/messages.js";
2 import { speech } from "./js/speech.js";
3 import * as api from "./js/api.js";
4 import * as css from "./js/css.js";
5 +import { sleep } from "./js/sleep.js";
6 import { store as attachmentsStore } from "./components/chat/attachments/attachmentsStore.js";
7
8 window.fetchApi = api.fetchApi; // TODO - backward compatibility for non-modular scripts, remove once refactored to alpine
@@ -95,29 +96,40 @@ export async function sendMessage() {
96 try {
97 const message = chatInput.value.trim();
98 // const attachmentsStore = attachmentsStore; //window.Alpine ? Alpine.store('chatAttachments') : null;
98 - const attachments = attachmentsStore.attachments; // attachmentsStore ? attachmentsStore.attachments : [];
99 - const hasAttachments = attachmentsStore.hasAttachments; // attachmentsStore ? attachmentsStore.hasAttachments : false;
99 + const attachmentsWithUrls = attachmentsStore.getAttachmentsForSending();
100 + const hasAttachments = attachmentsWithUrls.length > 0;
101
102 if (message || hasAttachments) {
103 let response;
104 const messageId = generateGUID();
105
106 + // Clear input and attachments
107 + chatInput.value = "";
108 + attachmentsStore.clearAttachments();
109 + adjustTextareaHeight();
110 +
111 // Include attachments in the user message
112 if (hasAttachments) {
107 - const attachmentsWithUrls = attachmentsStore.getAttachmentsForSending();
113 + const heading =
114 + attachmentsWithUrls.length > 0
115 + ? "Uploading attachments..."
116 + : "User message";
117
118 // Render user message with attachments
110 - setMessage(messageId, "user", "", message, false, {
111 - attachments: attachmentsWithUrls,
119 + setMessage(messageId, "user", heading, message, false, {
120 + // attachments: attachmentsWithUrls, // skip here, let the backend properly log them
121 });
122
123 + // sleep one frame to render the message before upload starts - better UX
124 + sleep(0);
125 +
126 const formData = new FormData();
127 formData.append("text", message);
128 formData.append("context", context);
129 formData.append("message_id", messageId);
130
119 - for (let i = 0; i < attachments.length; i++) {
120 - formData.append("attachments", attachments[i].file);
131 + for (let i = 0; i < attachmentsWithUrls.length; i++) {
132 + formData.append("attachments", attachmentsWithUrls[i].file);
133 }
134
135 response = await api.fetchApi("/message_async", {
@@ -144,24 +156,9 @@ export async function sendMessage() {
156 const jsonResponse = await response.json();
157 if (!jsonResponse) {
158 toast("No response returned.", "error");
147 - }
148 - // else if (!jsonResponse.ok) {
149 - // if (jsonResponse.message) {
150 - // toast(jsonResponse.message, "error");
151 - // } else {
152 - // toast("Undefined error.", "error");
153 - // }
154 - // }
155 - else {
159 + } else {
160 setContext(jsonResponse.context);
161 }
158 -
159 - // Clear input and attachments
160 - chatInput.value = "";
161 - // if (attachmentsStore) {
162 - attachmentsStore.clearAttachments();
163 - // }
164 - adjustTextareaHeight();
162 }
163 } catch (e) {
164 toastFetchError("Error sending message", e);
@@ -1097,20 +1094,6 @@ async function startPolling() {
1094
1095 document.addEventListener("DOMContentLoaded", startPolling);
1096
1100 -// Drag and drop functionality has been moved to attachmentsStore.js
1101 -
1102 -// Update handleFileUpload to use the attachments store
1103 -window.handleFileUpload = function (event) {
1104 - // console.log('handleFileUpload called with files:', event.target.files.length);
1105 - const files = event.target.files;
1106 - // if (window.Alpine && Alpine.store('chatAttachments')) {
1107 - // console.log('Calling store handleFiles...');
1108 - // Alpine.store('chatAttachments').handleFiles(files);
1109 - // } else {
1110 - // console.error('Alpine or chatAttachments store not found!');
1111 - // }
1112 - attachmentsStore.handleFiles(files);
1113 -};
1097
1098 // Setup event handlers once the DOM is fully loaded
1099 document.addEventListener("DOMContentLoaded", function () {
webui/js/messages.js
+4 -4
@@ -15,9 +15,9 @@ export function setMessage(id, type, heading, content, temp, kvps = null) {
15
16 if (messageContainer) {
17 // Don't re-render user messages
18 - if (type === "user") {
19 - return; // Skip re-rendering
20 - }
18 + // if (type === "user") {
19 + // return; // Skip re-rendering
20 + // }
21 // For other types, update the message
22 messageContainer.innerHTML = "";
23 } else {
@@ -365,7 +365,7 @@ export function drawMessageUser(
365 const headingElement = document.createElement("h4");
366 headingElement.classList.add("msg-heading");
367 headingElement.innerHTML =
368 - "User message <span class='icon material-symbols-outlined'>person</span>";
368 + `${heading} <span class='icon material-symbols-outlined'>person</span>`;
369 messageDiv.appendChild(headingElement);
370
371 if (content && content.trim().length > 0) {