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) {