mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-10 22:32:16 +02:00
feat(rename): complete searchSpace to workspace transition across frontend and backend
- Introduced a comprehensive specification for renaming `searchSpace` to `workspace` in `surfsense_web` and `surfsense_desktop`, ensuring all TypeScript identifiers, React props, and local data structures are updated. - Implemented migration shims for persisted local state to prevent data loss during the transition. - Updated observability metrics and IPC channels to reflect the new naming convention. - Removed legacy `active-search-space` module and replaced it with `active-workspace` to maintain consistency. - Ensured no behavioral changes or data loss for users during the renaming process.
This commit is contained in:
parent
2a020629c5
commit
a8c1fb660d
259 changed files with 5480 additions and 2285 deletions
|
|
@ -13,21 +13,20 @@ interface ActivateChatThreadInput {
|
|||
id: number | null;
|
||||
title?: string;
|
||||
url?: string;
|
||||
searchSpaceId: number | string;
|
||||
workspaceId: number | string;
|
||||
visibility?: ChatVisibility;
|
||||
hasComments?: boolean;
|
||||
}
|
||||
|
||||
function getSearchSpaceId(searchSpaceId: number | string): number {
|
||||
const parsed =
|
||||
typeof searchSpaceId === "number" ? searchSpaceId : Number.parseInt(searchSpaceId, 10);
|
||||
function getWorkspaceId(workspaceId: number | string): number {
|
||||
const parsed = typeof workspaceId === "number" ? workspaceId : Number.parseInt(workspaceId, 10);
|
||||
return Number.isNaN(parsed) ? 0 : parsed;
|
||||
}
|
||||
|
||||
function getChatUrl(searchSpaceId: number | string, threadId: number | null): string {
|
||||
function getChatUrl(workspaceId: number | string, threadId: number | null): string {
|
||||
return threadId
|
||||
? `/dashboard/${searchSpaceId}/new-chat/${threadId}`
|
||||
: `/dashboard/${searchSpaceId}/new-chat`;
|
||||
? `/dashboard/${workspaceId}/new-chat/${threadId}`
|
||||
: `/dashboard/${workspaceId}/new-chat`;
|
||||
}
|
||||
|
||||
export function useActivateChatThread() {
|
||||
|
|
@ -46,22 +45,22 @@ export function useActivateChatThread() {
|
|||
);
|
||||
|
||||
const activateChatThread = useCallback(
|
||||
({ id, title, url, searchSpaceId, visibility, hasComments }: ActivateChatThreadInput) => {
|
||||
const numericSearchSpaceId = getSearchSpaceId(searchSpaceId);
|
||||
const chatUrl = url ?? getChatUrl(searchSpaceId, id);
|
||||
({ id, title, url, workspaceId, visibility, hasComments }: ActivateChatThreadInput) => {
|
||||
const numericWorkspaceId = getWorkspaceId(workspaceId);
|
||||
const chatUrl = url ?? getChatUrl(workspaceId, id);
|
||||
|
||||
syncChatTab({
|
||||
chatId: id,
|
||||
title: id ? title : (title ?? "New Chat"),
|
||||
chatUrl,
|
||||
searchSpaceId: numericSearchSpaceId,
|
||||
workspaceId: numericWorkspaceId,
|
||||
...(visibility !== undefined ? { visibility } : {}),
|
||||
...(hasComments !== undefined ? { hasComments } : {}),
|
||||
});
|
||||
|
||||
setCurrentThreadMetadata({
|
||||
id,
|
||||
searchSpaceId: numericSearchSpaceId,
|
||||
workspaceId: numericWorkspaceId,
|
||||
...(visibility !== undefined ? { visibility } : {}),
|
||||
...(hasComments !== undefined ? { hasComments } : {}),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -80,12 +80,12 @@ export interface ActionLogSseEvent {
|
|||
export function applyActionLogSse(
|
||||
queryClient: QueryClient,
|
||||
threadId: number,
|
||||
searchSpaceId: number,
|
||||
workspaceId: number,
|
||||
event: ActionLogSseEvent
|
||||
): void {
|
||||
dbg("applyActionLogSse: incoming SSE event", {
|
||||
threadId,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
event,
|
||||
});
|
||||
queryClient.setQueryData<AgentActionListResponse>(agentActionsQueryKey(threadId), (prev) => {
|
||||
|
|
@ -93,7 +93,7 @@ export function applyActionLogSse(
|
|||
id: event.id,
|
||||
thread_id: threadId,
|
||||
user_id: null,
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
tool_name: event.tool_name,
|
||||
args: null,
|
||||
result_id: null,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export interface EligibleModelOption {
|
|||
|
||||
export interface EligibleModelKind {
|
||||
options: EligibleModelOption[];
|
||||
/** Default selection: the search-space pref when eligible, else first option. */
|
||||
/** Default selection: the workspace pref when eligible, else first option. */
|
||||
defaultId: number | null;
|
||||
/** O(1) id → option lookup for trigger labels (avoids per-render `.find()`). */
|
||||
byId: Map<number, EligibleModelOption>;
|
||||
|
|
@ -39,7 +39,7 @@ export interface AutomationEligibleModels {
|
|||
|
||||
/**
|
||||
* Build the eligible option list for one model kind: premium globals
|
||||
* followed by all BYOK/search-space models.
|
||||
* followed by all BYOK/workspace models.
|
||||
*/
|
||||
function buildKind(
|
||||
globals: ConnectionRead[] | undefined,
|
||||
|
|
@ -93,7 +93,7 @@ function buildKind(
|
|||
/**
|
||||
* Lists the LLM / image / vision models that are eligible for automations
|
||||
* (premium globals + user BYOK — never free globals or Auto mode), with a
|
||||
* default selection seeded from the search space's role preferences.
|
||||
* default selection seeded from the workspace's role preferences.
|
||||
*
|
||||
* Everything is derived during render from the connection/model query atoms;
|
||||
* there are no effects, so option lists/maps keep stable references.
|
||||
|
|
|
|||
|
|
@ -5,21 +5,21 @@ import { automationsApiService } from "@/lib/apis/automations-api.service";
|
|||
import { cacheKeys } from "@/lib/query-client/cache-keys";
|
||||
|
||||
/**
|
||||
* Whether the search space's configured models are billable for automations.
|
||||
* Whether the workspace's configured models are billable for automations.
|
||||
*
|
||||
* Automations may only run on premium global models or user-provided (BYOK)
|
||||
* models; free global models and Auto mode are blocked so every run is metered
|
||||
* in premium credits. Creation surfaces use this to gate their CTAs before the
|
||||
* user invests effort drafting an automation that can't be saved.
|
||||
*
|
||||
* Keyed by search space id (not the jotai "current scope" atom) so it can be
|
||||
* Keyed by workspace id (not the jotai "current scope" atom) so it can be
|
||||
* used on the create route as well as the list page.
|
||||
*/
|
||||
export function useAutomationModelEligibility(searchSpaceId: number | undefined) {
|
||||
export function useAutomationModelEligibility(workspaceId: number | undefined) {
|
||||
return useQuery<ModelEligibility, Error>({
|
||||
queryKey: cacheKeys.automations.modelEligibility(searchSpaceId ?? 0),
|
||||
queryFn: () => automationsApiService.getModelEligibility(searchSpaceId as number),
|
||||
enabled: !!searchSpaceId,
|
||||
queryKey: cacheKeys.automations.modelEligibility(workspaceId ?? 0),
|
||||
queryFn: () => automationsApiService.getModelEligibility(workspaceId as number),
|
||||
enabled: !!workspaceId,
|
||||
staleTime: 60_000,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { useAtomValue } from "jotai";
|
|||
import { automationsListAtom } from "@/atoms/automations/automations-query.atoms";
|
||||
|
||||
/**
|
||||
* List automations in the active search space (first page).
|
||||
* List automations in the active workspace (first page).
|
||||
* Pagination knobs live in detail/list hooks below; v1 surfaces only the
|
||||
* first page since automation counts are expected to be small.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -6,16 +6,16 @@ import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
|||
import { queries } from "@/zero/queries";
|
||||
|
||||
/**
|
||||
* Syncs connectors for a search space via Zero.
|
||||
* Syncs connectors for a workspace via Zero.
|
||||
* Returns connectors, loading state, error, and a refresh function.
|
||||
*/
|
||||
export function useConnectorsSync(searchSpaceId: number | string | null) {
|
||||
const spaceId = searchSpaceId ? Number(searchSpaceId) : -1;
|
||||
export function useConnectorsSync(workspaceId: number | string | null) {
|
||||
const spaceId = workspaceId ? Number(workspaceId) : -1;
|
||||
|
||||
const [data, result] = useQuery(queries.connectors.bySpace({ searchSpaceId: spaceId }));
|
||||
const [data, result] = useQuery(queries.connectors.bySpace({ workspaceId: spaceId }));
|
||||
|
||||
const connectors: SearchSourceConnector[] = useMemo(() => {
|
||||
if (!searchSpaceId || !data) return [];
|
||||
if (!workspaceId || !data) return [];
|
||||
return data.map((c) => ({
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
|
|
@ -27,14 +27,14 @@ export function useConnectorsSync(searchSpaceId: number | string | null) {
|
|||
periodic_indexing_enabled: c.periodicIndexingEnabled,
|
||||
indexing_frequency_minutes: c.indexingFrequencyMinutes ?? null,
|
||||
next_scheduled_at: c.nextScheduledAt ? new Date(c.nextScheduledAt).toISOString() : null,
|
||||
workspace_id: c.searchSpaceId,
|
||||
workspace_id: c.workspaceId,
|
||||
user_id: c.userId,
|
||||
created_at: c.createdAt ? new Date(c.createdAt).toISOString() : new Date().toISOString(),
|
||||
}));
|
||||
}, [searchSpaceId, data]);
|
||||
}, [workspaceId, data]);
|
||||
|
||||
const loading = !searchSpaceId ? false : result.type !== "complete";
|
||||
const error = !searchSpaceId ? null : null;
|
||||
const loading = !workspaceId ? false : result.type !== "complete";
|
||||
const error = !workspaceId ? null : null;
|
||||
|
||||
const refreshConnectors = async () => {};
|
||||
|
||||
|
|
|
|||
|
|
@ -15,13 +15,13 @@ const SEARCH_SCROLL_SIZE = 5;
|
|||
* pagination via skip/page_size, and staleness detection
|
||||
* so fast typing never renders stale results.
|
||||
*
|
||||
* @param searchSpaceId - The search space to search within
|
||||
* @param workspaceId - The workspace to search within
|
||||
* @param query - The debounced search query
|
||||
* @param activeTypes - Document types to filter by
|
||||
* @param enabled - When false the hook resets and stops fetching
|
||||
*/
|
||||
export function useDocumentSearch(
|
||||
searchSpaceId: number,
|
||||
workspaceId: number,
|
||||
query: string,
|
||||
activeTypes: DocumentTypeEnum[],
|
||||
enabled: boolean
|
||||
|
|
@ -40,7 +40,7 @@ export function useDocumentSearch(
|
|||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: activeTypesKey serializes activeTypes
|
||||
useEffect(() => {
|
||||
if (!isActive || !searchSpaceId) {
|
||||
if (!isActive || !workspaceId) {
|
||||
setDocuments([]);
|
||||
setHasMore(false);
|
||||
setError(false);
|
||||
|
|
@ -56,7 +56,7 @@ export function useDocumentSearch(
|
|||
documentsApiService
|
||||
.searchDocuments({
|
||||
queryParams: {
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
page: 0,
|
||||
page_size: SEARCH_INITIAL_SIZE,
|
||||
title: query.trim(),
|
||||
|
|
@ -81,7 +81,7 @@ export function useDocumentSearch(
|
|||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [query, searchSpaceId, isActive, activeTypesKey]);
|
||||
}, [query, workspaceId, isActive, activeTypesKey]);
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: activeTypesKey serializes activeTypes
|
||||
const loadMore = useCallback(async () => {
|
||||
|
|
@ -91,7 +91,7 @@ export function useDocumentSearch(
|
|||
try {
|
||||
const response = await documentsApiService.searchDocuments({
|
||||
queryParams: {
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
skip: apiLoadedRef.current,
|
||||
page_size: SEARCH_SCROLL_SIZE,
|
||||
title: query.trim(),
|
||||
|
|
@ -108,7 +108,7 @@ export function useDocumentSearch(
|
|||
} finally {
|
||||
setLoadingMore(false);
|
||||
}
|
||||
}, [loadingMore, isActive, hasMore, searchSpaceId, query, activeTypesKey]);
|
||||
}, [loadingMore, isActive, hasMore, workspaceId, query, activeTypesKey]);
|
||||
|
||||
const removeItems = useCallback((ids: number[]) => {
|
||||
const idSet = new Set(ids);
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ interface UseDocumentsProcessingOptions {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the processing status of documents in the search space:
|
||||
* Returns the processing status of documents in the workspace:
|
||||
* - "processing" — docs are queued or actively being prepared for search
|
||||
* - "background_sync" — existing docs are being refreshed in the background
|
||||
* - "error" — nothing processing, but failed docs exist (show red icon)
|
||||
|
|
@ -26,17 +26,17 @@ interface UseDocumentsProcessingOptions {
|
|||
* - "idle" — nothing noteworthy (show normal icon)
|
||||
*/
|
||||
export function useDocumentsProcessing(
|
||||
searchSpaceId: number | null,
|
||||
workspaceId: number | null,
|
||||
{ hasPeriodicSyncEnabled = false }: UseDocumentsProcessingOptions = {}
|
||||
): DocumentsProcessingStatus {
|
||||
const [status, setStatus] = useState<DocumentsProcessingStatus>("idle");
|
||||
const wasProcessingRef = useRef(false);
|
||||
const successTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const [documents] = useQuery(queries.documents.bySpace({ searchSpaceId: searchSpaceId ?? -1 }));
|
||||
const [documents] = useQuery(queries.documents.bySpace({ workspaceId: workspaceId ?? -1 }));
|
||||
|
||||
useEffect(() => {
|
||||
if (!searchSpaceId || !documents) return;
|
||||
if (!workspaceId || !documents) return;
|
||||
|
||||
const clearSuccessTimer = () => {
|
||||
if (successTimerRef.current) {
|
||||
|
|
@ -95,7 +95,7 @@ export function useDocumentsProcessing(
|
|||
} else {
|
||||
setStatus("idle");
|
||||
}
|
||||
}, [searchSpaceId, documents, hasPeriodicSyncEnabled]);
|
||||
}, [workspaceId, documents, hasPeriodicSyncEnabled]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ const SCROLL_PAGE_SIZE = 5;
|
|||
* 4. Server-side sorting via sort_by + sort_order params
|
||||
*/
|
||||
export function useDocuments(
|
||||
searchSpaceId: number | null,
|
||||
workspaceId: number | null,
|
||||
typeFilter: DocumentTypeEnum[] = EMPTY_TYPE_FILTER,
|
||||
sortBy: DocumentSortBy = "created_at",
|
||||
sortOrder: SortOrder = "desc"
|
||||
|
|
@ -116,7 +116,7 @@ export function useDocuments(
|
|||
// EFFECT 1: Fetch first page + type counts when params change
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: typeFilterKey serializes typeFilter
|
||||
useEffect(() => {
|
||||
if (!searchSpaceId) return;
|
||||
if (!workspaceId) return;
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
|
|
@ -142,7 +142,7 @@ export function useDocuments(
|
|||
const [docsResponse, countsResponse] = await Promise.all([
|
||||
documentsApiService.getDocuments({
|
||||
queryParams: {
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
page: 0,
|
||||
page_size: INITIAL_PAGE_SIZE,
|
||||
...(typeFilter.length > 0 && { document_types: typeFilter }),
|
||||
|
|
@ -151,7 +151,7 @@ export function useDocuments(
|
|||
},
|
||||
}),
|
||||
documentsApiService.getDocumentTypeCounts({
|
||||
queryParams: { workspace_id: searchSpaceId },
|
||||
queryParams: { workspace_id: workspaceId },
|
||||
}),
|
||||
]);
|
||||
|
||||
|
|
@ -179,15 +179,13 @@ export function useDocuments(
|
|||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [searchSpaceId, typeFilterKey, sortBy, sortOrder, populateUserCache, apiToDisplayDoc]);
|
||||
}, [workspaceId, typeFilterKey, sortBy, sortOrder, populateUserCache, apiToDisplayDoc]);
|
||||
|
||||
// EFFECT 2: Zero real-time sync for document updates
|
||||
const [zeroDocuments] = useQuery(
|
||||
queries.documents.bySpace({ searchSpaceId: searchSpaceId ?? -1 })
|
||||
);
|
||||
const [zeroDocuments] = useQuery(queries.documents.bySpace({ workspaceId: workspaceId ?? -1 }));
|
||||
|
||||
useEffect(() => {
|
||||
if (!searchSpaceId || !zeroDocuments || !initialLoadDoneRef.current) return;
|
||||
if (!workspaceId || !zeroDocuments || !initialLoadDoneRef.current) return;
|
||||
|
||||
const validItems = zeroDocuments.filter(
|
||||
(doc) => doc.id != null && doc.title != null && doc.title !== ""
|
||||
|
|
@ -201,7 +199,7 @@ export function useDocuments(
|
|||
documentsApiService
|
||||
.getDocuments({
|
||||
queryParams: {
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
page: 0,
|
||||
page_size: 20,
|
||||
},
|
||||
|
|
@ -232,7 +230,7 @@ export function useDocuments(
|
|||
.filter((d) => !prevIds.has(d.id))
|
||||
.map((doc) => ({
|
||||
id: doc.id,
|
||||
workspace_id: doc.searchSpaceId,
|
||||
workspace_id: doc.workspaceId,
|
||||
document_type: doc.documentType,
|
||||
title: doc.title,
|
||||
created_by_id: doc.createdById ?? null,
|
||||
|
|
@ -280,13 +278,13 @@ export function useDocuments(
|
|||
}
|
||||
setTypeCounts(counts);
|
||||
setTotal(validItems.length);
|
||||
}, [searchSpaceId, zeroDocuments, populateUserCache]);
|
||||
}, [workspaceId, zeroDocuments, populateUserCache]);
|
||||
|
||||
// EFFECT 3: Reset on search space change
|
||||
const prevSearchSpaceIdRef = useRef<number | null>(null);
|
||||
// EFFECT 3: Reset on workspace change
|
||||
const prevWorkspaceIdRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (prevSearchSpaceIdRef.current !== null && prevSearchSpaceIdRef.current !== searchSpaceId) {
|
||||
if (prevWorkspaceIdRef.current !== null && prevWorkspaceIdRef.current !== workspaceId) {
|
||||
setDocuments([]);
|
||||
setTypeCounts({});
|
||||
setTotal(0);
|
||||
|
|
@ -296,19 +294,19 @@ export function useDocuments(
|
|||
userCacheRef.current.clear();
|
||||
emailCacheRef.current.clear();
|
||||
}
|
||||
prevSearchSpaceIdRef.current = searchSpaceId;
|
||||
}, [searchSpaceId]);
|
||||
prevWorkspaceIdRef.current = workspaceId;
|
||||
}, [workspaceId]);
|
||||
|
||||
// Load more pages via API
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: typeFilterKey serializes typeFilter
|
||||
const loadMore = useCallback(async () => {
|
||||
if (loadingMore || !hasMore || !searchSpaceId) return;
|
||||
if (loadingMore || !hasMore || !workspaceId) return;
|
||||
|
||||
setLoadingMore(true);
|
||||
try {
|
||||
const response = await documentsApiService.getDocuments({
|
||||
queryParams: {
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
skip: apiLoadedCountRef.current,
|
||||
page_size: SCROLL_PAGE_SIZE,
|
||||
...(typeFilter.length > 0 && { document_types: typeFilter }),
|
||||
|
|
@ -336,7 +334,7 @@ export function useDocuments(
|
|||
}, [
|
||||
loadingMore,
|
||||
hasMore,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
typeFilterKey,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { documentsApiService } from "@/lib/apis/documents-api.service";
|
|||
interface FileChangedEvent {
|
||||
id: string;
|
||||
rootFolderId: number | null;
|
||||
searchSpaceId: number;
|
||||
workspaceId: number;
|
||||
folderPath: string;
|
||||
folderName: string;
|
||||
relativePath: string;
|
||||
|
|
@ -29,7 +29,7 @@ interface FileEntry {
|
|||
interface BatchItem {
|
||||
folderPath: string;
|
||||
folderName: string;
|
||||
searchSpaceId: number;
|
||||
workspaceId: number;
|
||||
rootFolderId: number | null;
|
||||
files: FileEntry[];
|
||||
ackIds: string[];
|
||||
|
|
@ -66,7 +66,7 @@ export function useFolderSync() {
|
|||
|
||||
await documentsApiService.folderUploadFiles(files, {
|
||||
folder_name: batch.folderName,
|
||||
workspace_id: batch.searchSpaceId,
|
||||
workspace_id: batch.workspaceId,
|
||||
relative_paths: addChangeFiles.map((f) => f.relativePath),
|
||||
root_folder_id: batch.rootFolderId,
|
||||
});
|
||||
|
|
@ -75,7 +75,7 @@ export function useFolderSync() {
|
|||
if (unlinkFiles.length > 0) {
|
||||
await documentsApiService.folderNotifyUnlinked({
|
||||
folder_name: batch.folderName,
|
||||
workspace_id: batch.searchSpaceId,
|
||||
workspace_id: batch.workspaceId,
|
||||
root_folder_id: batch.rootFolderId,
|
||||
relative_paths: unlinkFiles.map((f) => f.relativePath),
|
||||
});
|
||||
|
|
@ -128,7 +128,7 @@ export function useFolderSync() {
|
|||
pendingByFolder.current.set(folderKey, {
|
||||
folderPath: event.folderPath,
|
||||
folderName: event.folderName,
|
||||
searchSpaceId: event.searchSpaceId,
|
||||
workspaceId: event.workspaceId,
|
||||
rootFolderId: event.rootFolderId,
|
||||
files: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ function getSyncCutoffDate(): string {
|
|||
*/
|
||||
export function useInbox(
|
||||
userId: string | null,
|
||||
searchSpaceId: number | null,
|
||||
workspaceId: number | null,
|
||||
category: NotificationCategory,
|
||||
prefetchedUnread?: { total_unread: number; recent_unread: number } | null,
|
||||
prefetchedUnreadReady = true
|
||||
|
|
@ -63,7 +63,7 @@ export function useInbox(
|
|||
|
||||
// EFFECT 1: Fetch first page + unread count from API with category filter
|
||||
useEffect(() => {
|
||||
if (!userId || !searchSpaceId) return;
|
||||
if (!userId || !workspaceId) return;
|
||||
if (!prefetchedUnreadReady) return;
|
||||
|
||||
let cancelled = false;
|
||||
|
|
@ -79,7 +79,7 @@ export function useInbox(
|
|||
try {
|
||||
const notificationsPromise = notificationsApiService.getNotifications({
|
||||
queryParams: {
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
category,
|
||||
limit: INITIAL_PAGE_SIZE,
|
||||
},
|
||||
|
|
@ -87,7 +87,7 @@ export function useInbox(
|
|||
|
||||
const unreadPromise = prefetchedUnread
|
||||
? Promise.resolve(prefetchedUnread)
|
||||
: notificationsApiService.getUnreadCount(searchSpaceId, undefined, category);
|
||||
: notificationsApiService.getUnreadCount(workspaceId, undefined, category);
|
||||
|
||||
const [notificationsResponse, unreadResponse] = await Promise.all([
|
||||
notificationsPromise,
|
||||
|
|
@ -115,20 +115,20 @@ export function useInbox(
|
|||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [userId, searchSpaceId, category, prefetchedUnread, prefetchedUnreadReady]);
|
||||
}, [userId, workspaceId, category, prefetchedUnread, prefetchedUnreadReady]);
|
||||
|
||||
// EFFECT 2: Zero real-time sync for notification updates
|
||||
const [zeroNotifications] = useQuery(queries.notifications.byUser({ userId: userId ?? "" }));
|
||||
|
||||
useEffect(() => {
|
||||
if (!userId || !searchSpaceId || !zeroNotifications || !initialLoadDoneRef.current) return;
|
||||
if (!userId || !workspaceId || !zeroNotifications || !initialLoadDoneRef.current) return;
|
||||
|
||||
const cutoff = new Date(getSyncCutoffDate());
|
||||
|
||||
const validItems = zeroNotifications.filter((item) => {
|
||||
if (item.id == null) return false;
|
||||
if (!categoryTypes.includes(item.type)) return false;
|
||||
if (item.searchSpaceId !== null && item.searchSpaceId !== searchSpaceId) return false;
|
||||
if (item.workspaceId !== null && item.workspaceId !== workspaceId) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
|
|
@ -146,7 +146,7 @@ export function useInbox(
|
|||
({
|
||||
id: item.id,
|
||||
user_id: item.userId,
|
||||
workspace_id: item.searchSpaceId ?? undefined,
|
||||
workspace_id: item.workspaceId ?? undefined,
|
||||
type: item.type,
|
||||
title: item.title,
|
||||
message: item.message,
|
||||
|
|
@ -195,11 +195,11 @@ export function useInbox(
|
|||
const recentUnreadCount = recentItems.filter((item) => !item.read).length;
|
||||
setUnreadCount(olderUnreadOffsetRef.current + recentUnreadCount);
|
||||
}
|
||||
}, [userId, searchSpaceId, zeroNotifications, categoryTypes]);
|
||||
}, [userId, workspaceId, zeroNotifications, categoryTypes]);
|
||||
|
||||
// Load more pages via API (cursor-based using before_date)
|
||||
const loadMore = useCallback(async () => {
|
||||
if (loadingMore || !hasMore || !userId || !searchSpaceId) return;
|
||||
if (loadingMore || !hasMore || !userId || !workspaceId) return;
|
||||
|
||||
setLoadingMore(true);
|
||||
try {
|
||||
|
|
@ -208,7 +208,7 @@ export function useInbox(
|
|||
|
||||
const response = await notificationsApiService.getNotifications({
|
||||
queryParams: {
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
category,
|
||||
before_date: beforeDate,
|
||||
limit: SCROLL_PAGE_SIZE,
|
||||
|
|
@ -228,7 +228,7 @@ export function useInbox(
|
|||
} finally {
|
||||
setLoadingMore(false);
|
||||
}
|
||||
}, [loadingMore, hasMore, userId, searchSpaceId, inboxItems, category]);
|
||||
}, [loadingMore, hasMore, userId, workspaceId, inboxItems, category]);
|
||||
|
||||
// Mark single item as read with optimistic update
|
||||
const markAsRead = useCallback(
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export type {
|
|||
LogSummary,
|
||||
} from "@/contracts/types/log.types";
|
||||
|
||||
export function useLogs(searchSpaceId?: number, filters: LogFilters = {}) {
|
||||
export function useLogs(workspaceId?: number, filters: LogFilters = {}) {
|
||||
const filtersKey = JSON.stringify(filters);
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: stable serialized key used intentionally
|
||||
const memoizedFilters = useMemo(() => filters, [filtersKey]);
|
||||
|
|
@ -55,17 +55,17 @@ export function useLogs(searchSpaceId?: number, filters: LogFilters = {}) {
|
|||
refetch,
|
||||
} = useQuery({
|
||||
queryKey: cacheKeys.logs.withQueryParams({
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
...buildQueryParams(filters ?? {}),
|
||||
}),
|
||||
queryFn: () =>
|
||||
logsApiService.getLogs({
|
||||
queryParams: {
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
...buildQueryParams(filters ?? {}),
|
||||
},
|
||||
}),
|
||||
enabled: !!searchSpaceId,
|
||||
enabled: !!workspaceId,
|
||||
staleTime: 3 * 60 * 1000,
|
||||
});
|
||||
|
||||
|
|
@ -80,7 +80,7 @@ export function useLogs(searchSpaceId?: number, filters: LogFilters = {}) {
|
|||
// Separate hook for log summary with smart polling support for document processing indicator UI
|
||||
// Polling only happens when there are active tasks, otherwise it stops to save resources
|
||||
export function useLogsSummary(
|
||||
searchSpaceId: number,
|
||||
workspaceId: number,
|
||||
hours: number = 24,
|
||||
options: { refetchInterval?: number; enablePolling?: boolean } = {}
|
||||
) {
|
||||
|
|
@ -92,13 +92,13 @@ export function useLogsSummary(
|
|||
error,
|
||||
refetch,
|
||||
} = useQuery({
|
||||
queryKey: cacheKeys.logs.summary(searchSpaceId),
|
||||
queryKey: cacheKeys.logs.summary(workspaceId),
|
||||
queryFn: () =>
|
||||
logsApiService.getLogSummary({
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
hours: hours,
|
||||
}),
|
||||
enabled: !!searchSpaceId,
|
||||
enabled: !!workspaceId,
|
||||
staleTime: 3 * 60 * 1000,
|
||||
// Always refetch on mount to show fresh processing tasks when navigating to the page
|
||||
refetchOnMount: "always",
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export interface LivePodcast {
|
|||
specVersion: number;
|
||||
durationSeconds: number | null;
|
||||
error: string | null;
|
||||
searchSpaceId: number;
|
||||
workspaceId: number;
|
||||
threadId: number | null;
|
||||
}
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ export function usePodcastLive(podcastId: number | undefined): UsePodcastLiveRes
|
|||
specVersion: row.specVersion,
|
||||
durationSeconds: row.durationSeconds ?? null,
|
||||
error: row.error ?? null,
|
||||
searchSpaceId: row.searchSpaceId,
|
||||
workspaceId: row.workspaceId,
|
||||
threadId: row.threadId ?? null,
|
||||
};
|
||||
}, [podcastId, row]);
|
||||
|
|
|
|||
|
|
@ -26,9 +26,9 @@ export interface ConnectorSourceItem {
|
|||
/**
|
||||
* Hook to fetch search source connectors from the API
|
||||
* @param lazy - If true, connectors won't be fetched on mount
|
||||
* @param searchSpaceId - Optional search space ID to filter connectors
|
||||
* @param workspaceId - Optional workspace ID to filter connectors
|
||||
*/
|
||||
export const useSearchSourceConnectors = (lazy: boolean = false, searchSpaceId?: number) => {
|
||||
export const useSearchSourceConnectors = (lazy: boolean = false, workspaceId?: number) => {
|
||||
const [connectors, setConnectors] = useState<SearchSourceConnector[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(!lazy); // Don't show loading initially for lazy mode
|
||||
const [isLoaded, setIsLoaded] = useState(false); // Memoization flag
|
||||
|
|
@ -139,26 +139,26 @@ export const useSearchSourceConnectors = (lazy: boolean = false, searchSpaceId?:
|
|||
);
|
||||
|
||||
useEffect(() => {
|
||||
// Only auto-fetch if lazy is false AND searchSpaceId is provided
|
||||
// This prevents 400 errors when the hook is used without a searchSpaceId
|
||||
if (!lazy && searchSpaceId !== undefined) {
|
||||
fetchConnectors(searchSpaceId);
|
||||
// Only auto-fetch if lazy is false AND workspaceId is provided
|
||||
// This prevents 400 errors when the hook is used without a workspaceId
|
||||
if (!lazy && workspaceId !== undefined) {
|
||||
fetchConnectors(workspaceId);
|
||||
}
|
||||
}, [lazy, fetchConnectors, searchSpaceId]);
|
||||
}, [lazy, fetchConnectors, workspaceId]);
|
||||
|
||||
// Function to refresh the connectors list
|
||||
const refreshConnectors = useCallback(
|
||||
async (spaceId?: number) => {
|
||||
setIsLoaded(false); // Reset memoization flag to allow refetch
|
||||
await fetchConnectors(spaceId !== undefined ? spaceId : searchSpaceId);
|
||||
await fetchConnectors(spaceId !== undefined ? spaceId : workspaceId);
|
||||
},
|
||||
[fetchConnectors, searchSpaceId]
|
||||
[fetchConnectors, workspaceId]
|
||||
);
|
||||
|
||||
/**
|
||||
* Create a new search source connector
|
||||
* @param connectorData - The connector data (excluding workspace_id)
|
||||
* @param spaceId - The search space ID to associate the connector with
|
||||
* @param spaceId - The workspace ID to associate the connector with
|
||||
*/
|
||||
const createConnector = async (
|
||||
connectorData: Omit<SearchSourceConnector, "id" | "user_id" | "created_at" | "workspace_id">,
|
||||
|
|
@ -258,18 +258,18 @@ export const useSearchSourceConnectors = (lazy: boolean = false, searchSpaceId?:
|
|||
};
|
||||
|
||||
/**
|
||||
* Index content from a connector to a search space
|
||||
* Index content from a connector to a workspace
|
||||
*/
|
||||
const indexConnector = async (
|
||||
connectorId: number,
|
||||
searchSpaceId: string | number,
|
||||
workspaceId: string | number,
|
||||
startDate?: string,
|
||||
endDate?: string
|
||||
) => {
|
||||
try {
|
||||
const response = await authenticatedFetch(
|
||||
buildBackendUrl(`/api/v1/search-source-connectors/${connectorId}/index`, {
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
start_date: startDate,
|
||||
end_date: endDate,
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import {
|
|||
updateThreadVisibility,
|
||||
} from "@/lib/chat/thread-persistence";
|
||||
|
||||
type SearchSpaceKey = number | string;
|
||||
type WorkspaceKey = number | string;
|
||||
|
||||
interface VisibilityVariables {
|
||||
thread: ThreadRecord;
|
||||
|
|
@ -58,7 +58,7 @@ interface ArchiveRollback {
|
|||
archived: boolean;
|
||||
}
|
||||
|
||||
export function useUpdateThreadVisibility(searchSpaceId: SearchSpaceKey) {
|
||||
export function useUpdateThreadVisibility(workspaceId: WorkspaceKey) {
|
||||
const queryClient = useQueryClient();
|
||||
const currentThread = useAtomValue(currentThreadAtom);
|
||||
const patchCurrentThreadMetadata = useSetAtom(patchCurrentThreadMetadataAtom);
|
||||
|
|
@ -68,7 +68,7 @@ export function useUpdateThreadVisibility(searchSpaceId: SearchSpaceKey) {
|
|||
onMutate: ({ thread, visibility }) => {
|
||||
const previousVisibility = thread.visibility ?? "PRIVATE";
|
||||
|
||||
patchThreadEverywhere(queryClient, searchSpaceId, thread.id, { visibility });
|
||||
patchThreadEverywhere(queryClient, workspaceId, thread.id, { visibility });
|
||||
if (currentThread.id === thread.id) {
|
||||
patchCurrentThreadMetadata({ id: thread.id, visibility });
|
||||
}
|
||||
|
|
@ -77,7 +77,7 @@ export function useUpdateThreadVisibility(searchSpaceId: SearchSpaceKey) {
|
|||
},
|
||||
onError: (_error, _variables, rollback) => {
|
||||
if (!rollback) return;
|
||||
patchThreadEverywhere(queryClient, searchSpaceId, rollback.threadId, {
|
||||
patchThreadEverywhere(queryClient, workspaceId, rollback.threadId, {
|
||||
visibility: rollback.visibility,
|
||||
});
|
||||
if (currentThread.id === rollback.threadId) {
|
||||
|
|
@ -88,7 +88,7 @@ export function useUpdateThreadVisibility(searchSpaceId: SearchSpaceKey) {
|
|||
}
|
||||
},
|
||||
onSuccess: (thread) => {
|
||||
replaceThreadEverywhere(queryClient, searchSpaceId, thread);
|
||||
replaceThreadEverywhere(queryClient, workspaceId, thread);
|
||||
if (currentThread.id === thread.id) {
|
||||
patchCurrentThreadMetadata({
|
||||
id: thread.id,
|
||||
|
|
@ -100,48 +100,48 @@ export function useUpdateThreadVisibility(searchSpaceId: SearchSpaceKey) {
|
|||
});
|
||||
}
|
||||
|
||||
export function useRenameThread(searchSpaceId: SearchSpaceKey) {
|
||||
export function useRenameThread(workspaceId: WorkspaceKey) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<ThreadRecord, Error, RenameVariables, RenameRollback>({
|
||||
mutationFn: ({ threadId, title }) => updateThread(threadId, { title }),
|
||||
onMutate: ({ threadId, title, previousTitle }) => {
|
||||
patchThreadEverywhere(queryClient, searchSpaceId, threadId, { title });
|
||||
patchThreadEverywhere(queryClient, workspaceId, threadId, { title });
|
||||
return { threadId, title: previousTitle };
|
||||
},
|
||||
onError: (_error, _variables, rollback) => {
|
||||
if (!rollback || rollback.title === undefined) return;
|
||||
patchThreadEverywhere(queryClient, searchSpaceId, rollback.threadId, {
|
||||
patchThreadEverywhere(queryClient, workspaceId, rollback.threadId, {
|
||||
title: rollback.title,
|
||||
});
|
||||
},
|
||||
onSuccess: (thread) => {
|
||||
replaceThreadEverywhere(queryClient, searchSpaceId, thread);
|
||||
replaceThreadEverywhere(queryClient, workspaceId, thread);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useArchiveThread(searchSpaceId: SearchSpaceKey) {
|
||||
export function useArchiveThread(workspaceId: WorkspaceKey) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<ThreadRecord, Error, ArchiveVariables, ArchiveRollback>({
|
||||
mutationFn: ({ threadId, archived }) => updateThread(threadId, { archived }),
|
||||
onMutate: ({ threadId, archived }) => {
|
||||
moveThreadArchiveState(queryClient, searchSpaceId, threadId, archived);
|
||||
moveThreadArchiveState(queryClient, workspaceId, threadId, archived);
|
||||
return { threadId, archived: !archived };
|
||||
},
|
||||
onError: (_error, _variables, rollback) => {
|
||||
if (!rollback) return;
|
||||
moveThreadArchiveState(queryClient, searchSpaceId, rollback.threadId, rollback.archived);
|
||||
moveThreadArchiveState(queryClient, workspaceId, rollback.threadId, rollback.archived);
|
||||
},
|
||||
onSuccess: (thread) => {
|
||||
replaceThreadEverywhere(queryClient, searchSpaceId, thread);
|
||||
moveThreadArchiveState(queryClient, searchSpaceId, thread.id, thread.archived);
|
||||
replaceThreadEverywhere(queryClient, workspaceId, thread);
|
||||
moveThreadArchiveState(queryClient, workspaceId, thread.id, thread.archived);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteThread(searchSpaceId: SearchSpaceKey) {
|
||||
export function useDeleteThread(workspaceId: WorkspaceKey) {
|
||||
const queryClient = useQueryClient();
|
||||
const currentThread = useAtomValue(currentThreadAtom);
|
||||
const resetCurrentThread = useSetAtom(resetCurrentThreadAtom);
|
||||
|
|
@ -149,7 +149,7 @@ export function useDeleteThread(searchSpaceId: SearchSpaceKey) {
|
|||
return useMutation<void, Error, DeleteVariables>({
|
||||
mutationFn: ({ threadId }) => deleteThread(threadId),
|
||||
onSuccess: (_data, { threadId }) => {
|
||||
removeThreadEverywhere(queryClient, searchSpaceId, threadId);
|
||||
removeThreadEverywhere(queryClient, workspaceId, threadId);
|
||||
if (currentThread.id === threadId) {
|
||||
resetCurrentThread();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@ import { queries } from "@/zero/queries";
|
|||
* Updates instantly as documents are created, deleted, or change type.
|
||||
*/
|
||||
export function useZeroDocumentTypeCounts(
|
||||
searchSpaceId: number | string | null
|
||||
workspaceId: number | string | null
|
||||
): Record<string, number> | undefined {
|
||||
const numericId = searchSpaceId != null ? Number(searchSpaceId) : null;
|
||||
const numericId = workspaceId != null ? Number(workspaceId) : null;
|
||||
|
||||
const [zeroDocuments] = useQuery(queries.documents.bySpace({ searchSpaceId: numericId ?? -1 }));
|
||||
const [zeroDocuments] = useQuery(queries.documents.bySpace({ workspaceId: numericId ?? -1 }));
|
||||
|
||||
return useMemo(() => {
|
||||
if (!zeroDocuments || numericId == null) return undefined;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue