fix: await processing end-to-end so replace status check is reliable; settle Firefox picker on cancel

- process_uploaded_file now awaits _process_handler (previously the
  coroutine was created and discarded), and the inner process_file
  calls inside _process_handler are awaited too. Without these awaits,
  uploads with process=True / process_in_background=False returned
  before processing ever ran, so data.status stayed 'pending' forever
  — which is exactly why upload_and_replace_file's Step 2 check
  (status == 'completed') was unreliable for valid files. Fix the
  upstream root cause instead of polling for status downstream.
- Firefox directory-picker fallback in collectDirectoryFiles now
  always settles its Promise: handlers are consolidated through a
  single finish() helper, the modern 'cancel' event is wired up, and
  a window-focus + 500ms sentinel resolves with empty files when the
  picker dismisses without any event. Without this, cancelling the
  picker in Firefox left the caller stuck on "Scanning directory..."
  with no completion path.
This commit is contained in:
DrMelone 2026-04-13 00:56:10 +02:00
parent a59beeaaf9
commit 1119bf045a
2 changed files with 53 additions and 20 deletions

View file

@ -115,7 +115,7 @@ async def process_uploaded_file(
file_path_processed = Storage.get_file(file_path)
result = transcribe(request, file_path_processed, file_metadata, user)
process_file(
await process_file(
request,
ProcessFileForm(file_id=file_item.id, content=result.get('text', '')),
user=user,
@ -124,7 +124,7 @@ async def process_uploaded_file(
elif (not content_type.startswith(('image/', 'video/'))) or (
request.app.state.config.CONTENT_EXTRACTION_ENGINE == 'external'
):
process_file(
await process_file(
request,
ProcessFileForm(file_id=file_item.id),
user=user,
@ -134,7 +134,7 @@ async def process_uploaded_file(
raise Exception(f'File type {content_type} is not supported for processing')
else:
log.info(f'File type {file.content_type} is not provided, but trying to process anyway')
process_file(
await process_file(
request,
ProcessFileForm(file_id=file_item.id),
user=user,
@ -153,10 +153,10 @@ async def process_uploaded_file(
)
if db:
_process_handler(db)
await _process_handler(db)
else:
with SessionLocal() as db_session:
_process_handler(db_session)
await _process_handler(db_session)
@router.post('/', response_model=FileModelResponse)

View file

@ -469,45 +469,78 @@
input.directory = true;
input.multiple = true;
input.style.display = 'none';
document.body.appendChild(input);
// Cancelling the native file picker does not always fire
// 'change' or 'error' — especially in Firefox, where the
// picker can dismiss without any event, leaving the caller
// stuck on "Scanning directory..." forever. Use 'cancel'
// (modern browsers) plus a window-focus + delayed sentinel
// fallback so the Promise ALWAYS settles: either we got
// files, or we resolve with an empty list and the caller's
// existing empty-check toasts.
let settled = false;
const finish = (err?: unknown) => {
if (settled) return;
settled = true;
if (input.parentNode) {
input.parentNode.removeChild(input);
}
window.removeEventListener('focus', onFocus);
if (err) {
reject(err);
} else {
resolve();
}
};
const onFocus = () => {
// 'change' fires after 'focus' returns to the window, so
// wait briefly before deciding the user cancelled.
setTimeout(() => finish(), 500);
};
input.onchange = async () => {
try {
const inputFiles = Array.from(input.files || []).filter(
(file) => !hasHiddenFolder(file.webkitRelativePath) && !file.name.startsWith('.')
);
for (const file of inputFiles) {
const relativePath = file.webkitRelativePath || file.name;
const fileWithPath = new File([file], relativePath, { type: file.type });
const collectedFile: CollectedFile = {
file: fileWithPath,
path: relativePath,
size: file.size
};
if (withHashes) {
collectedFile.hash = await calculateFileHash(file);
}
files.push(collectedFile);
}
document.body.removeChild(input);
resolve();
finish();
} catch (error) {
document.body.removeChild(input);
reject(error);
finish(error);
}
};
input.onerror = (error) => {
document.body.removeChild(input);
reject(error);
finish(error);
};
// Newer browsers fire 'cancel' when the picker is dismissed
// without a selection; cast because older lib.dom typings may
// not yet declare it.
(input as any).oncancel = () => {
finish();
};
window.addEventListener('focus', onFocus, { once: true });
input.click();
});
}