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:
Anish Sarkar 2026-07-06 15:12:40 +05:30
parent 2a020629c5
commit a8c1fb660d
259 changed files with 5480 additions and 2285 deletions

View file

@ -27,7 +27,7 @@ export function getClientPlatform(): ClientPlatform {
}
export async function getAgentFilesystemSelection(
searchSpaceId?: number | null,
workspaceId?: number | null,
options?: AgentFilesystemSelectionOptions
): Promise<AgentFilesystemSelection> {
const platform = getClientPlatform();
@ -39,9 +39,9 @@ export async function getAgentFilesystemSelection(
return { ...DEFAULT_SELECTION, client_platform: platform };
}
try {
const settings = await window.electronAPI.getAgentFilesystemSettings(searchSpaceId);
const settings = await window.electronAPI.getAgentFilesystemSettings(workspaceId);
if (settings.mode === "desktop_local_folder") {
const mounts = await window.electronAPI.getAgentFilesystemMounts?.(searchSpaceId);
const mounts = await window.electronAPI.getAgentFilesystemMounts?.(workspaceId);
const localFilesystemMounts =
mounts?.map((entry) => ({
mount_id: entry.mount,

View file

@ -80,7 +80,7 @@ export const announcements: Announcement[] = [
// id: "2026-02-10-podcast-improvements",
// title: "Podcast Generation Improvements",
// description:
// "We've improved podcast generation with faster processing, better audio quality, and support for longer documents. Try it out in any search space.",
// "We've improved podcast generation with faster processing, better audio quality, and support for longer documents. Try it out in any workspace.",
// category: "update",
// date: "2026-02-10T00:00:00Z",
// startTime: "2026-02-10T00:00:00Z",
@ -120,7 +120,7 @@ export const announcements: Announcement[] = [
// id: "2026-01-28-team-collaboration",
// title: "Enhanced Team Collaboration",
// description:
// "Shared search spaces now support real-time mentions, comment threads, and role-based access control. Invite your team and collaborate more effectively.",
// "Shared workspaces now support real-time mentions, comment threads, and role-based access control. Invite your team and collaborate more effectively.",
// category: "feature",
// date: "2026-01-28T00:00:00Z",
// startTime: "2026-01-28T00:00:00Z",

View file

@ -42,15 +42,15 @@ const AgentPermissionRuleUpdateSchema = z.object({
export type AgentPermissionRuleUpdate = z.infer<typeof AgentPermissionRuleUpdateSchema>;
class AgentPermissionsApiService {
list = async (searchSpaceId: number): Promise<AgentPermissionRule[]> => {
list = async (workspaceId: number): Promise<AgentPermissionRule[]> => {
return baseApiService.get(
`/api/v1/workspaces/${searchSpaceId}/agent/permissions/rules`,
`/api/v1/workspaces/${workspaceId}/agent/permissions/rules`,
AgentPermissionRuleListSchema
);
};
create = async (
searchSpaceId: number,
workspaceId: number,
payload: AgentPermissionRuleCreate
): Promise<AgentPermissionRule> => {
const parsed = AgentPermissionRuleCreateSchema.safeParse(payload);
@ -58,14 +58,14 @@ class AgentPermissionsApiService {
throw new ValidationError(parsed.error.issues.map((i) => i.message).join(", "));
}
return baseApiService.post(
`/api/v1/workspaces/${searchSpaceId}/agent/permissions/rules`,
`/api/v1/workspaces/${workspaceId}/agent/permissions/rules`,
AgentPermissionRuleSchema,
{ body: parsed.data }
);
};
update = async (
searchSpaceId: number,
workspaceId: number,
ruleId: number,
payload: AgentPermissionRuleUpdate
): Promise<AgentPermissionRule> => {
@ -74,15 +74,15 @@ class AgentPermissionsApiService {
throw new ValidationError(parsed.error.issues.map((i) => i.message).join(", "));
}
return baseApiService.patch(
`/api/v1/workspaces/${searchSpaceId}/agent/permissions/rules/${ruleId}`,
`/api/v1/workspaces/${workspaceId}/agent/permissions/rules/${ruleId}`,
AgentPermissionRuleSchema,
{ body: parsed.data }
);
};
remove = async (searchSpaceId: number, ruleId: number): Promise<void> => {
remove = async (workspaceId: number, ruleId: number): Promise<void> => {
await baseApiService.delete(
`/api/v1/workspaces/${searchSpaceId}/agent/permissions/rules/${ruleId}`
`/api/v1/workspaces/${workspaceId}/agent/permissions/rules/${ruleId}`
);
};
}

View file

@ -68,8 +68,8 @@ class AutomationsApiService {
// Whether the workspace's models are billable for automations (premium
// global or BYOK). Used to gate creation surfaces before submit.
getModelEligibility = async (searchSpaceId: number) => {
const qs = new URLSearchParams({ workspace_id: String(searchSpaceId) });
getModelEligibility = async (workspaceId: number) => {
const qs = new URLSearchParams({ workspace_id: String(workspaceId) });
return baseApiService.get(`${BASE}/model-eligibility?${qs.toString()}`, modelEligibility);
};

View file

@ -75,7 +75,7 @@ class ChatThreadsApiService {
/**
* List all public chat snapshots for a workspace.
*/
listPublicChatSnapshotsForSearchSpace = async (
listPublicChatSnapshotsForWorkspace = async (
request: PublicChatSnapshotsBySpaceRequest
): Promise<PublicChatSnapshotsBySpaceResponse> => {
const parsed = publicChatSnapshotsBySpaceRequest.safeParse(request);

View file

@ -257,10 +257,7 @@ class DocumentsApiService {
const transformedQueryParams = Object.fromEntries(
Object.entries(parsedRequest.data.queryParams)
.filter(([, v]) => v !== undefined)
.map(([k, v]) => [
k,
String(v),
])
.map(([k, v]) => [k, String(v)])
);
const queryParams = new URLSearchParams(transformedQueryParams).toString();
@ -301,10 +298,7 @@ class DocumentsApiService {
// Transform query params to be string values
const transformedQueryParams = parsedRequest.data.queryParams
? Object.fromEntries(
Object.entries(parsedRequest.data.queryParams).map(([k, v]) => [
k,
String(v),
])
Object.entries(parsedRequest.data.queryParams).map(([k, v]) => [k, String(v)])
)
: undefined;
@ -491,9 +485,9 @@ class DocumentsApiService {
}) as unknown as { deleted_count: number };
};
getWatchedFolders = async (searchSpaceId: number) => {
getWatchedFolders = async (workspaceId: number) => {
return baseApiService.get(
`/api/v1/documents/watched-folders?workspace_id=${searchSpaceId}`,
`/api/v1/documents/watched-folders?workspace_id=${workspaceId}`,
folderListResponse
);
};

View file

@ -33,8 +33,8 @@ class FoldersApiService {
});
};
listFolders = async (searchSpaceId: number) => {
return baseApiService.get(`/api/v1/folders?workspace_id=${searchSpaceId}`, folderListResponse);
listFolders = async (workspaceId: number) => {
return baseApiService.get(`/api/v1/folders?workspace_id=${workspaceId}`, folderListResponse);
};
getFolder = async (folderId: number) => {

View file

@ -7,9 +7,9 @@ import { baseApiService } from "./base-api.service";
const BASE = "/api/v1/image-generations";
class ImageGenerationsApiService {
list = async (searchSpaceId: number, limit = 100) => {
list = async (workspaceId: number, limit = 100) => {
const qs = new URLSearchParams({
workspace_id: String(searchSpaceId),
workspace_id: String(workspaceId),
limit: String(limit),
}).toString();
return baseApiService.get(`${BASE}?${qs}`, imageGenerationList);

View file

@ -8,9 +8,9 @@ import {
getMembersResponse,
getMyAccessRequest,
getMyAccessResponse,
type LeaveSearchSpaceRequest,
leaveSearchSpaceRequest,
leaveSearchSpaceResponse,
type LeaveWorkspaceRequest,
leaveWorkspaceRequest,
leaveWorkspaceResponse,
type UpdateMembershipRequest,
updateMembershipRequest,
updateMembershipResponse,
@ -82,8 +82,8 @@ class MembersApiService {
/**
* Leave a workspace (remove self)
*/
leaveSearchSpace = async (request: LeaveSearchSpaceRequest) => {
const parsedRequest = leaveSearchSpaceRequest.safeParse(request);
leaveWorkspace = async (request: LeaveWorkspaceRequest) => {
const parsedRequest = leaveWorkspaceRequest.safeParse(request);
if (!parsedRequest.success) {
console.error("Invalid request:", parsedRequest.error);
@ -94,7 +94,7 @@ class MembersApiService {
return baseApiService.delete(
`/api/v1/workspaces/${parsedRequest.data.workspace_id}/members/me`,
leaveSearchSpaceResponse
leaveWorkspaceResponse
);
};

View file

@ -44,9 +44,9 @@ class ModelConnectionsApiService {
return baseApiService.get(`/api/v1/model-providers`, modelProviderListResponse);
};
getConnections = async (searchSpaceId: number): Promise<ConnectionRead[]> => {
getConnections = async (workspaceId: number): Promise<ConnectionRead[]> => {
return baseApiService.get(
`/api/v1/model-connections?workspace_id=${searchSpaceId}`,
`/api/v1/model-connections?workspace_id=${workspaceId}`,
connectionListResponse
);
};
@ -179,12 +179,12 @@ class ModelConnectionsApiService {
return baseApiService.post(`/api/v1/models/${id}/test`, verifyConnectionResponse);
};
getModelRoles = async (searchSpaceId: number): Promise<ModelRoles> => {
return baseApiService.get(`/api/v1/workspaces/${searchSpaceId}/model-roles`, modelRoles);
getModelRoles = async (workspaceId: number): Promise<ModelRoles> => {
return baseApiService.get(`/api/v1/workspaces/${workspaceId}/model-roles`, modelRoles);
};
updateModelRoles = async (searchSpaceId: number, roles: ModelRoles): Promise<ModelRoles> => {
return baseApiService.put(`/api/v1/workspaces/${searchSpaceId}/model-roles`, modelRoles, {
updateModelRoles = async (workspaceId: number, roles: ModelRoles): Promise<ModelRoles> => {
return baseApiService.put(`/api/v1/workspaces/${workspaceId}/model-roles`, modelRoles, {
body: roles,
});
};

View file

@ -110,10 +110,10 @@ class NotificationsApiService {
* Get distinct source types (connector + document types) across all
* status notifications. Used to populate the inbox Status tab filter.
*/
getSourceTypes = async (searchSpaceId?: number): Promise<GetSourceTypesResponse> => {
getSourceTypes = async (workspaceId?: number): Promise<GetSourceTypesResponse> => {
const params = new URLSearchParams();
if (searchSpaceId !== undefined) {
params.append("workspace_id", String(searchSpaceId));
if (workspaceId !== undefined) {
params.append("workspace_id", String(workspaceId));
}
const queryString = params.toString();
@ -125,18 +125,18 @@ class NotificationsApiService {
/**
* Get unread notification count with split between total and recent
* @param searchSpaceId - Optional search space ID to filter by
* @param workspaceId - Optional workspace ID to filter by
* @param type - Optional notification type to filter by (type-safe enum)
* @param category - Optional category filter ('comments' or 'status')
*/
getUnreadCount = async (
searchSpaceId?: number,
workspaceId?: number,
type?: InboxItemTypeEnum,
category?: NotificationCategory
): Promise<GetUnreadCountResponse> => {
const params = new URLSearchParams();
if (searchSpaceId !== undefined) {
params.append("workspace_id", String(searchSpaceId));
if (workspaceId !== undefined) {
params.append("workspace_id", String(workspaceId));
}
if (type) {
params.append("type", type);
@ -156,10 +156,10 @@ class NotificationsApiService {
* Get unread counts for all categories in a single request.
* Replaces 2 separate getUnreadCount calls (comments + status).
*/
getBatchUnreadCounts = async (searchSpaceId?: number): Promise<GetBatchUnreadCountResponse> => {
getBatchUnreadCounts = async (workspaceId?: number): Promise<GetBatchUnreadCountResponse> => {
const params = new URLSearchParams();
if (searchSpaceId !== undefined) {
params.append("workspace_id", String(searchSpaceId));
if (workspaceId !== undefined) {
params.append("workspace_id", String(workspaceId));
}
const queryString = params.toString();

View file

@ -15,9 +15,9 @@ const BASE = "/api/v1/podcasts";
const voiceOptionList = z.array(voiceOption);
class PodcastsApiService {
list = async (searchSpaceId: number, limit = 200) => {
list = async (workspaceId: number, limit = 200) => {
const qs = new URLSearchParams({
workspace_id: String(searchSpaceId),
workspace_id: String(workspaceId),
limit: String(limit),
}).toString();
return baseApiService.get(`${BASE}?${qs}`, podcastSummaryList);

View file

@ -12,10 +12,10 @@ import { ValidationError } from "@/lib/error";
import { baseApiService } from "./base-api.service";
class PromptsApiService {
list = async (searchSpaceId?: number) => {
list = async (workspaceId?: number) => {
const params = new URLSearchParams();
if (searchSpaceId !== undefined) {
params.set("workspace_id", String(searchSpaceId));
if (workspaceId !== undefined) {
params.set("workspace_id", String(workspaceId));
}
const queryString = params.toString();
const url = queryString ? `/api/v1/prompts?${queryString}` : "/api/v1/prompts";

View file

@ -4,9 +4,9 @@ import { baseApiService } from "./base-api.service";
const BASE = "/api/v1/reports";
class ReportsApiService {
list = async (searchSpaceId: number, limit = 200) => {
list = async (workspaceId: number, limit = 200) => {
const qs = new URLSearchParams({
workspace_id: String(searchSpaceId),
workspace_id: String(workspaceId),
limit: String(limit),
}).toString();
return baseApiService.get(`${BASE}?${qs}`, reportList);

View file

@ -4,9 +4,9 @@ import { baseApiService } from "./base-api.service";
const BASE = "/api/v1/video-presentations";
class VideoPresentationsApiService {
list = async (searchSpaceId: number, limit = 200) => {
list = async (workspaceId: number, limit = 200) => {
const qs = new URLSearchParams({
workspace_id: String(searchSpaceId),
workspace_id: String(workspaceId),
limit: String(limit),
}).toString();
return baseApiService.get(`${BASE}?${qs}`, videoPresentationList);

View file

@ -1,34 +1,34 @@
import { z } from "zod";
import {
type CreateSearchSpaceRequest,
createSearchSpaceRequest,
createSearchSpaceResponse,
type DeleteSearchSpaceRequest,
deleteSearchSpaceRequest,
deleteSearchSpaceResponse,
type GetSearchSpaceRequest,
type GetSearchSpacesRequest,
getSearchSpaceRequest,
getSearchSpaceResponse,
getSearchSpacesRequest,
getSearchSpacesResponse,
leaveSearchSpaceResponse,
type UpdateSearchSpaceApiAccessRequest,
type UpdateSearchSpaceRequest,
updateSearchSpaceApiAccessRequest,
updateSearchSpaceApiAccessResponse,
updateSearchSpaceRequest,
updateSearchSpaceResponse,
type CreateWorkspaceRequest,
createWorkspaceRequest,
createWorkspaceResponse,
type DeleteWorkspaceRequest,
deleteWorkspaceRequest,
deleteWorkspaceResponse,
type GetWorkspaceRequest,
type GetWorkspacesRequest,
getWorkspaceRequest,
getWorkspaceResponse,
getWorkspacesRequest,
getWorkspacesResponse,
leaveWorkspaceResponse,
type UpdateWorkspaceApiAccessRequest,
type UpdateWorkspaceRequest,
updateWorkspaceApiAccessRequest,
updateWorkspaceApiAccessResponse,
updateWorkspaceRequest,
updateWorkspaceResponse,
} from "@/contracts/types/workspace.types";
import { ValidationError } from "../error";
import { baseApiService } from "./base-api.service";
class SearchSpacesApiService {
class WorkspacesApiService {
/**
* Get a list of search spaces with optional filtering and pagination
* Get a list of workspaces with optional filtering and pagination
*/
getSearchSpaces = async (request?: GetSearchSpacesRequest) => {
const parsedRequest = getSearchSpacesRequest.safeParse(request || {});
getWorkspaces = async (request?: GetWorkspacesRequest) => {
const parsedRequest = getWorkspacesRequest.safeParse(request || {});
if (!parsedRequest.success) {
console.error("Invalid request:", parsedRequest.error);
@ -50,14 +50,14 @@ class SearchSpacesApiService {
? new URLSearchParams(transformedQueryParams).toString()
: "";
return baseApiService.get(`/api/v1/workspaces?${queryParams}`, getSearchSpacesResponse);
return baseApiService.get(`/api/v1/workspaces?${queryParams}`, getWorkspacesResponse);
};
/**
* Create a new search space
* Create a new workspace
*/
createSearchSpace = async (request: CreateSearchSpaceRequest) => {
const parsedRequest = createSearchSpaceRequest.safeParse(request);
createWorkspace = async (request: CreateWorkspaceRequest) => {
const parsedRequest = createWorkspaceRequest.safeParse(request);
if (!parsedRequest.success) {
console.error("Invalid request:", parsedRequest.error);
@ -66,16 +66,16 @@ class SearchSpacesApiService {
throw new ValidationError(`Invalid request: ${errorMessage}`);
}
return baseApiService.post(`/api/v1/workspaces`, createSearchSpaceResponse, {
return baseApiService.post(`/api/v1/workspaces`, createWorkspaceResponse, {
body: parsedRequest.data,
});
};
/**
* Get a single search space by ID
* Get a single workspace by ID
*/
getSearchSpace = async (request: GetSearchSpaceRequest) => {
const parsedRequest = getSearchSpaceRequest.safeParse(request);
getWorkspace = async (request: GetWorkspaceRequest) => {
const parsedRequest = getWorkspaceRequest.safeParse(request);
if (!parsedRequest.success) {
console.error("Invalid request:", parsedRequest.error);
@ -84,14 +84,14 @@ class SearchSpacesApiService {
throw new ValidationError(`Invalid request: ${errorMessage}`);
}
return baseApiService.get(`/api/v1/workspaces/${request.id}`, getSearchSpaceResponse);
return baseApiService.get(`/api/v1/workspaces/${request.id}`, getWorkspaceResponse);
};
/**
* Update an existing search space
* Update an existing workspace
*/
updateSearchSpace = async (request: UpdateSearchSpaceRequest) => {
const parsedRequest = updateSearchSpaceRequest.safeParse(request);
updateWorkspace = async (request: UpdateWorkspaceRequest) => {
const parsedRequest = updateWorkspaceRequest.safeParse(request);
if (!parsedRequest.success) {
console.error("Invalid request:", parsedRequest.error);
@ -100,13 +100,13 @@ class SearchSpacesApiService {
throw new ValidationError(`Invalid request: ${errorMessage}`);
}
return baseApiService.put(`/api/v1/workspaces/${request.id}`, updateSearchSpaceResponse, {
return baseApiService.put(`/api/v1/workspaces/${request.id}`, updateWorkspaceResponse, {
body: parsedRequest.data.data,
});
};
updateSearchSpaceApiAccess = async (request: UpdateSearchSpaceApiAccessRequest) => {
const parsedRequest = updateSearchSpaceApiAccessRequest.safeParse(request);
updateWorkspaceApiAccess = async (request: UpdateWorkspaceApiAccessRequest) => {
const parsedRequest = updateWorkspaceApiAccessRequest.safeParse(request);
if (!parsedRequest.success) {
console.error("Invalid request:", parsedRequest.error);
@ -116,7 +116,7 @@ class SearchSpacesApiService {
return baseApiService.put(
`/api/v1/workspaces/${request.id}/api-access`,
updateSearchSpaceApiAccessResponse,
updateWorkspaceApiAccessResponse,
{
body: { api_access_enabled: parsedRequest.data.api_access_enabled },
}
@ -124,10 +124,10 @@ class SearchSpacesApiService {
};
/**
* Delete a search space
* Delete a workspace
*/
deleteSearchSpace = async (request: DeleteSearchSpaceRequest) => {
const parsedRequest = deleteSearchSpaceRequest.safeParse(request);
deleteWorkspace = async (request: DeleteWorkspaceRequest) => {
const parsedRequest = deleteWorkspaceRequest.safeParse(request);
if (!parsedRequest.success) {
console.error("Invalid request:", parsedRequest.error);
@ -136,30 +136,30 @@ class SearchSpacesApiService {
throw new ValidationError(`Invalid request: ${errorMessage}`);
}
return baseApiService.delete(`/api/v1/workspaces/${request.id}`, deleteSearchSpaceResponse);
return baseApiService.delete(`/api/v1/workspaces/${request.id}`, deleteWorkspaceResponse);
};
/**
* Trigger AI file sorting for all documents in a search space
* Trigger AI file sorting for all documents in a workspace
*/
triggerAiSort = async (searchSpaceId: number) => {
triggerAiSort = async (workspaceId: number) => {
return baseApiService.post(
`/api/v1/workspaces/${searchSpaceId}/ai-sort`,
`/api/v1/workspaces/${workspaceId}/ai-sort`,
z.object({ message: z.string() }),
{}
);
};
/**
* Leave a search space (remove own membership)
* This is used by non-owners to leave a shared search space
* Leave a workspace (remove own membership)
* This is used by non-owners to leave a shared workspace
*/
leaveSearchSpace = async (searchSpaceId: number) => {
leaveWorkspace = async (workspaceId: number) => {
return baseApiService.delete(
`/api/v1/workspaces/${searchSpaceId}/members/me`,
leaveSearchSpaceResponse
`/api/v1/workspaces/${workspaceId}/members/me`,
leaveWorkspaceResponse
);
};
}
export const searchSpacesApiService = new SearchSpacesApiService();
export const workspacesApiService = new WorkspacesApiService();

View file

@ -70,7 +70,7 @@ export type BuilderExecution = z.infer<typeof builderExecutionSchema>;
* Per-automation model selection. ``0`` means "unset" the builder resolves it
* to the eligible default during render, and the resolved (non-zero) ids are
* written onto ``definition.models`` at submit so the run is insulated from
* later chat/search-space model changes.
* later chat/workspace model changes.
*/
export const builderModelsSchema = z.object({
chatModelId: z.number().int(),
@ -236,7 +236,7 @@ function buildDefinition(form: BuilderForm): AutomationDefinition {
metadata: { tags: form.tags },
// Only emit models when fully resolved (the builder seeds non-zero
// defaults before submit). A zero/unset triple is omitted so the
// backend falls back to the search-space snapshot.
// backend falls back to the workspace snapshot.
...(hasResolvedModels(form.models)
? {
models: {
@ -267,11 +267,11 @@ export function buildScheduleTrigger(form: BuilderForm): TriggerCreateRequest |
export function buildCreatePayload(
form: BuilderForm,
searchSpaceId: number
workspaceId: number
): AutomationCreateRequest {
const trigger = buildScheduleTrigger(form);
return {
workspace_id: searchSpaceId,
workspace_id: workspaceId,
name: form.name.trim(),
description: form.description?.trim() ? form.description.trim() : null,
definition: buildDefinition(form),

View file

@ -39,7 +39,7 @@ export interface RawChatErrorInput {
error: unknown;
flow: ChatFlow;
context?: {
searchSpaceId?: number;
workspaceId?: number;
threadId?: number | null;
};
}

View file

@ -5,15 +5,15 @@ import type {
ThreadRecord,
} from "@/lib/chat/thread-persistence";
type SearchSpaceKey = number | string;
type WorkspaceKey = number | string;
type ThreadMetadataPatch = Partial<ThreadRecord> &
Partial<ThreadListItem> & {
has_comments?: boolean;
};
function isSameSearchSpace(keyValue: unknown, searchSpaceId: SearchSpaceKey): boolean {
return String(keyValue) === String(searchSpaceId);
function isSameWorkspace(keyValue: unknown, workspaceId: WorkspaceKey): boolean {
return String(keyValue) === String(workspaceId);
}
function isThreadListResponse(value: unknown): value is ThreadListResponse {
@ -88,30 +88,30 @@ function patchThreadRecord(
};
}
function threadListQueryFilter(searchSpaceId: SearchSpaceKey) {
function threadListQueryFilter(workspaceId: WorkspaceKey) {
return {
predicate: ({ queryKey }: { queryKey: QueryKey }) =>
Array.isArray(queryKey) &&
queryKey[0] === "threads" &&
isSameSearchSpace(queryKey[1], searchSpaceId),
isSameWorkspace(queryKey[1], workspaceId),
};
}
function allThreadsQueryFilter(searchSpaceId: SearchSpaceKey) {
function allThreadsQueryFilter(workspaceId: WorkspaceKey) {
return {
predicate: ({ queryKey }: { queryKey: QueryKey }) =>
Array.isArray(queryKey) &&
queryKey[0] === "all-threads" &&
isSameSearchSpace(queryKey[1], searchSpaceId),
isSameWorkspace(queryKey[1], workspaceId),
};
}
function searchThreadsQueryFilter(searchSpaceId: SearchSpaceKey) {
function searchThreadsQueryFilter(workspaceId: WorkspaceKey) {
return {
predicate: ({ queryKey }: { queryKey: QueryKey }) =>
Array.isArray(queryKey) &&
queryKey[0] === "search-threads" &&
isSameSearchSpace(queryKey[1], searchSpaceId),
isSameWorkspace(queryKey[1], workspaceId),
};
}
@ -149,14 +149,14 @@ function updateThreadListResponse(
export function patchThreadEverywhere(
queryClient: QueryClient,
searchSpaceId: SearchSpaceKey,
workspaceId: WorkspaceKey,
threadId: number,
patch: ThreadMetadataPatch
): void {
updateThreadListResponse(queryClient, threadListQueryFilter(searchSpaceId), threadId, patch);
updateThreadListResponse(queryClient, allThreadsQueryFilter(searchSpaceId), threadId, patch);
updateThreadListResponse(queryClient, threadListQueryFilter(workspaceId), threadId, patch);
updateThreadListResponse(queryClient, allThreadsQueryFilter(workspaceId), threadId, patch);
queryClient.setQueriesData<ThreadListItem[]>(searchThreadsQueryFilter(searchSpaceId), (old) => {
queryClient.setQueriesData<ThreadListItem[]>(searchThreadsQueryFilter(workspaceId), (old) => {
if (!isThreadListItemArray(old)) return old;
return patchThreadListItems(old, threadId, patch);
});
@ -169,15 +169,15 @@ export function patchThreadEverywhere(
export function replaceThreadEverywhere(
queryClient: QueryClient,
searchSpaceId: SearchSpaceKey,
workspaceId: WorkspaceKey,
thread: ThreadRecord
): void {
patchThreadEverywhere(queryClient, searchSpaceId, thread.id, thread);
patchThreadEverywhere(queryClient, workspaceId, thread.id, thread);
}
export function removeThreadEverywhere(
queryClient: QueryClient,
searchSpaceId: SearchSpaceKey,
workspaceId: WorkspaceKey,
threadId: number
): void {
const removeFromListResponse = (old: ThreadListResponse | undefined) => {
@ -190,14 +190,14 @@ export function removeThreadEverywhere(
};
queryClient.setQueriesData<ThreadListResponse>(
threadListQueryFilter(searchSpaceId),
threadListQueryFilter(workspaceId),
removeFromListResponse
);
queryClient.setQueriesData<ThreadListResponse>(
allThreadsQueryFilter(searchSpaceId),
allThreadsQueryFilter(workspaceId),
removeFromListResponse
);
queryClient.setQueriesData<ThreadListItem[]>(searchThreadsQueryFilter(searchSpaceId), (old) => {
queryClient.setQueriesData<ThreadListItem[]>(searchThreadsQueryFilter(workspaceId), (old) => {
if (!isThreadListItemArray(old)) return old;
return old.filter((thread) => thread.id !== threadId);
});
@ -207,7 +207,7 @@ export function removeThreadEverywhere(
export function moveThreadArchiveState(
queryClient: QueryClient,
searchSpaceId: SearchSpaceKey,
workspaceId: WorkspaceKey,
threadId: number,
archived: boolean
): void {
@ -232,14 +232,14 @@ export function moveThreadArchiveState(
};
queryClient.setQueriesData<ThreadListResponse>(
threadListQueryFilter(searchSpaceId),
threadListQueryFilter(workspaceId),
moveInListResponse
);
queryClient.setQueriesData<ThreadListResponse>(
allThreadsQueryFilter(searchSpaceId),
allThreadsQueryFilter(workspaceId),
moveInListResponse
);
queryClient.setQueriesData<ThreadListItem[]>(searchThreadsQueryFilter(searchSpaceId), (old) => {
queryClient.setQueriesData<ThreadListItem[]>(searchThreadsQueryFilter(workspaceId), (old) => {
if (!isThreadListItemArray(old)) return old;
return old.map((thread) => (thread.id === threadId ? { ...thread, archived } : thread));
});

View file

@ -89,13 +89,13 @@ export interface ThreadHistoryLoadResponse {
// =============================================================================
/**
* Fetch list of threads for a search space
* Fetch list of threads for a workspace
*/
export async function fetchThreads(
searchSpaceId: number,
workspaceId: number,
limit?: number
): Promise<ThreadListResponse> {
const params = new URLSearchParams({ workspace_id: String(searchSpaceId) });
const params = new URLSearchParams({ workspace_id: String(workspaceId) });
if (limit) params.append("limit", String(limit));
return baseApiService.get<ThreadListResponse>(`/api/v1/threads?${params}`);
}
@ -103,12 +103,9 @@ export async function fetchThreads(
/**
* Search threads by title
*/
export async function searchThreads(
searchSpaceId: number,
title: string
): Promise<ThreadListItem[]> {
export async function searchThreads(workspaceId: number, title: string): Promise<ThreadListItem[]> {
const params = new URLSearchParams({
workspace_id: String(searchSpaceId),
workspace_id: String(workspaceId),
title,
});
return baseApiService.get<ThreadListItem[]>(`/api/v1/threads/search?${params}`);
@ -117,15 +114,12 @@ export async function searchThreads(
/**
* Create a new thread
*/
export async function createThread(
searchSpaceId: number,
title = "New Chat"
): Promise<ThreadRecord> {
export async function createThread(workspaceId: number, title = "New Chat"): Promise<ThreadRecord> {
return baseApiService.post<ThreadRecord>("/api/v1/threads", undefined, {
body: {
title,
archived: false,
workspace_id: searchSpaceId,
workspace_id: workspaceId,
},
});
}
@ -212,7 +206,7 @@ export async function getThreadFull(threadId: number): Promise<ThreadRecord> {
* Regeneration request parameters
*/
export interface RegenerateParams {
searchSpaceId: number;
workspaceId: number;
userQuery?: string | null; // New user query (for edit). Null/undefined = reload with same query
attachments?: Array<{
id: string;

View file

@ -13,7 +13,7 @@ export interface FolderSyncProgress {
export interface FolderSyncParams {
folderPath: string;
folderName: string;
searchSpaceId: number;
workspaceId: number;
excludePatterns: string[];
fileExtensions: string[];
processingMode?: "basic" | "premium";
@ -59,7 +59,7 @@ async function uploadBatchesWithConcurrency(
batches: FolderFileEntry[][],
params: {
folderName: string;
searchSpaceId: number;
workspaceId: number;
rootFolderId: number | null;
processingMode?: "basic" | "premium";
signal?: AbortSignal;
@ -95,7 +95,7 @@ async function uploadBatchesWithConcurrency(
files,
{
folder_name: params.folderName,
workspace_id: params.searchSpaceId,
workspace_id: params.workspaceId,
relative_paths: batch.map((e) => e.relativePath),
root_folder_id: resolvedRootFolderId,
processing_mode: params.processingMode,
@ -141,7 +141,7 @@ export async function uploadFolderScan(params: FolderSyncParams): Promise<number
const {
folderPath,
folderName,
searchSpaceId,
workspaceId,
excludePatterns,
fileExtensions,
processingMode,
@ -159,7 +159,7 @@ export async function uploadFolderScan(params: FolderSyncParams): Promise<number
excludePatterns,
fileExtensions,
rootFolderId: rootFolderId ?? null,
searchSpaceId,
workspaceId,
active: true,
});
@ -169,7 +169,7 @@ export async function uploadFolderScan(params: FolderSyncParams): Promise<number
const mtimeCheckResult = await documentsApiService.folderMtimeCheck({
folder_name: folderName,
workspace_id: searchSpaceId,
workspace_id: workspaceId,
files: allFiles.map((f) => ({ relative_path: f.relativePath, mtime: f.mtimeMs / 1000 })),
});
@ -187,7 +187,7 @@ export async function uploadFolderScan(params: FolderSyncParams): Promise<number
const uploadedRootId = await uploadBatchesWithConcurrency(batches, {
folderName,
searchSpaceId,
workspaceId,
rootFolderId: rootFolderId ?? null,
processingMode,
signal,
@ -214,7 +214,7 @@ export async function uploadFolderScan(params: FolderSyncParams): Promise<number
await documentsApiService.folderSyncFinalize({
folder_name: folderName,
workspace_id: searchSpaceId,
workspace_id: workspaceId,
root_folder_id: rootFolderId ?? null,
all_relative_paths: allFiles.map((f) => f.relativePath),
});

View file

@ -11,9 +11,9 @@ export function hasEnabledChatModel(connections: ConnectionRead[]): boolean {
export function isLlmOnboardingComplete(
chatModelId: number | null | undefined,
globalConnections: ConnectionRead[],
searchSpaceConnections: ConnectionRead[]
workspaceConnections: ConnectionRead[]
): boolean {
const connections = [...globalConnections, ...searchSpaceConnections];
const connections = [...globalConnections, ...workspaceConnections];
const resolvedChatModelId = chatModelId ?? 0;
if (resolvedChatModelId === 0) {

View file

@ -13,7 +13,7 @@ import { getConnectorTelemetryMeta } from "@/lib/connector-telemetry";
*
* Categories:
* - auth: Authentication events
* - search_space: Search space management
* - workspace: Search space management
* - document: Document management
* - chat: Chat and messaging (authenticated + anonymous)
* - connector: External connector events (all lifecycle stages)
@ -78,22 +78,22 @@ export function trackLogout() {
// SEARCH SPACE EVENTS
// ============================================
export function trackSearchSpaceCreated(searchSpaceId: number, name: string) {
safeCapture("search_space_created", {
workspace_id: searchSpaceId,
export function trackWorkspaceCreated(workspaceId: number, name: string) {
safeCapture("workspace_created", {
workspace_id: workspaceId,
name,
});
}
export function trackSearchSpaceDeleted(searchSpaceId: number) {
safeCapture("search_space_deleted", {
workspace_id: searchSpaceId,
export function trackWorkspaceDeleted(workspaceId: number) {
safeCapture("workspace_deleted", {
workspace_id: workspaceId,
});
}
export function trackSearchSpaceViewed(searchSpaceId: number) {
safeCapture("search_space_viewed", {
workspace_id: searchSpaceId,
export function trackWorkspaceViewed(workspaceId: number) {
safeCapture("workspace_viewed", {
workspace_id: workspaceId,
});
}
@ -101,15 +101,15 @@ export function trackSearchSpaceViewed(searchSpaceId: number) {
// CHAT EVENTS
// ============================================
export function trackChatCreated(searchSpaceId: number, chatId: number) {
export function trackChatCreated(workspaceId: number, chatId: number) {
safeCapture("chat_created", {
workspace_id: searchSpaceId,
workspace_id: workspaceId,
chat_id: chatId,
});
}
export function trackChatMessageSent(
searchSpaceId: number,
workspaceId: number,
chatId: number,
options?: {
hasAttachments?: boolean;
@ -118,7 +118,7 @@ export function trackChatMessageSent(
}
) {
safeCapture("chat_message_sent", {
workspace_id: searchSpaceId,
workspace_id: workspaceId,
chat_id: chatId,
has_attachments: options?.hasAttachments ?? false,
has_mentioned_documents: options?.hasMentionedDocuments ?? false,
@ -126,16 +126,16 @@ export function trackChatMessageSent(
});
}
export function trackChatResponseReceived(searchSpaceId: number, chatId: number) {
export function trackChatResponseReceived(workspaceId: number, chatId: number) {
safeCapture("chat_response_received", {
workspace_id: searchSpaceId,
workspace_id: workspaceId,
chat_id: chatId,
});
}
export function trackChatError(searchSpaceId: number, chatId: number, error?: string) {
export function trackChatError(workspaceId: number, chatId: number, error?: string) {
safeCapture("chat_error", {
workspace_id: searchSpaceId,
workspace_id: workspaceId,
chat_id: chatId,
error,
});
@ -151,14 +151,14 @@ export interface ChatFailureTelemetry {
}
export function trackChatBlocked(
searchSpaceId: number,
workspaceId: number,
chatId: number | null,
payload: ChatFailureTelemetry
) {
safeCapture(
"chat_blocked",
compact({
workspace_id: searchSpaceId,
workspace_id: workspaceId,
chat_id: chatId ?? undefined,
flow: payload.flow,
kind: payload.kind,
@ -171,14 +171,14 @@ export function trackChatBlocked(
}
export function trackChatErrorDetailed(
searchSpaceId: number,
workspaceId: number,
chatId: number | null,
payload: ChatFailureTelemetry
) {
safeCapture(
"chat_error",
compact({
workspace_id: searchSpaceId,
workspace_id: workspaceId,
chat_id: chatId ?? undefined,
flow: payload.flow,
kind: payload.kind,
@ -215,48 +215,48 @@ export function trackAnonymousChatMessageSent(options: {
// ============================================
export function trackDocumentUploadStarted(
searchSpaceId: number,
workspaceId: number,
fileCount: number,
totalSizeBytes: number
) {
safeCapture("document_upload_started", {
workspace_id: searchSpaceId,
workspace_id: workspaceId,
file_count: fileCount,
total_size_bytes: totalSizeBytes,
});
}
export function trackDocumentUploadSuccess(searchSpaceId: number, fileCount: number) {
export function trackDocumentUploadSuccess(workspaceId: number, fileCount: number) {
safeCapture("document_upload_success", {
workspace_id: searchSpaceId,
workspace_id: workspaceId,
file_count: fileCount,
});
}
export function trackDocumentUploadFailure(searchSpaceId: number, error?: string) {
export function trackDocumentUploadFailure(workspaceId: number, error?: string) {
safeCapture("document_upload_failure", {
workspace_id: searchSpaceId,
workspace_id: workspaceId,
error,
});
}
export function trackDocumentDeleted(searchSpaceId: number, documentId: number) {
export function trackDocumentDeleted(workspaceId: number, documentId: number) {
safeCapture("document_deleted", {
workspace_id: searchSpaceId,
workspace_id: workspaceId,
document_id: documentId,
});
}
export function trackDocumentBulkDeleted(searchSpaceId: number, count: number) {
export function trackDocumentBulkDeleted(workspaceId: number, count: number) {
safeCapture("document_bulk_deleted", {
workspace_id: searchSpaceId,
workspace_id: workspaceId,
count,
});
}
export function trackYouTubeImport(searchSpaceId: number, url: string) {
export function trackYouTubeImport(workspaceId: number, url: string) {
safeCapture("youtube_import_started", {
workspace_id: searchSpaceId,
workspace_id: workspaceId,
url,
});
}
@ -281,7 +281,7 @@ export type ConnectorEventStage =
| "synced";
export interface ConnectorEventOptions {
searchSpaceId?: number | null;
workspaceId?: number | null;
connectorId?: number | null;
/** Source of the action (e.g. "oauth_callback", "non_oauth_form", "webcrawler_quick_add"). */
source?: string;
@ -303,7 +303,7 @@ export function trackConnectorEvent(
const meta = getConnectorTelemetryMeta(connectorType);
safeCapture(`connector_${stage}`, {
...compact({
workspace_id: options.searchSpaceId ?? undefined,
workspace_id: options.workspaceId ?? undefined,
connector_id: options.connectorId ?? undefined,
source: options.source,
error: options.error,
@ -319,64 +319,64 @@ export function trackConnectorEvent(
// ---- Convenience wrappers kept for backward compatibility ----
export function trackConnectorSetupStarted(
searchSpaceId: number,
workspaceId: number,
connectorType: string,
source?: string
) {
trackConnectorEvent("setup_started", connectorType, { searchSpaceId, source });
trackConnectorEvent("setup_started", connectorType, { workspaceId, source });
}
export function trackConnectorSetupSuccess(
searchSpaceId: number,
workspaceId: number,
connectorType: string,
connectorId: number
) {
trackConnectorEvent("setup_success", connectorType, { searchSpaceId, connectorId });
trackConnectorEvent("setup_success", connectorType, { workspaceId, connectorId });
}
export function trackConnectorSetupFailure(
searchSpaceId: number | null | undefined,
workspaceId: number | null | undefined,
connectorType: string,
error?: string,
source?: string
) {
trackConnectorEvent("setup_failure", connectorType, {
searchSpaceId: searchSpaceId ?? undefined,
workspaceId: workspaceId ?? undefined,
error,
source,
});
}
export function trackConnectorDeleted(
searchSpaceId: number,
workspaceId: number,
connectorType: string,
connectorId: number
) {
trackConnectorEvent("deleted", connectorType, { searchSpaceId, connectorId });
trackConnectorEvent("deleted", connectorType, { workspaceId, connectorId });
}
export function trackConnectorSynced(
searchSpaceId: number,
workspaceId: number,
connectorType: string,
connectorId: number
) {
trackConnectorEvent("synced", connectorType, { searchSpaceId, connectorId });
trackConnectorEvent("synced", connectorType, { workspaceId, connectorId });
}
// ============================================
// SETTINGS EVENTS
// ============================================
export function trackSettingsViewed(searchSpaceId: number, section: string) {
export function trackSettingsViewed(workspaceId: number, section: string) {
safeCapture("settings_viewed", {
workspace_id: searchSpaceId,
workspace_id: workspaceId,
section,
});
}
export function trackSettingsUpdated(searchSpaceId: number, section: string, setting: string) {
export function trackSettingsUpdated(workspaceId: number, section: string, setting: string) {
safeCapture("settings_updated", {
workspace_id: searchSpaceId,
workspace_id: workspaceId,
section,
setting,
});
@ -386,16 +386,16 @@ export function trackSettingsUpdated(searchSpaceId: number, section: string, set
// FEATURE USAGE EVENTS
// ============================================
export function trackPodcastGenerated(searchSpaceId: number, chatId: number) {
export function trackPodcastGenerated(workspaceId: number, chatId: number) {
safeCapture("podcast_generated", {
workspace_id: searchSpaceId,
workspace_id: workspaceId,
chat_id: chatId,
});
}
export function trackSourcesTabViewed(searchSpaceId: number, tab: string) {
export function trackSourcesTabViewed(workspaceId: number, tab: string) {
safeCapture("sources_tab_viewed", {
workspace_id: searchSpaceId,
workspace_id: workspaceId,
tab,
});
}
@ -414,59 +414,59 @@ export function trackDesktopDownloadClicked(options: {
// SEARCH SPACE INVITE EVENTS
// ============================================
export function trackSearchSpaceInviteSent(
searchSpaceId: number,
export function trackWorkspaceInviteSent(
workspaceId: number,
options?: {
roleName?: string;
hasExpiry?: boolean;
hasMaxUses?: boolean;
}
) {
safeCapture("search_space_invite_sent", {
workspace_id: searchSpaceId,
safeCapture("workspace_invite_sent", {
workspace_id: workspaceId,
role_name: options?.roleName,
has_expiry: options?.hasExpiry ?? false,
has_max_uses: options?.hasMaxUses ?? false,
});
}
export function trackSearchSpaceInviteAccepted(
searchSpaceId: number,
searchSpaceName: string,
export function trackWorkspaceInviteAccepted(
workspaceId: number,
workspaceName: string,
roleName?: string | null
) {
safeCapture("search_space_invite_accepted", {
workspace_id: searchSpaceId,
search_space_name: searchSpaceName,
safeCapture("workspace_invite_accepted", {
workspace_id: workspaceId,
workspace_name: workspaceName,
role_name: roleName,
});
}
export function trackSearchSpaceInviteDeclined(searchSpaceName?: string) {
safeCapture("search_space_invite_declined", {
search_space_name: searchSpaceName,
export function trackWorkspaceInviteDeclined(workspaceName?: string) {
safeCapture("workspace_invite_declined", {
workspace_name: workspaceName,
});
}
export function trackSearchSpaceUserAdded(
searchSpaceId: number,
searchSpaceName: string,
export function trackWorkspaceUserAdded(
workspaceId: number,
workspaceName: string,
roleName?: string | null
) {
safeCapture("search_space_user_added", {
workspace_id: searchSpaceId,
search_space_name: searchSpaceName,
safeCapture("workspace_user_added", {
workspace_id: workspaceId,
workspace_name: workspaceName,
role_name: roleName,
});
}
export function trackSearchSpaceUsersViewed(
searchSpaceId: number,
export function trackWorkspaceUsersViewed(
workspaceId: number,
userCount: number,
ownerCount: number
) {
safeCapture("search_space_users_viewed", {
workspace_id: searchSpaceId,
safeCapture("workspace_users_viewed", {
workspace_id: workspaceId,
user_count: userCount,
owner_count: ownerCount,
});
@ -477,12 +477,12 @@ export function trackSearchSpaceUsersViewed(
// ============================================
export function trackConnectorConnected(
searchSpaceId: number,
workspaceId: number,
connectorType: string,
connectorId?: number
) {
trackConnectorEvent("connected", connectorType, {
searchSpaceId,
workspaceId,
connectorId: connectorId ?? undefined,
});
}
@ -492,19 +492,19 @@ export function trackConnectorConnected(
// ============================================
export function trackIndexWithDateRangeOpened(
searchSpaceId: number,
workspaceId: number,
connectorType: string,
connectorId: number
) {
safeCapture("index_with_date_range_opened", {
workspace_id: searchSpaceId,
workspace_id: workspaceId,
connector_type: connectorType,
connector_id: connectorId,
});
}
export function trackIndexWithDateRangeStarted(
searchSpaceId: number,
workspaceId: number,
connectorType: string,
connectorId: number,
options?: {
@ -513,7 +513,7 @@ export function trackIndexWithDateRangeStarted(
}
) {
safeCapture("index_with_date_range_started", {
workspace_id: searchSpaceId,
workspace_id: workspaceId,
connector_type: connectorType,
connector_id: connectorId,
has_start_date: options?.hasStartDate ?? false,
@ -522,37 +522,37 @@ export function trackIndexWithDateRangeStarted(
}
export function trackQuickIndexClicked(
searchSpaceId: number,
workspaceId: number,
connectorType: string,
connectorId: number
) {
safeCapture("quick_index_clicked", {
workspace_id: searchSpaceId,
workspace_id: workspaceId,
connector_type: connectorType,
connector_id: connectorId,
});
}
export function trackConfigurePeriodicIndexingOpened(
searchSpaceId: number,
workspaceId: number,
connectorType: string,
connectorId: number
) {
safeCapture("configure_periodic_indexing_opened", {
workspace_id: searchSpaceId,
workspace_id: workspaceId,
connector_type: connectorType,
connector_id: connectorId,
});
}
export function trackPeriodicIndexingStarted(
searchSpaceId: number,
workspaceId: number,
connectorType: string,
connectorId: number,
frequencyMinutes: number
) {
safeCapture("periodic_indexing_started", {
workspace_id: searchSpaceId,
workspace_id: workspaceId,
connector_type: connectorType,
connector_id: connectorId,
frequency_minutes: frequencyMinutes,

View file

@ -1,7 +1,7 @@
import type { GetConnectorsRequest } from "@/contracts/types/connector.types";
import type { GetDocumentsRequest } from "@/contracts/types/document.types";
import type { GetLogsRequest } from "@/contracts/types/log.types";
import type { GetSearchSpacesRequest } from "@/contracts/types/workspace.types";
import type { GetWorkspacesRequest } from "@/contracts/types/workspace.types";
/**
* Convert an object to a stable array of [key, value] pairs sorted by key.
@ -30,51 +30,51 @@ export const cacheKeys = {
document: (documentId: string) => ["document", documentId] as const,
},
logs: {
list: (searchSpaceId?: number | string) => ["logs", "list", searchSpaceId] as const,
list: (workspaceId?: number | string) => ["logs", "list", workspaceId] as const,
detail: (logId: number | string) => ["logs", "detail", logId] as const,
summary: (searchSpaceId?: number | string) => ["logs", "summary", searchSpaceId] as const,
summary: (workspaceId?: number | string) => ["logs", "summary", workspaceId] as const,
withQueryParams: (queries: GetLogsRequest["queryParams"]) =>
["logs", "with-query-params", ...stableEntries(queries)] as const,
},
modelConnections: {
all: (searchSpaceId: number) => ["model-connections", searchSpaceId] as const,
all: (workspaceId: number) => ["model-connections", workspaceId] as const,
global: () => ["model-connections", "global"] as const,
globalConfigStatus: () => ["model-connections", "global-config-status"] as const,
providers: () => ["model-connections", "providers"] as const,
roles: (searchSpaceId: number) => ["model-roles", searchSpaceId] as const,
roles: (workspaceId: number) => ["model-roles", workspaceId] as const,
},
auth: {
user: ["auth", "user"] as const,
},
searchSpaces: {
all: ["search-spaces"] as const,
withQueryParams: (queries: GetSearchSpacesRequest["queryParams"]) =>
["search-spaces", ...stableEntries(queries)] as const,
detail: (searchSpaceId: string) => ["search-spaces", searchSpaceId] as const,
workspaces: {
all: ["workspaces"] as const,
withQueryParams: (queries: GetWorkspacesRequest["queryParams"]) =>
["workspaces", ...stableEntries(queries)] as const,
detail: (workspaceId: string) => ["workspaces", workspaceId] as const,
},
user: {
current: () => ["user", "me"] as const,
},
roles: {
all: (searchSpaceId: string) => ["roles", searchSpaceId] as const,
byId: (searchSpaceId: string, roleId: string) => ["roles", searchSpaceId, roleId] as const,
all: (workspaceId: string) => ["roles", workspaceId] as const,
byId: (workspaceId: string, roleId: string) => ["roles", workspaceId, roleId] as const,
},
permissions: {
all: () => ["permissions"] as const,
},
members: {
all: (searchSpaceId: string) => ["members", searchSpaceId] as const,
myAccess: (searchSpaceId: string) => ["members", "my-access", searchSpaceId] as const,
all: (workspaceId: string) => ["members", workspaceId] as const,
myAccess: (workspaceId: string) => ["members", "my-access", workspaceId] as const,
},
invites: {
all: (searchSpaceId: string) => ["invites", searchSpaceId] as const,
all: (workspaceId: string) => ["invites", workspaceId] as const,
info: (inviteCode: string) => ["invites", "info", inviteCode] as const,
},
agentTools: {
all: () => ["agent-tools"] as const,
},
connectors: {
all: (searchSpaceId: string) => ["connectors", searchSpaceId] as const,
all: (workspaceId: string) => ["connectors", workspaceId] as const,
withQueryParams: (queries: GetConnectorsRequest["queryParams"]) =>
["connectors", ...stableEntries(queries)] as const,
byId: (connectorId: string) => ["connector", connectorId] as const,
@ -96,31 +96,31 @@ export const cacheKeys = {
},
publicChatSnapshots: {
all: ["public-chat-snapshots"] as const,
bySearchSpace: (searchSpaceId: number) =>
["public-chat-snapshots", "search-space", searchSpaceId] as const,
byWorkspace: (workspaceId: number) =>
["public-chat-snapshots", "workspace", workspaceId] as const,
},
prompts: {
all: () => ["prompts"] as const,
public: () => ["prompts", "public"] as const,
},
notifications: {
search: (searchSpaceId: number | null, search: string, tab: string) =>
["notifications", "search", searchSpaceId, search, tab] as const,
sourceTypes: (searchSpaceId: number | null) =>
["notifications", "source-types", searchSpaceId] as const,
batchUnreadCounts: (searchSpaceId: number | null) =>
["notifications", "unread-counts-batch", searchSpaceId] as const,
search: (workspaceId: number | null, search: string, tab: string) =>
["notifications", "search", workspaceId, search, tab] as const,
sourceTypes: (workspaceId: number | null) =>
["notifications", "source-types", workspaceId] as const,
batchUnreadCounts: (workspaceId: number | null) =>
["notifications", "unread-counts-batch", workspaceId] as const,
},
automations: {
// list endpoint is keyed by pagination too so distinct pages don't collide
list: (searchSpaceId: number, limit: number, offset: number) =>
["automations", "list", searchSpaceId, limit, offset] as const,
list: (workspaceId: number, limit: number, offset: number) =>
["automations", "list", workspaceId, limit, offset] as const,
detail: (automationId: number) => ["automations", "detail", automationId] as const,
runs: (automationId: number, limit: number, offset: number) =>
["automations", "runs", automationId, limit, offset] as const,
run: (automationId: number, runId: number) =>
["automations", "runs", automationId, runId] as const,
modelEligibility: (searchSpaceId: number) =>
["automations", "model-eligibility", searchSpaceId] as const,
modelEligibility: (workspaceId: number) =>
["automations", "model-eligibility", workspaceId] as const,
},
};