mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-25 19:15:18 +02:00
Merge remote-tracking branch 'upstream/dev' into feat/replace-logs
This commit is contained in:
commit
2e0f742000
47 changed files with 2365 additions and 700 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue