diff --git a/backend/src/handlers/fileHandler.ts b/backend/src/handlers/fileHandler.ts index 204e901..b6a1261 100644 --- a/backend/src/handlers/fileHandler.ts +++ b/backend/src/handlers/fileHandler.ts @@ -440,7 +440,13 @@ const chatWithFileStream = async (req: AuthenticatedRequest, res: Response): Pro return; } if (fileRecord.extractionStatus !== 'done') { - res.status(409).json({ message: 'Not ready for chat yet.' }); + const statusMsg = fileRecord.extractionStatus === 'failed' + ? 'Extraction failed for this file. Re-run extraction from the file menu.' + : 'This file is still being processed. It usually takes 30-60 seconds — try again in a moment.'; + res.status(409).json({ + message: statusMsg, + extraction_status: fileRecord.extractionStatus, + }); return; } diff --git a/backend/src/services/searchPipeline.ts b/backend/src/services/searchPipeline.ts index 4e8f9b4..1cea4cf 100644 --- a/backend/src/services/searchPipeline.ts +++ b/backend/src/services/searchPipeline.ts @@ -478,11 +478,19 @@ export const runSearchPipeline = async ( }; }); - results.sort((a, b) => b.score - a.score); - logger.info(`search: ${results.length} results for "${rawQuery}" (top score=${results[0]?.score.toFixed(4)})`); + // Drop nameless results. These come from file_ids that the parent fetch + // couldn't find a matching summary row for (race condition between delete + // and re-extract, or orphaned chunk rows). The UI can't render them and + // they'd crash on .split() / .localeCompare(). + const cleaned = results.filter((r) => typeof r.filename === "string" && r.filename.length > 0); + cleaned.sort((a, b) => b.score - a.score); + if (cleaned.length < results.length) { + logger.warn(`search: dropped ${results.length - cleaned.length} orphan result(s) without filename`); + } + logger.info(`search: ${cleaned.length} results for "${rawQuery}" (top score=${cleaned[0]?.score.toFixed(4)})`); // Cap to a reasonable top-K — UI doesn't need 200 results. - return results.slice(0, 30); + return cleaned.slice(0, 30); }; // ---------- 7. Optional LLM rerank for the top results ---------- diff --git a/smartdrive-frontend/src/components/FileChat.tsx b/smartdrive-frontend/src/components/FileChat.tsx index 17b24b7..9020c8c 100644 --- a/smartdrive-frontend/src/components/FileChat.tsx +++ b/smartdrive-frontend/src/components/FileChat.tsx @@ -349,9 +349,34 @@ export function FileChat({ fileId, fileName, ready }: Props) { {error && ( -
- {error} -
+ (() => { + const isNotReady = /still being processed|not ready/i.test(error); + const isFailed = /extraction failed/i.test(error); + const isPrivate = /private/i.test(error); + const tone = isNotReady + ? "border-amber-300/60 bg-amber-50/70 text-amber-900 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-100" + : isPrivate + ? "border-emerald-300/60 bg-emerald-50/70 text-emerald-900 dark:border-emerald-500/30 dark:bg-emerald-500/10 dark:text-emerald-100" + : "border-red-300/60 bg-red-50/70 text-red-900 dark:border-red-500/30 dark:bg-red-500/10 dark:text-red-100"; + return ( +
+ +
+
{error}
+ {isNotReady && ( +
+ The file is being indexed in the background. This usually takes 30-60 seconds. +
+ )} + {isFailed && ( +
+ Open the file menu and click “Re-run extraction” to try again. +
+ )} +
+
+ ); + })() )}
diff --git a/smartdrive-frontend/src/components/FileListWithDrawer.tsx b/smartdrive-frontend/src/components/FileListWithDrawer.tsx index b350c8c..5c9a2f2 100644 --- a/smartdrive-frontend/src/components/FileListWithDrawer.tsx +++ b/smartdrive-frontend/src/components/FileListWithDrawer.tsx @@ -492,7 +492,7 @@ function FileCard({
{(() => { - const ext = file.filename.split(".").pop()?.toUpperCase(); + const ext = (file.filename ?? "").split(".").pop()?.toUpperCase(); if (!ext || ext.length > 5) return null; return (
@@ -697,8 +697,8 @@ function sortFiles(arr: UploadItem[], kind: SortKind): UploadItem[] { switch (kind) { case "newest": return copy.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); case "oldest": return copy.sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime()); - case "name": return copy.sort((a, b) => a.filename.localeCompare(b.filename)); - case "type": return copy.sort((a, b) => a.filetype.localeCompare(b.filetype)); + case "name": return copy.sort((a, b) => (a.filename ?? "").localeCompare(b.filename ?? "")); + case "type": return copy.sort((a, b) => (a.filetype ?? "").localeCompare(b.filetype ?? "")); case "status": { const order: Record = { failed: 0, processing: 1, pending: 2, done: 3 }; return copy.sort((a, b) => (order[a.extraction_status ?? "done"] ?? 4) - (order[b.extraction_status ?? "done"] ?? 4)); @@ -805,7 +805,7 @@ export function FileListWithDrawer({ const visibleFiles = useMemo(() => { const filtered = files.filter((f) => matchesFilter(f, filter)); const searched = search.trim() - ? filtered.filter((f) => f.filename.toLowerCase().includes(search.toLowerCase().trim())) + ? filtered.filter((f) => (f.filename ?? "").toLowerCase().includes(search.toLowerCase().trim())) : filtered; return sortFiles(searched, sort); }, [files, filter, sort, search]);