Merge remote-tracking branch 'upstream/dev' into feat/replace-logs

This commit is contained in:
Anish Sarkar 2026-01-15 03:07:20 +05:30
commit 2e0f742000
47 changed files with 2365 additions and 700 deletions

View file

@ -79,25 +79,17 @@ export function DocumentsTableShell({
[documents, sortKey, sortDesc]
);
// Filter out SURFSENSE_DOCS for selection purposes
const selectableDocs = React.useMemo(
() => sorted.filter((d) => d.document_type !== "SURFSENSE_DOCS"),
[sorted]
);
const allSelectedOnPage =
selectableDocs.length > 0 && selectableDocs.every((d) => selectedIds.has(d.id));
const someSelectedOnPage =
selectableDocs.some((d) => selectedIds.has(d.id)) && !allSelectedOnPage;
const allSelectedOnPage = sorted.length > 0 && sorted.every((d) => selectedIds.has(d.id));
const someSelectedOnPage = sorted.some((d) => selectedIds.has(d.id)) && !allSelectedOnPage;
const toggleAll = (checked: boolean) => {
const next = new Set(selectedIds);
if (checked)
selectableDocs.forEach((d) => {
sorted.forEach((d) => {
next.add(d.id);
});
else
selectableDocs.forEach((d) => {
sorted.forEach((d) => {
next.delete(d.id);
});
setSelectedIds(next);
@ -238,10 +230,9 @@ export function DocumentsTableShell({
const icon = getDocumentTypeIcon(doc.document_type);
const title = doc.title;
const truncatedTitle = title.length > 30 ? `${title.slice(0, 30)}...` : title;
const isSurfsenseDoc = doc.document_type === "SURFSENSE_DOCS";
return (
<motion.tr
key={`${doc.document_type}-${doc.id}`}
key={doc.id}
initial={{ opacity: 0, y: 10 }}
animate={{
opacity: 1,
@ -258,9 +249,8 @@ export function DocumentsTableShell({
>
<TableCell className="px-4 py-3">
<Checkbox
checked={selectedIds.has(doc.id) && !isSurfsenseDoc}
onCheckedChange={(v) => !isSurfsenseDoc && toggleOne(doc.id, !!v)}
disabled={isSurfsenseDoc}
checked={selectedIds.has(doc.id)}
onCheckedChange={(v) => toggleOne(doc.id, !!v)}
aria-label="Select row"
/>
</TableCell>

View file

@ -18,7 +18,7 @@ import { cacheKeys } from "@/lib/query-client/cache-keys";
import { DocumentsFilters } from "./components/DocumentsFilters";
import { DocumentsTableShell, type SortKey } from "./components/DocumentsTableShell";
import { PaginationControls } from "./components/PaginationControls";
import type { ColumnVisibility, Document } from "./components/types";
import type { ColumnVisibility } from "./components/types";
function useDebounced<T>(value: T, delay = 250) {
const [debounced, setDebounced] = useState(value);
@ -58,39 +58,30 @@ export default function DocumentsTable() {
const { data: rawTypeCounts } = useAtomValue(documentTypeCountsAtom);
const { mutateAsync: deleteDocumentMutation } = useAtomValue(deleteDocumentMutationAtom);
// Filter out SURFSENSE_DOCS from active types for regular documents API
const regularDocumentTypes = useMemo(
() => activeTypes.filter((t) => t !== "SURFSENSE_DOCS"),
[activeTypes]
);
// Check if only SURFSENSE_DOCS is selected (skip regular docs query)
const onlySurfsenseDocsSelected = activeTypes.length === 1 && activeTypes[0] === "SURFSENSE_DOCS";
// Build query parameters for fetching documents (excluding SURFSENSE_DOCS type)
// Build query parameters for fetching documents
const queryParams = useMemo(
() => ({
search_space_id: searchSpaceId,
page: pageIndex,
page_size: pageSize,
...(regularDocumentTypes.length > 0 && { document_types: regularDocumentTypes }),
...(activeTypes.length > 0 && { document_types: activeTypes }),
}),
[searchSpaceId, pageIndex, pageSize, regularDocumentTypes]
[searchSpaceId, pageIndex, pageSize, activeTypes]
);
// Build search query parameters (excluding SURFSENSE_DOCS type)
// Build search query parameters
const searchQueryParams = useMemo(
() => ({
search_space_id: searchSpaceId,
page: pageIndex,
page_size: pageSize,
title: debouncedSearch.trim(),
...(regularDocumentTypes.length > 0 && { document_types: regularDocumentTypes }),
...(activeTypes.length > 0 && { document_types: activeTypes }),
}),
[searchSpaceId, pageIndex, pageSize, regularDocumentTypes, debouncedSearch]
[searchSpaceId, pageIndex, pageSize, activeTypes, debouncedSearch]
);
// Use query for fetching documents (disabled when only SURFSENSE_DOCS is selected)
// Use query for fetching documents
const {
data: documentsResponse,
isLoading: isDocumentsLoading,
@ -100,10 +91,10 @@ export default function DocumentsTable() {
queryKey: cacheKeys.documents.globalQueryParams(queryParams),
queryFn: () => documentsApiService.getDocuments({ queryParams }),
staleTime: 3 * 60 * 1000, // 3 minutes
enabled: !!searchSpaceId && !debouncedSearch.trim() && !onlySurfsenseDocsSelected,
enabled: !!searchSpaceId && !debouncedSearch.trim(),
});
// Use query for searching documents (disabled when only SURFSENSE_DOCS is selected)
// Use query for searching documents
const {
data: searchResponse,
isLoading: isSearchLoading,
@ -113,7 +104,7 @@ export default function DocumentsTable() {
queryKey: cacheKeys.documents.globalQueryParams(searchQueryParams),
queryFn: () => documentsApiService.searchDocuments({ queryParams: searchQueryParams }),
staleTime: 3 * 60 * 1000, // 3 minutes
enabled: !!searchSpaceId && !!debouncedSearch.trim() && !onlySurfsenseDocsSelected,
enabled: !!searchSpaceId && !!debouncedSearch.trim(),
});
// Determine if we should show SurfSense docs (when no type filter or SURFSENSE_DOCS is selected)
@ -163,64 +154,16 @@ export default function DocumentsTable() {
}, [rawTypeCounts, surfsenseDocsResponse?.total]);
// Extract documents and total based on search state
const regularDocuments = debouncedSearch.trim()
const documents = debouncedSearch.trim()
? searchResponse?.items || []
: documentsResponse?.items || [];
const regularTotal = debouncedSearch.trim()
? searchResponse?.total || 0
: documentsResponse?.total || 0;
const total = debouncedSearch.trim() ? searchResponse?.total || 0 : documentsResponse?.total || 0;
// Merge regular documents with SurfSense docs
const documents = useMemo(() => {
// If filtering by type and not including SURFSENSE_DOCS, only show regular docs
if (activeTypes.length > 0 && !activeTypes.includes("SURFSENSE_DOCS" as DocumentTypeEnum)) {
return regularDocuments;
}
// If filtering only by SURFSENSE_DOCS, only show surfsense docs
if (activeTypes.length === 1 && activeTypes[0] === "SURFSENSE_DOCS") {
return surfsenseDocsAsDocuments;
}
// Otherwise, merge both (surfsense docs first)
return [...surfsenseDocsAsDocuments, ...regularDocuments];
}, [regularDocuments, surfsenseDocsAsDocuments, activeTypes]);
const loading = debouncedSearch.trim() ? isSearchLoading : isDocumentsLoading;
const error = debouncedSearch.trim() ? searchError : documentsError;
const total = useMemo(() => {
if (activeTypes.length > 0 && !activeTypes.includes("SURFSENSE_DOCS" as DocumentTypeEnum)) {
return regularTotal;
}
if (activeTypes.length === 1 && activeTypes[0] === "SURFSENSE_DOCS") {
return surfsenseDocsResponse?.total || 0;
}
return regularTotal + (surfsenseDocsResponse?.total || 0);
}, [regularTotal, surfsenseDocsResponse?.total, activeTypes]);
const loading = useMemo(() => {
// If only SURFSENSE_DOCS selected, only check surfsense loading
if (onlySurfsenseDocsSelected) {
return isSurfsenseDocsLoading;
}
// Otherwise check both regular docs and surfsense docs loading
const regularLoading = debouncedSearch.trim() ? isSearchLoading : isDocumentsLoading;
return regularLoading || (showSurfsenseDocs && isSurfsenseDocsLoading);
}, [
onlySurfsenseDocsSelected,
isSurfsenseDocsLoading,
debouncedSearch,
isSearchLoading,
isDocumentsLoading,
showSurfsenseDocs,
]);
const error = useMemo(() => {
// If only SURFSENSE_DOCS selected, no regular docs errors
if (onlySurfsenseDocsSelected) {
return null;
}
return debouncedSearch.trim() ? searchError : documentsError;
}, [onlySurfsenseDocsSelected, debouncedSearch, searchError, documentsError]);
// Display server-filtered results directly
const displayDocs = documents || [];
// Display results directly
const displayDocs = documents;
const displayTotal = total;
const pageStart = pageIndex * pageSize;
const pageEnd = Math.min(pageStart + pageSize, displayTotal);
@ -240,33 +183,16 @@ export default function DocumentsTable() {
if (isRefreshing) return;
setIsRefreshing(true);
try {
const refetchPromises: Promise<unknown>[] = [];
// Only refetch regular documents if not in "only surfsense docs" mode
if (!onlySurfsenseDocsSelected) {
if (debouncedSearch.trim()) {
refetchPromises.push(refetchSearch());
} else {
refetchPromises.push(refetchDocuments());
}
if (debouncedSearch.trim()) {
await refetchSearch();
} else {
await refetchDocuments();
}
if (showSurfsenseDocs) {
refetchPromises.push(refetchSurfsenseDocs());
}
await Promise.all(refetchPromises);
toast.success(t("refresh_success") || "Documents refreshed");
} finally {
setIsRefreshing(false);
}
}, [
debouncedSearch,
refetchSearch,
refetchDocuments,
refetchSurfsenseDocs,
showSurfsenseDocs,
onlySurfsenseDocsSelected,
t,
isRefreshing,
]);
}, [debouncedSearch, refetchSearch, refetchDocuments, t, isRefreshing]);
// Create a delete function for single document deletion
const deleteDocument = useCallback(
@ -357,7 +283,7 @@ export default function DocumentsTable() {
</motion.div>
<DocumentsFilters
typeCounts={typeCounts ?? {}}
typeCounts={rawTypeCounts ?? {}}
selectedIds={selectedIds}
onSearch={setSearch}
searchValue={search}

View file

@ -23,6 +23,7 @@ import {
// extractWriteTodosFromContent,
hydratePlanStateAtom,
} from "@/atoms/chat/plan-state.atom";
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
import { Thread } from "@/components/assistant-ui/thread";
import { ChatHeader } from "@/components/new-chat/chat-header";
import type { ThinkingStep } from "@/components/tool-ui/deepagent-thinking";
@ -185,12 +186,25 @@ function convertToThreadMessage(msg: MessageRecord): ThreadMessageLike {
}
}
// Build metadata.custom for author display in shared chats
const metadata = msg.author_id
? {
custom: {
author: {
displayName: msg.author_display_name ?? null,
avatarUrl: msg.author_avatar_url ?? null,
},
},
}
: undefined;
return {
id: `msg-${msg.id}`,
role: msg.role,
content,
createdAt: new Date(msg.created_at),
attachments,
metadata,
};
}
@ -238,6 +252,9 @@ export default function NewChatPage() {
const setMessageDocumentsMap = useSetAtom(messageDocumentsMapAtom);
const hydratePlanState = useSetAtom(hydratePlanStateAtom);
// Get current user for author info in shared chats
const { data: currentUser } = useAtomValue(currentUserAtom);
// Create the attachment adapter for file processing
const attachmentAdapter = useMemo(() => createAttachmentAdapter(), []);
@ -306,12 +323,6 @@ export default function NewChatPage() {
if (steps.length > 0) {
restoredThinkingSteps.set(`msg-${msg.id}`, steps);
}
// Hydrate write_todos plan state from persisted tool calls
// Disabled for now
// const writeTodosCalls = extractWriteTodosFromContent(msg.content);
// for (const todoData of writeTodosCalls) {
// hydratePlanState(todoData);
// }
}
if (msg.role === "user") {
const docs = extractMentionedDocuments(msg.content);
@ -448,13 +459,27 @@ export default function NewChatPage() {
// Add user message to state
const userMsgId = `msg-user-${Date.now()}`;
// Include author metadata for shared chats
const authorMetadata =
currentThread?.visibility === "SEARCH_SPACE" && currentUser
? {
custom: {
author: {
displayName: currentUser.display_name ?? null,
avatarUrl: currentUser.avatar_url ?? null,
},
},
}
: undefined;
const userMessage: ThreadMessageLike = {
id: userMsgId,
role: "user",
content: message.content,
createdAt: new Date(),
// Include attachments so they can be displayed
attachments: message.attachments || [],
metadata: authorMetadata,
};
setMessages((prev) => [...prev, userMessage]);
@ -884,6 +909,8 @@ export default function NewChatPage() {
setMentionedDocuments,
setMessageDocumentsMap,
queryClient,
currentThread,
currentUser,
]
);