Allow stalled chat uploads to be cancelled

This commit is contained in:
pewdiepie-archdaemon
2026-06-29 02:06:39 +00:00
parent b419caf9f7
commit e2c8b8eb37
2 changed files with 56 additions and 1 deletions
+12
View File
@@ -292,6 +292,9 @@ import { wireArrowUpRecall, getLastUserMessageFromChatHistory } from './composer
// If currently streaming, stop it // If currently streaming, stop it
if (isStreaming) { if (isStreaming) {
if (fileHandlerModule.isUploading && fileHandlerModule.isUploading()) {
fileHandlerModule.cancelUpload && fileHandlerModule.cancelUpload();
}
// Cancel server-side research if in progress // Cancel server-side research if in progress
const _cancelSid = sessionModule.getCurrentSessionId(); const _cancelSid = sessionModule.getCurrentSessionId();
if (_cancelSid && _researchingStreamIds.has(_cancelSid)) { if (_cancelSid && _researchingStreamIds.has(_cancelSid)) {
@@ -687,6 +690,15 @@ import { wireArrowUpRecall, getLastUserMessageFromChatHistory } from './composer
} catch(e) { } catch(e) {
console.error('upload failed', e); console.error('upload failed', e);
} }
if (_pendingAttachInfo && !ids.length && !(_pendingRegenAttachments && _pendingRegenAttachments.length)) {
if (_userMsgEl && _userMsgEl.parentNode) _userMsgEl.remove();
if (fileHandlerModule.wasLastUploadCancelled && !fileHandlerModule.wasLastUploadCancelled()) {
uiModule.showError && uiModule.showError('Upload failed. Attachment kept so you can retry.');
}
updateSubmitButton('idle', submitBtn);
_releaseSendFlag();
return;
}
// Carry over the original message's file-ids on a regenerate so the new // Carry over the original message's file-ids on a regenerate so the new
// send still references the same photos / docs (and picks up the user's // send still references the same photos / docs (and picks up the user's
+44 -1
View File
@@ -15,6 +15,9 @@ let uploaded = [];
let _lastUploadedMeta = []; let _lastUploadedMeta = [];
let API_BASE = ''; let API_BASE = '';
let _uploadSpinners = []; let _uploadSpinners = [];
let _uploadAbortCtrl = null;
let _uploading = false;
let _lastUploadCancelled = false;
const _previewUrls = new WeakMap(); const _previewUrls = new WeakMap();
const MAX_FILES = 10; const MAX_FILES = 10;
@@ -130,6 +133,7 @@ function _createChip(f, idx) {
* Remove a pending file by index * Remove a pending file by index
*/ */
export function removePending(idx) { export function removePending(idx) {
if (_uploading) cancelUpload();
_revokePreviewUrl(pendingFiles[idx]); _revokePreviewUrl(pendingFiles[idx]);
pendingFiles.splice(idx, 1); pendingFiles.splice(idx, 1);
renderAttachStrip(); renderAttachStrip();
@@ -140,6 +144,7 @@ export function removePending(idx) {
*/ */
export async function uploadPending() { export async function uploadPending() {
if (pendingFiles.length === 0) return []; if (pendingFiles.length === 0) return [];
_lastUploadCancelled = false;
// The message bubble is shown immediately, but the upload can take a moment — // The message bubble is shown immediately, but the upload can take a moment —
// dim the chips and overlay a whirlpool so it's clear the files are still // dim the chips and overlay a whirlpool so it's clear the files are still
@@ -164,11 +169,19 @@ export async function uploadPending() {
const fd = new FormData(); const fd = new FormData();
pendingFiles.forEach(f => fd.append('files', f, f.name || 'paste.png')); pendingFiles.forEach(f => fd.append('files', f, f.name || 'paste.png'));
_uploadAbortCtrl = new AbortController();
_uploading = true;
const timeoutId = setTimeout(() => {
if (_uploadAbortCtrl && !_uploadAbortCtrl.signal.aborted) {
try { _uploadAbortCtrl.abort(); } catch (_) {}
}
}, 120000);
try { try {
const res = await fetch(`${API_BASE}/api/upload`, { const res = await fetch(`${API_BASE}/api/upload`, {
method: 'POST', method: 'POST',
body: fd body: fd,
signal: _uploadAbortCtrl.signal,
}); });
if (!res.ok) { if (!res.ok) {
// Surface the failure instead of swallowing it. Previously a non-OK // Surface the failure instead of swallowing it. Previously a non-OK
@@ -193,7 +206,18 @@ export async function uploadPending() {
// returned shape as `ids` for backward-compatibility with existing call sites. // returned shape as `ids` for backward-compatibility with existing call sites.
_lastUploadedMeta = uploaded; _lastUploadedMeta = uploaded;
return uploaded.map(x => x.id); return uploaded.map(x => x.id);
} catch (e) {
if (e && e.name === 'AbortError') {
_lastUploadCancelled = true;
_showToast('Upload cancelled');
return [];
}
_showToast('Upload failed: ' + (e?.message || 'network error'));
return [];
} finally { } finally {
clearTimeout(timeoutId);
_uploading = false;
_uploadAbortCtrl = null;
_uploadSpinners.forEach(sp => { try { sp.stop && sp.stop(); } catch (_) {} }); _uploadSpinners.forEach(sp => { try { sp.stop && sp.stop(); } catch (_) {} });
_uploadSpinners = []; _uploadSpinners = [];
if (strip) strip.classList.remove('attach-uploading'); if (strip) strip.classList.remove('attach-uploading');
@@ -266,6 +290,7 @@ export function getPendingInfo() {
* Clear all pending files * Clear all pending files
*/ */
export function clearPending() { export function clearPending() {
if (_uploading) cancelUpload();
pendingFiles.forEach(_revokePreviewUrl); pendingFiles.forEach(_revokePreviewUrl);
pendingFiles = []; pendingFiles = [];
renderAttachStrip(); renderAttachStrip();
@@ -276,6 +301,21 @@ export function getLastUploadedMeta() {
return _lastUploadedMeta; return _lastUploadedMeta;
} }
export function isUploading() {
return _uploading;
}
export function wasLastUploadCancelled() {
return _lastUploadCancelled;
}
export function cancelUpload() {
_lastUploadCancelled = true;
if (_uploadAbortCtrl && !_uploadAbortCtrl.signal.aborted) {
try { _uploadAbortCtrl.abort(); } catch (_) {}
}
}
var escapeHtml = uiModule.esc; var escapeHtml = uiModule.esc;
const fileHandlerModule = { const fileHandlerModule = {
@@ -290,6 +330,9 @@ const fileHandlerModule = {
getPendingRaw, getPendingRaw,
clearPending, clearPending,
getLastUploadedMeta, getLastUploadedMeta,
isUploading,
wasLastUploadCancelled,
cancelUpload,
}; };
export default fileHandlerModule; export default fileHandlerModule;