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]);