Merge remote-tracking branch 'upstream/ci_mvp' into feat/ci-ui-changes

This commit is contained in:
Anish Sarkar 2026-07-06 13:25:51 +05:30
commit fd8d1273cd
188 changed files with 2163 additions and 3644 deletions

View file

@ -156,7 +156,7 @@ export function AutomationBuilderForm({
if (mode === "edit" && automation) {
return { ...buildUpdatePayload(formForPayload), status: automation.status };
}
const { search_space_id: _ignored, ...rest } = buildCreatePayload(formForPayload, workspaceId);
const { workspace_id: _ignored, ...rest } = buildCreatePayload(formForPayload, workspaceId);
return rest;
}

View file

@ -51,7 +51,7 @@ export function AutomationModelFields({
errors,
}: AutomationModelFieldsProps) {
const { llm, image, vision, isLoading } = useAutomationEligibleModels();
const rolesHref = `/dashboard/${workspaceId}/search-space-settings/models`;
const rolesHref = `/dashboard/${workspaceId}/workspace-settings/models`;
return (
<div className="flex flex-col gap-4">

View file

@ -13,7 +13,7 @@ import {
modelConnectionsAtom,
modelRolesAtom,
} from "@/atoms/model-connections/model-connections-query.atoms";
import { activeWorkspaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { activeWorkspaceIdAtom } from "@/atoms/workspaces/workspace-query.atoms";
import { DocumentUploadDialogProvider } from "@/components/assistant-ui/document-upload-popup";
import { LayoutDataProvider } from "@/components/layout";
import { OnboardingTour } from "@/components/onboarding-tour";

View file

@ -754,8 +754,8 @@ export default function NewChatPage() {
syncChatTab({
chatId: thread.id,
title: thread.title,
chatUrl: `/dashboard/${thread.search_space_id ?? workspaceId}/new-chat/${thread.id}`,
searchSpaceId: thread.search_space_id ?? workspaceId,
chatUrl: `/dashboard/${thread.workspace_id ?? workspaceId}/new-chat/${thread.id}`,
searchSpaceId: thread.workspace_id ?? workspaceId,
visibility: thread.visibility,
hasComments: thread.has_comments ?? false,
});
@ -906,7 +906,7 @@ export default function NewChatPage() {
setCurrentThreadMetadata({
id: currentThread.id,
searchSpaceId: currentThread.search_space_id ?? workspaceId,
searchSpaceId: currentThread.workspace_id ?? workspaceId,
visibility,
hasComments: currentThread.has_comments ?? false,
});

View file

@ -139,7 +139,7 @@ export function TeamContent({ workspaceId }: TeamContentProps) {
const handleRevokeInvite = useCallback(
async (inviteId: number): Promise<boolean> => {
const request: DeleteInviteRequest = {
search_space_id: workspaceId,
workspace_id: workspaceId,
invite_id: inviteId,
};
await revokeInvite(request);
@ -151,7 +151,7 @@ export function TeamContent({ workspaceId }: TeamContentProps) {
const handleCreateInvite = useCallback(
async (inviteData: CreateInviteRequest["data"]) => {
const request: CreateInviteRequest = {
search_space_id: workspaceId,
workspace_id: workspaceId,
data: inviteData,
};
return await createInvite(request);
@ -162,7 +162,7 @@ export function TeamContent({ workspaceId }: TeamContentProps) {
const handleUpdateMember = useCallback(
async (membershipId: number, roleId: number | null): Promise<Membership> => {
const request: UpdateMembershipRequest = {
search_space_id: workspaceId,
workspace_id: workspaceId,
membership_id: membershipId,
data: { role_id: roleId },
};
@ -174,7 +174,7 @@ export function TeamContent({ workspaceId }: TeamContentProps) {
const handleRemoveMember = useCallback(
async (membershipId: number) => {
const request: DeleteMembershipRequest = {
search_space_id: workspaceId,
workspace_id: workspaceId,
membership_id: membershipId,
};
await deleteMember(request);
@ -185,13 +185,13 @@ export function TeamContent({ workspaceId }: TeamContentProps) {
const { data: roles = [], isLoading: rolesLoading } = useQuery({
queryKey: cacheKeys.roles.all(workspaceId.toString()),
queryFn: () => rolesApiService.getRoles({ search_space_id: workspaceId }),
queryFn: () => rolesApiService.getRoles({ workspace_id: workspaceId }),
enabled: !!workspaceId,
});
const { data: invites = [], isLoading: invitesLoading } = useQuery({
queryKey: cacheKeys.invites.all(workspaceId.toString()),
queryFn: () => invitesApiService.getInvites({ search_space_id: workspaceId }),
queryFn: () => invitesApiService.getInvites({ workspace_id: workspaceId }),
staleTime: 5 * 60 * 1000,
});
@ -581,7 +581,7 @@ function MemberRow({
<AlertDialogTitle>Remove member?</AlertDialogTitle>
<AlertDialogDescription>
This will remove <span className="font-medium">{member.user_email}</span>{" "}
from this search space. They will lose access to all resources.
from this workspace. They will lose access to all resources.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
@ -599,7 +599,7 @@ function MemberRow({
<DropdownMenuSeparator className="bg-popover-border" />
<DropdownMenuItem
onClick={() =>
router.push(`/dashboard/${workspaceId}/search-space-settings/team-roles`)
router.push(`/dashboard/${workspaceId}/workspace-settings/team-roles`)
}
>
Manage Roles
@ -709,7 +709,7 @@ function CreateInviteDialog({
Invite Created!
</DialogTitle>
<DialogDescription>
Share this link to invite people to your search space.
Share this link to invite people to your workspace.
</DialogDescription>
</DialogHeader>
<div className="space-y-3 py-2 md:py-4">
@ -748,7 +748,7 @@ function CreateInviteDialog({
<DialogHeader>
<DialogTitle>Invite Members</DialogTitle>
<DialogDescription>
Create a link to invite people to this search space.
Create a link to invite people to this workspace.
</DialogDescription>
</DialogHeader>
<div className="space-y-3 py-2 md:py-4">

View file

@ -6,7 +6,7 @@ import { AlertTriangle, Info, ShieldCheck, Trash2 } from "lucide-react";
import { useCallback, useMemo, useState } from "react";
import { toast } from "sonner";
import { agentFlagsAtom } from "@/atoms/agent/agent-flags-query.atom";
import { activeWorkspaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { activeWorkspaceIdAtom } from "@/atoms/workspaces/workspace-query.atoms";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import {
AlertDialog,

View file

@ -13,9 +13,9 @@ import {
import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton";
import { Switch } from "@/components/ui/switch";
import type { SearchSpace } from "@/contracts/types/search-space.types";
import type { SearchSpace } from "@/contracts/types/workspace.types";
import { useElectronAPI } from "@/hooks/use-platform";
import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service";
import { searchSpacesApiService } from "@/lib/apis/workspaces-api.service";
export function DesktopContent() {
const api = useElectronAPI();

View file

@ -17,8 +17,8 @@ import {
} from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton";
import type { SearchSpace } from "@/contracts/types/search-space.types";
import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service";
import type { SearchSpace } from "@/contracts/types/workspace.types";
import { searchSpacesApiService } from "@/lib/apis/workspaces-api.service";
import { authenticatedFetch } from "@/lib/auth-fetch";
import { buildBackendUrl } from "@/lib/env-config";
import { cn } from "@/lib/utils";

View file

@ -11,7 +11,5 @@ export default function UserSettingsLayout({
}) {
const { workspace_id } = use(params);
return (
<UserSettingsLayoutShell workspaceId={workspace_id}>{children}</UserSettingsLayoutShell>
);
return <UserSettingsLayoutShell workspaceId={workspace_id}>{children}</UserSettingsLayoutShell>;
}

View file

@ -76,7 +76,7 @@ export function SearchSpaceSettingsLayoutShell({
const selectedLabel = navItems.find((item) => item.value === activeTab)?.label ?? t("title");
const hrefFor = (tab: SearchSpaceSettingsTab) =>
`/dashboard/${workspaceId}/search-space-settings/${tab}`;
`/dashboard/${workspaceId}/workspace-settings/${tab}`;
return (
<section className="flex h-full min-h-[min(680px,calc(100vh-5rem))] w-full select-none flex-col gap-6 md:pt-6 md:flex-row">

View file

@ -6,5 +6,5 @@ export default async function SearchSpaceSettingsPage({
params: Promise<{ workspace_id: string }>;
}) {
const { workspace_id } = await params;
redirect(`/dashboard/${workspace_id}/search-space-settings/general`);
redirect(`/dashboard/${workspace_id}/workspace-settings/general`);
}

View file

@ -6,7 +6,7 @@ import { motion } from "motion/react";
import { useRouter } from "next/navigation";
import { useTranslations } from "next-intl";
import { useEffect, useState } from "react";
import { searchSpacesAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { searchSpacesAtom } from "@/atoms/workspaces/workspace-query.atoms";
import { CreateSearchSpaceDialog } from "@/components/layout";
import { Button } from "@/components/ui/button";
import { Card, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";

View file

@ -14,7 +14,7 @@ import { Separator } from "@/components/ui/separator";
import { ShortcutKbd } from "@/components/ui/shortcut-kbd";
import { Spinner } from "@/components/ui/spinner";
import { useElectronAPI } from "@/hooks/use-platform";
import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service";
import { searchSpacesApiService } from "@/lib/apis/workspaces-api.service";
import { getPostLoginRedirectPath } from "@/lib/auth-utils";
type ShortcutKey = "generalAssist" | "quickAsk" | "screenshotAssist";

View file

@ -98,15 +98,11 @@ export default function InviteAcceptPage() {
// Track invite accepted and user added events
trackSearchSpaceInviteAccepted(
result.search_space_id,
result.search_space_name,
result.role_name
);
trackSearchSpaceUserAdded(
result.search_space_id,
result.search_space_name,
result.workspace_id,
result.workspace_name,
result.role_name
);
trackSearchSpaceUserAdded(result.workspace_id, result.workspace_name, result.role_name);
}
} catch (err: any) {
setError(err.message || "Failed to accept invite");
@ -117,7 +113,7 @@ export default function InviteAcceptPage() {
const handleDecline = () => {
// Track invite declined event
trackSearchSpaceInviteDeclined(inviteInfo?.search_space_name);
trackSearchSpaceInviteDeclined(inviteInfo?.workspace_name);
router.push("/dashboard");
};
@ -180,7 +176,7 @@ export default function InviteAcceptPage() {
</motion.div>
<CardTitle className="text-2xl">Welcome to the team!</CardTitle>
<CardDescription>
You've successfully joined {acceptedData.search_space_name}
You've successfully joined {acceptedData.workspace_name}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
@ -190,7 +186,7 @@ export default function InviteAcceptPage() {
<Users className="h-5 w-5 text-primary" />
</div>
<div>
<p className="font-medium">{acceptedData.search_space_name}</p>
<p className="font-medium">{acceptedData.workspace_name}</p>
<p className="text-sm text-muted-foreground">Search Space</p>
</div>
</div>
@ -208,7 +204,7 @@ export default function InviteAcceptPage() {
<CardFooter>
<Button
className="w-full gap-2"
onClick={() => router.push(`/dashboard/${acceptedData.search_space_id}`)}
onClick={() => router.push(`/dashboard/${acceptedData.workspace_id}`)}
>
Go to Search Space
<ArrowRight className="h-4 w-4" />
@ -260,7 +256,7 @@ export default function InviteAcceptPage() {
</motion.div>
<CardTitle className="text-2xl">You're Invited!</CardTitle>
<CardDescription>
Sign in to join {inviteInfo?.search_space_name || "this search space"}
Sign in to join {inviteInfo?.workspace_name || "this search space"}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
@ -270,7 +266,7 @@ export default function InviteAcceptPage() {
<Users className="h-5 w-5 text-primary" />
</div>
<div>
<p className="font-medium">{inviteInfo?.search_space_name}</p>
<p className="font-medium">{inviteInfo?.workspace_name}</p>
<p className="text-sm text-muted-foreground">Search Space</p>
</div>
</div>
@ -307,7 +303,7 @@ export default function InviteAcceptPage() {
</motion.div>
<CardTitle className="text-2xl">You're Invited!</CardTitle>
<CardDescription>
Accept this invite to join {inviteInfo?.search_space_name || "this search space"}
Accept this invite to join {inviteInfo?.workspace_name || "this search space"}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
@ -317,7 +313,7 @@ export default function InviteAcceptPage() {
<Users className="h-5 w-5 text-primary" />
</div>
<div>
<p className="font-medium">{inviteInfo?.search_space_name}</p>
<p className="font-medium">{inviteInfo?.workspace_name}</p>
<p className="text-sm text-muted-foreground">Search Space</p>
</div>
</div>

View file

@ -2,7 +2,7 @@ import { atom } from "jotai";
import { atomWithQuery } from "jotai-tanstack-query";
import { agentToolsApiService } from "@/lib/apis/agent-tools-api.service";
import { cacheKeys } from "@/lib/query-client/cache-keys";
import { activeSearchSpaceIdAtom } from "../search-spaces/search-space-query.atoms";
import { activeWorkspaceIdAtom } from "../workspaces/workspace-query.atoms";
export const agentToolsAtom = atomWithQuery((_get) => ({
queryKey: cacheKeys.agentTools.all(),
@ -42,7 +42,7 @@ const hydratedForAtom = atom<string | null>(null);
*/
export const disabledToolsAtom = atom(
(get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
const searchSpaceId = get(activeWorkspaceIdAtom);
const hydratedFor = get(hydratedForAtom);
if (searchSpaceId && hydratedFor !== searchSpaceId) {
return loadDisabledTools(searchSpaceId);
@ -50,7 +50,7 @@ export const disabledToolsAtom = atom(
return get(disabledToolsBaseAtom);
},
(get, set, update: string[] | ((prev: string[]) => string[])) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
const searchSpaceId = get(activeWorkspaceIdAtom);
const prev = get(disabledToolsBaseAtom);
const next = typeof update === "function" ? update(prev) : update;
set(disabledToolsBaseAtom, next);
@ -66,7 +66,7 @@ export const disabledToolsAtom = atom(
* Call this from a useEffect in a component that has access to the search space.
*/
export const hydrateDisabledToolsAtom = atom(null, (get, set) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
const searchSpaceId = get(activeWorkspaceIdAtom);
if (!searchSpaceId) return;
const stored = loadDisabledTools(searchSpaceId);
set(disabledToolsBaseAtom, stored);
@ -75,7 +75,7 @@ export const hydrateDisabledToolsAtom = atom(null, (get, set) => {
/** Toggle a single tool's enabled/disabled state */
export const toggleToolAtom = atom(null, (get, set, toolName: string) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
const searchSpaceId = get(activeWorkspaceIdAtom);
const current = get(disabledToolsBaseAtom);
const next = current.includes(toolName)
? current.filter((t) => t !== toolName)

View file

@ -49,10 +49,10 @@ export const createAutomationMutationAtom = atomWithMutation(() => ({
return automationsApiService.createAutomation(request);
},
onSuccess: (automation, variables) => {
invalidateList(variables.search_space_id);
invalidateList(variables.workspace_id);
toast.success("Automation created");
trackAutomationCreated({
search_space_id: variables.search_space_id,
workspace_id: variables.workspace_id,
automation_id: automation.id,
task_count: variables.definition.plan.length,
trigger_type: variables.triggers?.[0]?.type ?? "none",
@ -67,7 +67,7 @@ export const createAutomationMutationAtom = atomWithMutation(() => ({
console.error("Error creating automation:", error);
toast.error("Failed to create automation");
trackAutomationCreateFailed({
search_space_id: variables.search_space_id,
workspace_id: variables.workspace_id,
error: error.message,
});
},
@ -80,20 +80,20 @@ export const updateAutomationMutationAtom = atomWithMutation(() => ({
},
onSuccess: (automation, vars) => {
invalidateDetail(vars.automationId);
invalidateList(automation.search_space_id);
invalidateList(automation.workspace_id);
toast.success("Automation updated");
// A status-only patch (pause/resume/archive) is a distinct action from a
// definition/name edit, so split it into its own event.
if (vars.patch.status && !vars.patch.definition) {
trackAutomationStatusChanged({
automation_id: vars.automationId,
search_space_id: automation.search_space_id,
workspace_id: automation.workspace_id,
next_status: vars.patch.status,
});
} else {
trackAutomationUpdated({
automation_id: vars.automationId,
search_space_id: automation.search_space_id,
workspace_id: automation.workspace_id,
has_definition_change: !!vars.patch.definition,
has_name_change: vars.patch.name != null,
has_description_change: vars.patch.description !== undefined,
@ -123,7 +123,7 @@ export const deleteAutomationMutationAtom = atomWithMutation(() => ({
toast.success("Automation deleted");
trackAutomationDeleted({
automation_id: vars.automationId,
search_space_id: vars.searchSpaceId,
workspace_id: vars.searchSpaceId,
});
},
onError: (error: Error, vars) => {

View file

@ -1,5 +1,5 @@
import { atomWithQuery } from "jotai-tanstack-query";
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { activeWorkspaceIdAtom } from "@/atoms/workspaces/workspace-query.atoms";
import { automationsApiService } from "@/lib/apis/automations-api.service";
import { cacheKeys } from "@/lib/query-client/cache-keys";
@ -11,7 +11,7 @@ const DEFAULT_LIMIT = 50;
const DEFAULT_OFFSET = 0;
export const automationsListAtom = atomWithQuery((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
const searchSpaceId = get(activeWorkspaceIdAtom);
return {
queryKey: cacheKeys.automations.list(Number(searchSpaceId ?? 0), DEFAULT_LIMIT, DEFAULT_OFFSET),
@ -22,7 +22,7 @@ export const automationsListAtom = atomWithQuery((get) => {
return { items: [], total: 0 };
}
return automationsApiService.listAutomations({
search_space_id: Number(searchSpaceId),
workspace_id: Number(searchSpaceId),
limit: DEFAULT_LIMIT,
offset: DEFAULT_OFFSET,
});

View file

@ -10,10 +10,10 @@ import type {
import { connectorsApiService } from "@/lib/apis/connectors-api.service";
import { cacheKeys } from "@/lib/query-client/cache-keys";
import { queryClient } from "@/lib/query-client/client";
import { activeSearchSpaceIdAtom } from "../search-spaces/search-space-query.atoms";
import { activeWorkspaceIdAtom } from "../workspaces/workspace-query.atoms";
export const createConnectorMutationAtom = atomWithMutation((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
const searchSpaceId = get(activeWorkspaceIdAtom);
return {
mutationKey: cacheKeys.connectors.all(searchSpaceId ?? ""),
@ -32,7 +32,7 @@ export const createConnectorMutationAtom = atomWithMutation((get) => {
});
export const updateConnectorMutationAtom = atomWithMutation((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
const searchSpaceId = get(activeWorkspaceIdAtom);
return {
mutationKey: cacheKeys.connectors.all(searchSpaceId ?? ""),
@ -54,7 +54,7 @@ export const updateConnectorMutationAtom = atomWithMutation((get) => {
});
export const deleteConnectorMutationAtom = atomWithMutation((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
const searchSpaceId = get(activeWorkspaceIdAtom);
return {
mutationKey: cacheKeys.connectors.all(searchSpaceId ?? ""),
@ -80,7 +80,7 @@ export const deleteConnectorMutationAtom = atomWithMutation((get) => {
});
export const indexConnectorMutationAtom = atomWithMutation((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
const searchSpaceId = get(activeWorkspaceIdAtom);
return {
mutationKey: cacheKeys.connectors.index(),

View file

@ -1,10 +1,10 @@
import { atomWithQuery } from "jotai-tanstack-query";
import { connectorsApiService } from "@/lib/apis/connectors-api.service";
import { cacheKeys } from "@/lib/query-client/cache-keys";
import { activeSearchSpaceIdAtom } from "../search-spaces/search-space-query.atoms";
import { activeWorkspaceIdAtom } from "../workspaces/workspace-query.atoms";
export const connectorsAtom = atomWithQuery((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
const searchSpaceId = get(activeWorkspaceIdAtom);
return {
queryKey: cacheKeys.connectors.all(searchSpaceId!),
@ -13,7 +13,7 @@ export const connectorsAtom = atomWithQuery((get) => {
queryFn: async () => {
return connectorsApiService.getConnectors({
queryParams: {
search_space_id: searchSpaceId!,
workspace_id: searchSpaceId!,
},
});
},

View file

@ -1,6 +1,6 @@
import { atomWithMutation } from "jotai-tanstack-query";
import { toast } from "sonner";
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { activeWorkspaceIdAtom } from "@/atoms/workspaces/workspace-query.atoms";
import type {
CreateDocumentRequest,
DeleteDocumentRequest,
@ -14,7 +14,7 @@ import { queryClient } from "@/lib/query-client/client";
import { globalDocumentsQueryParamsAtom } from "./ui.atoms";
export const createDocumentMutationAtom = atomWithMutation((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
const searchSpaceId = get(activeWorkspaceIdAtom);
const documentsQueryParams = get(globalDocumentsQueryParamsAtom);
return {
@ -34,7 +34,7 @@ export const createDocumentMutationAtom = atomWithMutation((get) => {
});
export const uploadDocumentMutationAtom = atomWithMutation((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
const searchSpaceId = get(activeWorkspaceIdAtom);
const documentsQueryParams = get(globalDocumentsQueryParamsAtom);
return {
@ -54,7 +54,7 @@ export const uploadDocumentMutationAtom = atomWithMutation((get) => {
});
export const updateDocumentMutationAtom = atomWithMutation((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
const searchSpaceId = get(activeWorkspaceIdAtom);
const documentsQueryParams = get(globalDocumentsQueryParamsAtom);
return {
@ -77,7 +77,7 @@ export const updateDocumentMutationAtom = atomWithMutation((get) => {
});
export const deleteDocumentMutationAtom = atomWithMutation((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
const searchSpaceId = get(activeWorkspaceIdAtom);
const documentsQueryParams = get(globalDocumentsQueryParamsAtom);
return {

View file

@ -20,7 +20,7 @@ export const createInviteMutationAtom = atomWithMutation(() => ({
},
onSuccess: (_, variables) => {
queryClient.invalidateQueries({
queryKey: cacheKeys.invites.all(variables.search_space_id.toString()),
queryKey: cacheKeys.invites.all(variables.workspace_id.toString()),
});
toast.success("Invite created successfully");
},
@ -40,7 +40,7 @@ export const updateInviteMutationAtom = atomWithMutation(() => ({
},
onSuccess: (_, variables) => {
queryClient.invalidateQueries({
queryKey: cacheKeys.invites.all(variables.search_space_id.toString()),
queryKey: cacheKeys.invites.all(variables.workspace_id.toString()),
});
toast.success("Invite updated successfully");
},
@ -60,7 +60,7 @@ export const deleteInviteMutationAtom = atomWithMutation(() => ({
},
onSuccess: (_, variables) => {
queryClient.invalidateQueries({
queryKey: cacheKeys.invites.all(variables.search_space_id.toString()),
queryKey: cacheKeys.invites.all(variables.workspace_id.toString()),
});
toast.success("Invite deleted successfully");
},

View file

@ -1,10 +1,10 @@
import { atomWithQuery } from "jotai-tanstack-query";
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { activeWorkspaceIdAtom } from "@/atoms/workspaces/workspace-query.atoms";
import { invitesApiService } from "@/lib/apis/invites-api.service";
import { cacheKeys } from "@/lib/query-client/cache-keys";
export const invitesAtom = atomWithQuery((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
const searchSpaceId = get(activeWorkspaceIdAtom);
return {
queryKey: cacheKeys.invites.all(searchSpaceId?.toString() ?? ""),
@ -15,7 +15,7 @@ export const invitesAtom = atomWithQuery((get) => {
return [];
}
return invitesApiService.getInvites({
search_space_id: Number(searchSpaceId),
workspace_id: Number(searchSpaceId),
});
},
};

View file

@ -1,5 +1,5 @@
import { atomWithMutation } from "jotai-tanstack-query";
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { activeWorkspaceIdAtom } from "@/atoms/workspaces/workspace-query.atoms";
import type {
CreateLogRequest,
DeleteLogRequest,
@ -13,7 +13,7 @@ import { queryClient } from "@/lib/query-client/client";
* Create Log Mutation
*/
export const createLogMutationAtom = atomWithMutation((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
const searchSpaceId = get(activeWorkspaceIdAtom);
return {
mutationKey: cacheKeys.logs.list(searchSpaceId ?? undefined),
enabled: !!searchSpaceId,
@ -29,7 +29,7 @@ export const createLogMutationAtom = atomWithMutation((get) => {
* Update Log Mutation
*/
export const updateLogMutationAtom = atomWithMutation((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
const searchSpaceId = get(activeWorkspaceIdAtom);
return {
mutationKey: cacheKeys.logs.list(searchSpaceId ?? undefined),
enabled: !!searchSpaceId,
@ -45,7 +45,7 @@ export const updateLogMutationAtom = atomWithMutation((get) => {
* Delete Log Mutation
*/
export const deleteLogMutationAtom = atomWithMutation((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
const searchSpaceId = get(activeWorkspaceIdAtom);
return {
mutationKey: cacheKeys.logs.list(searchSpaceId ?? undefined),
enabled: !!searchSpaceId,

View file

@ -21,7 +21,7 @@ export const updateMemberMutationAtom = atomWithMutation(() => {
onSuccess: (_: UpdateMembershipResponse, request: UpdateMembershipRequest) => {
toast.success("Member updated successfully");
queryClient.invalidateQueries({
queryKey: cacheKeys.members.all(request.search_space_id.toString()),
queryKey: cacheKeys.members.all(request.workspace_id.toString()),
});
},
onError: () => {
@ -39,7 +39,7 @@ export const deleteMemberMutationAtom = atomWithMutation(() => {
onSuccess: (_: DeleteMembershipResponse, request: DeleteMembershipRequest) => {
toast.success("Member removed successfully");
queryClient.invalidateQueries({
queryKey: cacheKeys.members.all(request.search_space_id.toString()),
queryKey: cacheKeys.members.all(request.workspace_id.toString()),
});
},
onError: () => {
@ -57,7 +57,7 @@ export const leaveSearchSpaceMutationAtom = atomWithMutation(() => {
onSuccess: (_: LeaveSearchSpaceResponse, request: LeaveSearchSpaceRequest) => {
toast.success("Successfully left the search space");
queryClient.invalidateQueries({
queryKey: cacheKeys.members.all(request.search_space_id.toString()),
queryKey: cacheKeys.members.all(request.workspace_id.toString()),
});
},
onError: () => {

View file

@ -1,11 +1,11 @@
import { useAtomValue } from "jotai";
import { atomWithQuery } from "jotai-tanstack-query";
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { activeWorkspaceIdAtom } from "@/atoms/workspaces/workspace-query.atoms";
import { membersApiService } from "@/lib/apis/members-api.service";
import { cacheKeys } from "@/lib/query-client/cache-keys";
export const membersAtom = atomWithQuery((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
const searchSpaceId = get(activeWorkspaceIdAtom);
return {
queryKey: cacheKeys.members.all(searchSpaceId?.toString() ?? ""),
@ -17,14 +17,14 @@ export const membersAtom = atomWithQuery((get) => {
return [];
}
return membersApiService.getMembers({
search_space_id: Number(searchSpaceId),
workspace_id: Number(searchSpaceId),
});
},
};
});
export const myAccessAtom = atomWithQuery((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
const searchSpaceId = get(activeWorkspaceIdAtom);
return {
queryKey: cacheKeys.members.myAccess(searchSpaceId?.toString() ?? ""),
@ -35,7 +35,7 @@ export const myAccessAtom = atomWithQuery((get) => {
return null;
}
return membersApiService.getMyAccess({
search_space_id: Number(searchSpaceId),
workspace_id: Number(searchSpaceId),
});
},
};

View file

@ -16,7 +16,7 @@ import type {
import { modelConnectionsApiService } from "@/lib/apis/model-connections-api.service";
import { cacheKeys } from "@/lib/query-client/cache-keys";
import { queryClient } from "@/lib/query-client/client";
import { activeSearchSpaceIdAtom } from "../search-spaces/search-space-query.atoms";
import { activeWorkspaceIdAtom } from "../workspaces/workspace-query.atoms";
function invalidateModelConnections(searchSpaceId: number) {
queryClient.invalidateQueries({
@ -40,14 +40,14 @@ function upsertModelConnection(searchSpaceId: number, connection: ConnectionRead
}
export const createModelConnectionMutationAtom = atomWithMutation((get) => {
const searchSpaceId = Number(get(activeSearchSpaceIdAtom));
const searchSpaceId = Number(get(activeWorkspaceIdAtom));
return {
mutationKey: ["model-connections", "create"],
mutationFn: (request: ConnectionCreateRequest) =>
modelConnectionsApiService.createConnection(request),
onSuccess: (connection: ConnectionRead, request: ConnectionCreateRequest) => {
const resolvedSearchSpaceId = Number(
request.search_space_id ?? connection.search_space_id ?? searchSpaceId
request.workspace_id ?? connection.workspace_id ?? searchSpaceId
);
toast.success("Connection created");
if (resolvedSearchSpaceId > 0) {
@ -60,7 +60,7 @@ export const createModelConnectionMutationAtom = atomWithMutation((get) => {
});
export const updateModelConnectionMutationAtom = atomWithMutation((get) => {
const searchSpaceId = Number(get(activeSearchSpaceIdAtom));
const searchSpaceId = Number(get(activeWorkspaceIdAtom));
return {
mutationKey: ["model-connections", "update"],
mutationFn: ({ id, data }: { id: number; data: ConnectionUpdateRequest }) =>
@ -74,7 +74,7 @@ export const updateModelConnectionMutationAtom = atomWithMutation((get) => {
});
export const deleteModelConnectionMutationAtom = atomWithMutation((get) => {
const searchSpaceId = Number(get(activeSearchSpaceIdAtom));
const searchSpaceId = Number(get(activeWorkspaceIdAtom));
return {
mutationKey: ["model-connections", "delete"],
mutationFn: (id: number) => modelConnectionsApiService.deleteConnection(id),
@ -87,7 +87,7 @@ export const deleteModelConnectionMutationAtom = atomWithMutation((get) => {
});
export const verifyModelConnectionMutationAtom = atomWithMutation((get) => {
const searchSpaceId = Number(get(activeSearchSpaceIdAtom));
const searchSpaceId = Number(get(activeWorkspaceIdAtom));
return {
mutationKey: ["model-connections", "verify"],
mutationFn: (id: number) => modelConnectionsApiService.verifyConnection(id),
@ -110,7 +110,7 @@ export const verifyModelConnectionMutationAtom = atomWithMutation((get) => {
});
export const discoverConnectionModelsMutationAtom = atomWithMutation((get) => {
const searchSpaceId = Number(get(activeSearchSpaceIdAtom));
const searchSpaceId = Number(get(activeWorkspaceIdAtom));
return {
mutationKey: ["model-connections", "discover"],
mutationFn: (id: number) => modelConnectionsApiService.discoverModels(id),
@ -149,7 +149,7 @@ export const testPreviewModelMutationAtom = atomWithMutation(() => {
});
export const addManualModelMutationAtom = atomWithMutation((get) => {
const searchSpaceId = Number(get(activeSearchSpaceIdAtom));
const searchSpaceId = Number(get(activeWorkspaceIdAtom));
return {
mutationKey: ["models", "add-manual"],
mutationFn: ({ connectionId, data }: { connectionId: number; data: ModelCreateRequest }) =>
@ -163,7 +163,7 @@ export const addManualModelMutationAtom = atomWithMutation((get) => {
});
export const updateModelMutationAtom = atomWithMutation((get) => {
const searchSpaceId = Number(get(activeSearchSpaceIdAtom));
const searchSpaceId = Number(get(activeWorkspaceIdAtom));
return {
mutationKey: ["models", "update"],
mutationFn: ({ id, data }: { id: number; data: ModelUpdateRequest }) =>
@ -174,7 +174,7 @@ export const updateModelMutationAtom = atomWithMutation((get) => {
});
export const bulkUpdateModelsMutationAtom = atomWithMutation((get) => {
const searchSpaceId = Number(get(activeSearchSpaceIdAtom));
const searchSpaceId = Number(get(activeWorkspaceIdAtom));
return {
mutationKey: ["models", "bulk-update"],
mutationFn: ({ connectionId, data }: { connectionId: number; data: ModelsBulkUpdateRequest }) =>
@ -185,7 +185,7 @@ export const bulkUpdateModelsMutationAtom = atomWithMutation((get) => {
});
export const testModelMutationAtom = atomWithMutation((get) => {
const searchSpaceId = Number(get(activeSearchSpaceIdAtom));
const searchSpaceId = Number(get(activeWorkspaceIdAtom));
return {
mutationKey: ["models", "test"],
mutationFn: (id: number) => modelConnectionsApiService.testModel(id),
@ -199,7 +199,7 @@ export const testModelMutationAtom = atomWithMutation((get) => {
});
export const updateModelRolesMutationAtom = atomWithMutation((get) => {
const searchSpaceId = Number(get(activeSearchSpaceIdAtom));
const searchSpaceId = Number(get(activeWorkspaceIdAtom));
return {
mutationKey: ["model-roles", "update"],
mutationFn: (roles: ModelRoles) =>

View file

@ -2,7 +2,7 @@ import { atomWithQuery } from "jotai-tanstack-query";
import { modelConnectionsApiService } from "@/lib/apis/model-connections-api.service";
import { isAuthenticated } from "@/lib/auth-utils";
import { cacheKeys } from "@/lib/query-client/cache-keys";
import { activeSearchSpaceIdAtom } from "../search-spaces/search-space-query.atoms";
import { activeWorkspaceIdAtom } from "../workspaces/workspace-query.atoms";
export const globalModelConnectionsAtom = atomWithQuery(() => ({
queryKey: cacheKeys.modelConnections.global(),
@ -26,7 +26,7 @@ export const modelProvidersAtom = atomWithQuery(() => ({
}));
export const modelConnectionsAtom = atomWithQuery((get) => {
const searchSpaceId = Number(get(activeSearchSpaceIdAtom));
const searchSpaceId = Number(get(activeWorkspaceIdAtom));
return {
queryKey: cacheKeys.modelConnections.all(searchSpaceId),
enabled: !!searchSpaceId,
@ -36,7 +36,7 @@ export const modelConnectionsAtom = atomWithQuery((get) => {
});
export const modelRolesAtom = atomWithQuery((get) => {
const searchSpaceId = Number(get(activeSearchSpaceIdAtom));
const searchSpaceId = Number(get(activeWorkspaceIdAtom));
return {
queryKey: cacheKeys.modelConnections.roles(searchSpaceId),
enabled: !!searchSpaceId,

View file

@ -1,10 +1,10 @@
import { atomWithQuery } from "jotai-tanstack-query";
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { activeWorkspaceIdAtom } from "@/atoms/workspaces/workspace-query.atoms";
import { chatThreadsApiService } from "@/lib/apis/chat-threads-api.service";
import { cacheKeys } from "@/lib/query-client/cache-keys";
export const publicChatSnapshotsAtom = atomWithQuery((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
const searchSpaceId = get(activeWorkspaceIdAtom);
return {
queryKey: cacheKeys.publicChatSnapshots.bySearchSpace(Number(searchSpaceId) || 0),
@ -15,7 +15,7 @@ export const publicChatSnapshotsAtom = atomWithQuery((get) => {
return { snapshots: [] };
}
return chatThreadsApiService.listPublicChatSnapshotsForSearchSpace({
search_space_id: Number(searchSpaceId),
workspace_id: Number(searchSpaceId),
});
},
};

View file

@ -21,7 +21,7 @@ export const createRoleMutationAtom = atomWithMutation(() => {
onSuccess: (_: CreateRoleResponse, request: CreateRoleRequest) => {
toast.success("Role created successfully");
queryClient.invalidateQueries({
queryKey: cacheKeys.roles.all(request.search_space_id.toString()),
queryKey: cacheKeys.roles.all(request.workspace_id.toString()),
});
},
onError: () => {
@ -39,13 +39,10 @@ export const updateRoleMutationAtom = atomWithMutation(() => {
onSuccess: (_: UpdateRoleResponse, request: UpdateRoleRequest) => {
toast.success("Role updated successfully");
queryClient.invalidateQueries({
queryKey: cacheKeys.roles.all(request.search_space_id.toString()),
queryKey: cacheKeys.roles.all(request.workspace_id.toString()),
});
queryClient.invalidateQueries({
queryKey: cacheKeys.roles.byId(
request.search_space_id.toString(),
request.role_id.toString()
),
queryKey: cacheKeys.roles.byId(request.workspace_id.toString(), request.role_id.toString()),
});
},
onError: () => {
@ -63,7 +60,7 @@ export const deleteRoleMutationAtom = atomWithMutation(() => {
onSuccess: (_: DeleteRoleResponse, request: DeleteRoleRequest) => {
toast.success("Role deleted successfully");
queryClient.invalidateQueries({
queryKey: cacheKeys.roles.all(request.search_space_id.toString()),
queryKey: cacheKeys.roles.all(request.workspace_id.toString()),
});
},
onError: () => {

View file

@ -5,11 +5,11 @@ import type {
DeleteSearchSpaceRequest,
UpdateSearchSpaceApiAccessRequest,
UpdateSearchSpaceRequest,
} from "@/contracts/types/search-space.types";
import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service";
} from "@/contracts/types/workspace.types";
import { searchSpacesApiService } from "@/lib/apis/workspaces-api.service";
import { cacheKeys } from "@/lib/query-client/cache-keys";
import { queryClient } from "@/lib/query-client/client";
import { activeSearchSpaceIdAtom } from "./search-space-query.atoms";
import { activeWorkspaceIdAtom } from "./workspace-query.atoms";
export const createSearchSpaceMutationAtom = atomWithMutation(() => {
return {
@ -28,7 +28,7 @@ export const createSearchSpaceMutationAtom = atomWithMutation(() => {
});
export const updateSearchSpaceMutationAtom = atomWithMutation((get) => {
const activeSearchSpaceId = get(activeSearchSpaceIdAtom);
const activeSearchSpaceId = get(activeWorkspaceIdAtom);
return {
mutationKey: ["update-search-space", activeSearchSpaceId],
@ -52,7 +52,7 @@ export const updateSearchSpaceMutationAtom = atomWithMutation((get) => {
});
export const updateSearchSpaceApiAccessMutationAtom = atomWithMutation((get) => {
const activeSearchSpaceId = get(activeSearchSpaceIdAtom);
const activeSearchSpaceId = get(activeWorkspaceIdAtom);
return {
mutationKey: ["update-search-space-api-access", activeSearchSpaceId],
@ -74,7 +74,7 @@ export const updateSearchSpaceApiAccessMutationAtom = atomWithMutation((get) =>
});
export const deleteSearchSpaceMutationAtom = atomWithMutation((get) => {
const activeSearchSpaceId = get(activeSearchSpaceIdAtom);
const activeSearchSpaceId = get(activeWorkspaceIdAtom);
return {
mutationKey: ["delete-search-space", activeSearchSpaceId],

View file

@ -1,11 +1,10 @@
import { atom } from "jotai";
import { atomWithQuery } from "jotai-tanstack-query";
import type { GetSearchSpacesRequest } from "@/contracts/types/search-space.types";
import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service";
import type { GetSearchSpacesRequest } from "@/contracts/types/workspace.types";
import { searchSpacesApiService } from "@/lib/apis/workspaces-api.service";
import { cacheKeys } from "@/lib/query-client/cache-keys";
export const activeSearchSpaceIdAtom = atom<string | null>(null);
export const activeWorkspaceIdAtom = activeSearchSpaceIdAtom;
export const activeWorkspaceIdAtom = atom<string | null>(null);
export const searchSpacesQueryParamsAtom = atom<GetSearchSpacesRequest["queryParams"]>({
skip: 0,

View file

@ -0,0 +1,33 @@
---
date: "2026-07-05"
---
## SurfSense's Next Chapter: Competitive Intelligence for AI Agents
Happy Independence Day to everyone celebrating in the United States! We hope you had a wonderful Fourth of July. It felt like the right weekend for a declaration of our own.
Today we are pointing SurfSense at a single mission: **give your AI agents competitive intelligence.**
### How we got here
For the past couple of months we set out to build the best general research agent for your own knowledge. That chapter treated us well. As the open-source alternative to NotebookLM, SurfSense earned a community we are genuinely proud of: more than 15,000 GitHub stars, self-hosted deployments everywhere from homelabs to enterprises, and users who pushed the product further than we imagined.
That same period settled a bigger question. Agentic tools like Claude, OpenCode, Hermes, and OpenClaw have proven themselves. Agents that plan, use tools, and finish multi-step work on their own are no longer a demo, they are how work gets done. General research assistance is becoming something every capable agent does out of the box.
So we asked what these agents still lack. Not reasoning. Not tools. **Live market data and the workflows around it.** An agent asked "what are competitors charging this week?" or "what is Reddit saying about us since the launch?" has nowhere trustworthy to look. We want to be one of the first, and hopefully the definitive, open-source competitive intelligence agent platforms. This focus also lets our team pour all of our energy into the high-value workflows that genuinely need AI automation.
### What SurfSense is now
- **Platform-native connectors for live market data.** [Reddit](/reddit), [YouTube](/youtube), [Google Maps](/google-maps), [Google Search](/google-search), and the [open web](/web-crawl). Each one is a typed REST endpoint you can call from any language with your SurfSense API key.
- **An MCP server that makes every connector an agent tool.** Add SurfSense to Claude, Cursor, or your own agent framework. And with the [MCP connector](/mcp-connector), your SurfSense agents can use any MCP server too, with one-click OAuth for apps like Notion, Slack, and Jira.
- **An agent harness, not just raw data.** Retries, structured output, and credit metering are built in, so agents go from a question to a brief without you building the plumbing.
- **Automations for the workflows that matter.** Scheduled and event-triggered agents watch competitor pricing, track brand mentions, monitor rankings and reviews, then deliver briefs and alerts.
- **Simpler pricing.** Self-hosting stays free with no metering. Cloud is pay as you go: start with $5 of free credit, connectors bill per item returned, crawls per page fetched, models at provider cost. Failed calls are never billed. Details on the [pricing page](/pricing).
### To our existing users: nothing you rely on is going away
This is an addition, not a replacement. Your knowledge base, chat with citations, reports, podcasts, presentations, automations, and collaborative chats all keep working exactly as they do today. You can still upload files and sync Google Drive, OneDrive, and Dropbox. Self-hosting stays free and open source, and your existing deployments keep running without any action from you. What changes is where we invest: expect deeper connectors, sharper agent workflows, and broader market coverage.
If you are building agents, [grab an API key](/login) or add the MCP server, and start with the [connector catalog](/connectors). Tell us what you are building on [Discord](https://discord.gg/ejRNvftDp9) or [GitHub](https://github.com/MODSetter/SurfSense).
We think the next great competitive intelligence tool will not be a dashboard. It will be an agent that already knows your market. We are building the platform it runs on.

View file

@ -29,7 +29,7 @@ import {
globalModelConnectionsAtom,
modelConnectionsAtom,
} from "@/atoms/model-connections/model-connections-query.atoms";
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { activeWorkspaceIdAtom } from "@/atoms/workspaces/workspace-query.atoms";
import {
CitationMetadataProvider,
useAllCitationMetadata,
@ -491,7 +491,7 @@ export const AssistantMessage: FC = () => {
const commentPanelRef = useRef<HTMLDivElement>(null);
const commentTriggerRef = useRef<HTMLButtonElement>(null);
const messageId = useAuiState(({ message }) => message?.id);
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
const searchSpaceId = useAtomValue(activeWorkspaceIdAtom);
const dbMessageId = parseMessageId(messageId);
const commentsEnabled = useAtomValue(commentsEnabledAtom);

View file

@ -4,7 +4,7 @@ import { useAtomValue } from "jotai";
import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from "react";
import { createPortal } from "react-dom";
import { statusInboxItemsAtom } from "@/atoms/inbox/status-inbox.atom";
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { activeWorkspaceIdAtom } from "@/atoms/workspaces/workspace-query.atoms";
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
import { Tabs, TabsContent } from "@/components/ui/tabs";
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
@ -36,7 +36,7 @@ interface ConnectorIndicatorProps {
export const ConnectorIndicator = forwardRef<ConnectorIndicatorHandle, ConnectorIndicatorProps>(
(_props, ref) => {
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
const searchSpaceId = useAtomValue(activeWorkspaceIdAtom);
// Real-time document type counts via Zero (updates instantly as docs are indexed)
const documentTypeCounts = useZeroDocumentTypeCounts(searchSpaceId);

View file

@ -18,7 +18,7 @@ export interface CirclebackConfigProps extends ConnectorConfigProps {
// Type-safe schema for webhook info response
const circlebackWebhookInfoSchema = z.object({
webhook_url: z.string(),
search_space_id: z.number(),
workspace_id: z.number(),
method: z.string(),
content_type: z.string(),
description: z.string(),
@ -40,12 +40,12 @@ export const CirclebackConfig: FC<CirclebackConfigProps> = ({ connector, onNameC
const controller = new AbortController();
const doFetch = async () => {
if (!connector.search_space_id) return;
if (!connector.workspace_id) return;
setIsLoading(true);
try {
const response = await authenticatedFetch(
buildBackendUrl(`/api/v1/webhooks/circleback/${connector.search_space_id}/info`),
buildBackendUrl(`/api/v1/webhooks/circleback/${connector.workspace_id}/info`),
{ signal: controller.signal }
);
if (controller.signal.aborted) return;
@ -70,7 +70,7 @@ export const CirclebackConfig: FC<CirclebackConfigProps> = ({ connector, onNameC
doFetch().catch(() => {});
return () => controller.abort();
}, [connector.search_space_id]);
}, [connector.workspace_id]);
const handleNameChange = (value: string) => {
setName(value);

View file

@ -4,7 +4,7 @@ import { useAtomValue } from "jotai";
import { ArrowLeft, Info, RefreshCw } from "lucide-react";
import { type FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { toast } from "sonner";
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { activeWorkspaceIdAtom } from "@/atoms/workspaces/workspace-query.atoms";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
@ -78,7 +78,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
onConfigChange,
onNameChange,
}) => {
const searchSpaceIdAtom = useAtomValue(activeSearchSpaceIdAtom);
const searchSpaceIdAtom = useAtomValue(activeWorkspaceIdAtom);
const isAuthExpired = connector.config?.auth_expired === true;
const reauthEndpoint = getReauthEndpoint(connector);
const [reauthing, setReauthing] = useState(false);

View file

@ -10,7 +10,7 @@ import {
updateConnectorMutationAtom,
} from "@/atoms/connectors/connector-mutation.atoms";
import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms";
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { activeWorkspaceIdAtom } from "@/atoms/workspaces/workspace-query.atoms";
import { EnumConnectorName } from "@/contracts/enums/connector";
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
import { searchSourceConnector } from "@/contracts/types/connector.types";
@ -58,7 +58,7 @@ function clearOAuthResultCookie(): void {
}
export const useConnectorDialog = () => {
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
const searchSpaceId = useAtomValue(activeWorkspaceIdAtom);
const { data: allConnectors, refetch: refetchAllConnectors } = useAtomValue(connectorsAtom);
const { mutateAsync: indexConnector } = useAtomValue(indexConnectorMutationAtom);
const { mutateAsync: updateConnector } = useAtomValue(updateConnectorMutationAtom);
@ -165,7 +165,7 @@ export const useConnectorDialog = () => {
await indexConnector({
connector_id: connector.id,
queryParams: {
search_space_id: searchSpaceId,
workspace_id: searchSpaceId,
start_date: format(startDate, "yyyy-MM-dd"),
end_date: format(endDate, "yyyy-MM-dd"),
},
@ -418,7 +418,7 @@ export const useConnectorDialog = () => {
enable_vision_llm: false,
},
queryParams: {
search_space_id: searchSpaceId,
workspace_id: searchSpaceId,
},
});
@ -519,7 +519,7 @@ export const useConnectorDialog = () => {
enable_vision_llm: false,
},
queryParams: {
search_space_id: searchSpaceId,
workspace_id: searchSpaceId,
},
});
// Refetch connectors to get the new one
@ -612,7 +612,7 @@ export const useConnectorDialog = () => {
await indexConnector({
connector_id: connector.id,
queryParams: {
search_space_id: searchSpaceId,
workspace_id: searchSpaceId,
start_date: startDateStr,
end_date: endDateStr,
},
@ -847,7 +847,7 @@ export const useConnectorDialog = () => {
await indexConnector({
connector_id: indexingConfig.connectorId,
queryParams: {
search_space_id: searchSpaceId,
workspace_id: searchSpaceId,
},
body: {
folders: selectedFolders || [],
@ -870,14 +870,14 @@ export const useConnectorDialog = () => {
await indexConnector({
connector_id: indexingConfig.connectorId,
queryParams: {
search_space_id: searchSpaceId,
workspace_id: searchSpaceId,
},
});
} else {
await indexConnector({
connector_id: indexingConfig.connectorId,
queryParams: {
search_space_id: searchSpaceId,
workspace_id: searchSpaceId,
start_date: startDateStr,
end_date: endDateStr,
},
@ -1114,7 +1114,7 @@ export const useConnectorDialog = () => {
await indexConnector({
connector_id: editingConnector.id,
queryParams: {
search_space_id: searchSpaceId,
workspace_id: searchSpaceId,
},
body: {
folders: selectedFolders || [],
@ -1134,7 +1134,7 @@ export const useConnectorDialog = () => {
await indexConnector({
connector_id: editingConnector.id,
queryParams: {
search_space_id: searchSpaceId,
workspace_id: searchSpaceId,
},
});
indexingDescription = "Re-indexing started with updated configuration.";
@ -1143,7 +1143,7 @@ export const useConnectorDialog = () => {
await indexConnector({
connector_id: editingConnector.id,
queryParams: {
search_space_id: searchSpaceId,
workspace_id: searchSpaceId,
start_date: startDateStr,
end_date: endDateStr,
},
@ -1296,7 +1296,7 @@ export const useConnectorDialog = () => {
await indexConnector({
connector_id: connectorId,
queryParams: {
search_space_id: searchSpaceId,
workspace_id: searchSpaceId,
start_date: startDateStr,
end_date: endDateStr,
},

View file

@ -4,7 +4,7 @@ import { useAtomValue } from "jotai";
import { ArrowLeft, Plus, RefreshCw, Server } from "lucide-react";
import { type FC, useCallback, useState } from "react";
import { toast } from "sonner";
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { activeWorkspaceIdAtom } from "@/atoms/workspaces/workspace-query.atoms";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { EnumConnectorName } from "@/contracts/enums/connector";
@ -44,7 +44,7 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
isConnecting = false,
addButtonText,
}) => {
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
const searchSpaceId = useAtomValue(activeWorkspaceIdAtom);
const [reauthingId, setReauthingId] = useState<number | null>(null);
const [confirmDisconnectId, setConfirmDisconnectId] = useState<number | null>(null);
const [disconnectingId, setDisconnectingId] = useState<number | null>(null);

View file

@ -165,7 +165,7 @@ export const YouTubeCrawlerView: FC<YouTubeCrawlerViewProps> = ({ searchSpaceId,
{
document_type: "YOUTUBE_VIDEO",
content: videoUrls,
search_space_id: parseInt(searchSpaceId, 10),
workspace_id: parseInt(searchSpaceId, 10),
},
{
onSuccess: () => {

View file

@ -10,7 +10,7 @@ import {
useRef,
useState,
} from "react";
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { activeWorkspaceIdAtom } from "@/atoms/workspaces/workspace-query.atoms";
import { DocumentUploadTab } from "@/components/sources/DocumentUploadTab";
import {
Dialog,
@ -90,7 +90,7 @@ const DocumentUploadPopupContent: FC<{
isOpen: boolean;
onOpenChange: (open: boolean) => void;
}> = ({ isOpen, onOpenChange }) => {
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
const searchSpaceId = useAtomValue(activeWorkspaceIdAtom);
if (!searchSpaceId) return null;

View file

@ -223,7 +223,7 @@ function FilePathLink({ path, className }: { path: string; className?: string })
if (!resolvedSearchSpaceId || !path.startsWith("/documents/")) return;
try {
const doc = await documentsApiService.getDocumentByVirtualPath({
search_space_id: resolvedSearchSpaceId,
workspace_id: resolvedSearchSpaceId,
virtual_path: path,
});
openEditorPanel({

View file

@ -77,7 +77,7 @@ export const CitationPanelContent: FC<CitationPanelContentProps> = ({
if (!data) return;
openEditorPanel({
documentId: data.id,
searchSpaceId: data.search_space_id,
searchSpaceId: data.workspace_id,
title: data.title,
});
};

View file

@ -6,7 +6,7 @@ import { BreadcrumbNav } from "@/components/seo/breadcrumb-nav";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import type { ConnectorPageContent } from "@/lib/connectors-marketing/types";
import type { ConnectorPageContent, SchemaField } from "@/lib/connectors-marketing/types";
import { AgentTranscript } from "./agent-transcript";
import { ApiMcpTabs } from "./api-mcp-tabs";
import { ConnectorFaq } from "./connector-faq";
@ -14,6 +14,46 @@ import { Reveal } from "./reveal";
const GITHUB_URL = "https://github.com/MODSetter/SurfSense";
function SchemaTable({ caption, fields }: { caption: string; fields: SchemaField[] }) {
return (
<div className="overflow-x-auto rounded-xl border bg-card">
<table className="w-full min-w-xl text-sm">
<caption className="sr-only">{caption}</caption>
<thead>
<tr className="border-b bg-muted/40 text-left">
<th className="p-4 font-medium">Field</th>
<th className="p-4 font-medium">Type</th>
<th className="p-4 font-medium">Description</th>
</tr>
</thead>
<tbody>
{fields.map((field) => (
<tr key={field.name} className="border-b align-top last:border-b-0">
<th scope="row" className="p-4 text-left">
<code className="font-mono text-[13px] font-semibold">{field.name}</code>
</th>
<td className="whitespace-nowrap p-4">
<code className="font-mono text-[13px] text-muted-foreground">{field.type}</code>
{field.required ? (
<span className="ml-2 rounded-full bg-brand/10 px-2 py-0.5 text-xs font-medium text-brand">
required
</span>
) : null}
{field.defaultValue !== undefined ? (
<div className="mt-1 text-xs text-muted-foreground">
default <code className="font-mono">{field.defaultValue}</code>
</div>
) : null}
</td>
<td className="p-4 text-muted-foreground leading-relaxed">{field.description}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
export function ConnectorPage({ content }: { content: ConnectorPageContent }) {
const Icon = content.icon;
const label = content.cardTitle ?? `${content.name} API`;
@ -136,6 +176,44 @@ export function ConnectorPage({ content }: { content: ConnectorPageContent }) {
</Reveal>
</MarketingSection>
{/* Request / response schema */}
<MarketingSection>
<Reveal>
<h2 className="text-2xl font-bold tracking-tight sm:text-3xl">
{content.name} API request and response schema
</h2>
<p className="mt-3 max-w-2xl text-muted-foreground leading-relaxed">
The exact contract behind{" "}
<code className="rounded bg-muted px-1.5 py-0.5 font-mono text-sm">
POST /workspaces/{"{workspace_id}"}/scrapers/{content.api.platform}/{content.api.verb}
</code>
. The same fields power the{" "}
<code className="rounded bg-muted px-1.5 py-0.5 font-mono text-sm">
{content.api.mcpTool}
</code>{" "}
MCP tool.
</p>
</Reveal>
<Reveal>
<h3 className="mt-8 text-lg font-semibold">Request parameters</h3>
<p className="mt-2 max-w-2xl text-sm text-muted-foreground leading-relaxed">
{content.schema.requestNote}
</p>
<div className="mt-4">
<SchemaTable caption="Request parameters" fields={content.schema.request} />
</div>
</Reveal>
<Reveal>
<h3 className="mt-10 text-lg font-semibold">Response fields</h3>
<p className="mt-2 max-w-2xl text-sm text-muted-foreground leading-relaxed">
{content.schema.responseNote}
</p>
<div className="mt-4">
<SchemaTable caption="Response fields" fields={content.schema.response} />
</div>
</Reveal>
</MarketingSection>
{/* Comparison */}
<MarketingSection>
<Reveal>

View file

@ -276,7 +276,7 @@ export function EditorPanelContent({
}
const response = await authenticatedFetch(
buildBackendUrl(
`/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/editor-content`
`/api/v1/workspaces/${searchSpaceId}/documents/${documentId}/editor-content`
),
{ method: "GET" }
);
@ -412,7 +412,7 @@ export function EditorPanelContent({
throw new Error("Missing document context");
}
const response = await authenticatedFetch(
buildBackendUrl(`/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/save`),
buildBackendUrl(`/api/v1/workspaces/${searchSpaceId}/documents/${documentId}/save`),
{
method: "POST",
headers: { "Content-Type": "application/json" },
@ -520,7 +520,7 @@ export function EditorPanelContent({
try {
const response = await authenticatedFetch(
buildBackendUrl(
`/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/download-markdown`
`/api/v1/workspaces/${searchSpaceId}/documents/${documentId}/download-markdown`
),
{ method: "GET" }
);

View file

@ -1,4 +1,4 @@
import { IconBrandDiscord, IconBrandGithub } from "@tabler/icons-react";
import { IconBrandDiscord, IconBrandGithub, IconBrandReddit } from "@tabler/icons-react";
import { ArrowRight } from "lucide-react";
import Link from "next/link";
import { Reveal } from "@/components/connectors-marketing/reveal";
@ -7,6 +7,7 @@ import { Button } from "@/components/ui/button";
const GITHUB_URL = "https://github.com/MODSetter/SurfSense";
const DISCORD_URL = "https://discord.gg/ejRNvftDp9";
const REDDIT_URL = "https://www.reddit.com/r/SurfSense/";
/** Closing CTA doubling as the GitHub/community strip (brief section 7). */
export function CommunityStrip() {
@ -40,6 +41,12 @@ export function CommunityStrip() {
Join Discord
</Link>
</Button>
<Button asChild variant="ghost" size="lg">
<Link href={REDDIT_URL} target="_blank" rel="noopener noreferrer">
<IconBrandReddit className="size-4" />
r/SurfSense
</Link>
</Button>
</div>
</div>
</Reveal>

View file

@ -2,6 +2,7 @@ import {
IconBrandDiscord,
IconBrandGithub,
IconBrandLinkedin,
IconBrandReddit,
IconBrandTwitter,
} from "@tabler/icons-react";
import Link from "next/link";
@ -68,6 +69,11 @@ export function FooterNew() {
href: "https://discord.gg/ejRNvftDp9",
icon: IconBrandDiscord,
},
{
title: "Reddit",
href: "https://www.reddit.com/r/SurfSense/",
icon: IconBrandReddit,
},
];
const legals = [
{

View file

@ -12,10 +12,11 @@ import { currentThreadAtom, resetCurrentThreadAtom } from "@/atoms/chat/current-
import { documentsSidebarOpenAtom } from "@/atoms/documents/ui.atoms";
import { statusInboxItemsAtom } from "@/atoms/inbox/status-inbox.atom";
import { announcementsDialogAtom } from "@/atoms/layout/dialogs.atom";
import { deleteSearchSpaceMutationAtom } from "@/atoms/search-spaces/search-space-mutation.atoms";
import { searchSpacesAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { rightPanelCollapsedAtom } from "@/atoms/layout/right-panel.atom";
import { removeChatTabAtom, syncChatTabAtom, type Tab } from "@/atoms/tabs/tabs.atom";
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
import { deleteSearchSpaceMutationAtom } from "@/atoms/workspaces/workspace-mutation.atoms";
import { searchSpacesAtom } from "@/atoms/workspaces/workspace-query.atoms";
import { ActionLogDialog } from "@/components/agent-action-log/action-log-dialog";
import { AnnouncementSpotlight } from "@/components/announcements/AnnouncementSpotlight";
import { AnnouncementsDialog } from "@/components/announcements/AnnouncementsDialog";
@ -46,7 +47,7 @@ import { useInbox } from "@/hooks/use-inbox";
import { useIsMobile } from "@/hooks/use-mobile";
import { useArchiveThread, useDeleteThread, useRenameThread } from "@/hooks/use-thread-mutations";
import { notificationsApiService } from "@/lib/apis/notifications-api.service";
import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service";
import { searchSpacesApiService } from "@/lib/apis/workspaces-api.service";
import { getLoginPath, logout } from "@/lib/auth-utils";
import { fetchThreads } from "@/lib/chat/thread-persistence";
import { resetUser, trackLogout } from "@/lib/posthog/events";
@ -406,7 +407,7 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
const handleSearchSpaceSettings = useCallback(
(space: SearchSpace) => {
router.push(`/dashboard/${space.id}/search-space-settings/general`);
router.push(`/dashboard/${space.id}/workspace-settings`);
},
[router]
);
@ -594,7 +595,7 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
);
const handleSettings = useCallback(() => {
router.push(`/dashboard/${searchSpaceId}/search-space-settings/general`);
router.push(`/dashboard/${searchSpaceId}/workspace-settings`);
}, [router, searchSpaceId]);
const handleManageMembers = useCallback(() => {
@ -694,7 +695,7 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
// Detect if we're on the chat page (needs overflow-hidden for chat's own scroll)
const isChatPage = pathname?.includes("/new-chat") ?? false;
const isUserSettingsPage = pathname?.includes("/user-settings") === true;
const isSearchSpaceSettingsPage = pathname?.includes("/search-space-settings") === true;
const isSearchSpaceSettingsPage = pathname?.includes("/workspace-settings") === true;
const isTeamPage = pathname?.endsWith("/team") === true;
const isAutomationsPage = pathname?.includes("/automations") === true;
const isAllChatsPage = pathname?.endsWith("/chats") === true;

View file

@ -7,7 +7,7 @@ import { useTranslations } from "next-intl";
import { useState } from "react";
import { useForm } from "react-hook-form";
import * as z from "zod";
import { createSearchSpaceMutationAtom } from "@/atoms/search-spaces/search-space-mutation.atoms";
import { createSearchSpaceMutationAtom } from "@/atoms/workspaces/workspace-mutation.atoms";
import { Button } from "@/components/ui/button";
import {
Dialog,

View file

@ -1 +1 @@
export { CreateSearchSpaceDialog } from "./CreateSearchSpaceDialog";
export { CreateSearchSpaceDialog } from "./CreateWorkspaceDialog";

View file

@ -3,8 +3,8 @@
import { useAtomValue } from "jotai";
import { usePathname } from "next/navigation";
import { currentThreadAtom } from "@/atoms/chat/current-thread.atom";
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { activeTabAtom } from "@/atoms/tabs/tabs.atom";
import { activeWorkspaceIdAtom } from "@/atoms/workspaces/workspace-query.atoms";
import { ActionLogButton } from "@/components/agent-action-log/action-log-button";
import { ChatShareButton } from "@/components/new-chat/chat-share-button";
import { ArtifactsToggleButton } from "@/features/chat-artifacts";
@ -16,7 +16,7 @@ interface HeaderProps {
export function Header({ mobileMenuTrigger }: HeaderProps) {
const pathname = usePathname();
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
const searchSpaceId = useAtomValue(activeWorkspaceIdAtom);
const activeTab = useAtomValue(activeTabAtom);
const isFreePage = pathname?.startsWith("/free") ?? false;
@ -56,7 +56,7 @@ export function Header({ mobileMenuTrigger }: HeaderProps) {
id: currentThreadState.id,
visibility: currentThreadState.visibility,
created_by_id: null,
search_space_id: currentThreadState.searchSpaceId,
workspace_id: currentThreadState.searchSpaceId,
title: "",
archived: false,
created_at: "",

View file

@ -7,7 +7,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip
import { cn } from "@/lib/utils";
import type { NavItem, SearchSpace, User } from "../../types/layout.types";
import { SidebarUserProfile } from "../sidebar/SidebarUserProfile";
import { SearchSpaceAvatar } from "./SearchSpaceAvatar";
import { SearchSpaceAvatar } from "./WorkspaceAvatar";
interface IconRailProps {
searchSpaces: SearchSpace[];

View file

@ -1,3 +1,3 @@
export { IconRail } from "./IconRail";
export { NavIcon } from "./NavIcon";
export { SearchSpaceAvatar } from "./SearchSpaceAvatar";
export { SearchSpaceAvatar } from "./WorkspaceAvatar";

View file

@ -8,10 +8,16 @@ import { useTranslations } from "next-intl";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { toast } from "sonner";
import { makeFolderMention, mentionedDocumentsAtom } from "@/atoms/chat/mentioned-documents.atom";
import { connectorDialogOpenAtom } from "@/atoms/connector-dialog/connector-dialog.atoms";
import { deleteDocumentMutationAtom } from "@/atoms/documents/document-mutation.atoms";
import { expandedFolderIdsAtom } from "@/atoms/documents/folder.atoms";
import { agentCreatedDocumentsAtom } from "@/atoms/documents/ui.atoms";
import { openEditorPanelAtom } from "@/atoms/editor/editor-panel.atom";
import {
folderWatchDialogOpenAtom,
folderWatchInitialFolderAtom,
} from "@/atoms/folder-sync/folder-sync.atoms";
import { searchSpacesAtom } from "@/atoms/workspaces/workspace-query.atoms";
import { CreateFolderDialog } from "@/components/documents/CreateFolderDialog";
import type { DocumentNodeDoc } from "@/components/documents/DocumentNode";
import { getDocumentTypeIcon } from "@/components/documents/DocumentTypeIcon";
@ -51,6 +57,7 @@ import type { DocumentTypeEnum } from "@/contracts/types/document.types";
import { useElectronAPI, usePlatform } from "@/hooks/use-platform";
import { documentsApiService } from "@/lib/apis/documents-api.service";
import { foldersApiService } from "@/lib/apis/folders-api.service";
import { searchSpacesApiService } from "@/lib/apis/workspaces-api.service";
import { authenticatedFetch } from "@/lib/auth-fetch";
import { getMentionDocKey } from "@/lib/chat/mention-doc-key";
import { getDocumentTypeLabel } from "@/lib/documents/document-type-labels";
@ -234,7 +241,8 @@ function AuthenticatedDocumentsSidebarBase({
const platformElectronAPI = useElectronAPI();
const electronAPI = desktopFeaturesEnabled ? platformElectronAPI : null;
const { etlService } = useRuntimeConfig();
const searchSpaceId = getWorkspaceIdNumber(params) ?? 0;
const workspaceId = getWorkspaceIdNumber(params) ?? 0;
const setConnectorDialogOpen = useSetAtom(connectorDialogOpenAtom);
const openEditorPanel = useSetAtom(openEditorPanelAtom);
const [activeTypes, setActiveTypes] = useState<DocumentTypeEnum[]>([]);
@ -248,7 +256,7 @@ function AuthenticatedDocumentsSidebarBase({
if (folders.length === 0) {
try {
const backendFolders = await documentsApiService.getWatchedFolders(searchSpaceId);
const backendFolders = await documentsApiService.getWatchedFolders(workspaceId);
for (const bf of backendFolders) {
const meta = bf.metadata as Record<string, unknown> | null;
if (!meta?.watched || !meta.folder_path) continue;
@ -256,7 +264,7 @@ function AuthenticatedDocumentsSidebarBase({
path: meta.folder_path as string,
name: bf.name,
rootFolderId: bf.id,
searchSpaceId: bf.search_space_id,
searchSpaceId: bf.workspace_id,
excludePatterns: (meta.exclude_patterns as string[]) ?? [],
fileExtensions: (meta.file_extensions as string[] | null) ?? null,
active: true,
@ -281,7 +289,7 @@ function AuthenticatedDocumentsSidebarBase({
.map((f: WatchedFolderEntry) => f.rootFolderId as number)
);
setWatchedFolderIds(ids);
}, [searchSpaceId, electronAPI]);
}, [workspaceId, electronAPI]);
useEffect(() => {
refreshWatchedIds();
@ -297,24 +305,28 @@ function AuthenticatedDocumentsSidebarBase({
// Folder state
const [expandedFolderMap, setExpandedFolderMap] = useAtom(expandedFolderIdsAtom);
const expandedIds = useMemo(
() => new Set(expandedFolderMap[searchSpaceId] ?? []),
[expandedFolderMap, searchSpaceId]
() => new Set(expandedFolderMap[workspaceId] ?? []),
[expandedFolderMap, workspaceId]
);
const toggleFolderExpand = useCallback(
(folderId: number) => {
setExpandedFolderMap((prev) => {
const current = new Set(prev[searchSpaceId] ?? []);
const current = new Set(prev[workspaceId] ?? []);
if (current.has(folderId)) current.delete(folderId);
else current.add(folderId);
return { ...prev, [searchSpaceId]: [...current] };
return { ...prev, [workspaceId]: [...current] };
});
},
[searchSpaceId, setExpandedFolderMap]
[workspaceId, setExpandedFolderMap]
);
// Zero queries for tree data
const [zeroFolders, zeroFoldersResult] = useQuery(queries.folders.bySpace({ searchSpaceId }));
const [zeroAllDocs, zeroAllDocsResult] = useQuery(queries.documents.bySpace({ searchSpaceId }));
const [zeroFolders, zeroFoldersResult] = useQuery(
queries.folders.bySpace({ searchSpaceId: workspaceId })
);
const [zeroAllDocs, zeroAllDocsResult] = useQuery(
queries.documents.bySpace({ searchSpaceId: workspaceId })
);
const [agentCreatedDocs, setAgentCreatedDocs] = useAtom(agentCreatedDocumentsAtom);
const treeFolders: FolderDisplay[] = useMemo(
@ -349,7 +361,7 @@ function AuthenticatedDocumentsSidebarBase({
const zeroIds = new Set(zeroDocs.map((d) => d.id));
const pendingAgentDocs = agentCreatedDocs
.filter((d) => d.searchSpaceId === searchSpaceId && !zeroIds.has(d.id))
.filter((d) => d.searchSpaceId === workspaceId && !zeroIds.has(d.id))
.map((d) => ({
id: d.id,
title: d.title,
@ -359,7 +371,7 @@ function AuthenticatedDocumentsSidebarBase({
}));
return [...pendingAgentDocs, ...zeroDocs];
}, [zeroAllDocs, agentCreatedDocs, searchSpaceId]);
}, [zeroAllDocs, agentCreatedDocs, workspaceId]);
// Prune agent-created docs once Zero has caught up
useEffect(() => {
@ -409,21 +421,21 @@ function AuthenticatedDocumentsSidebarBase({
await foldersApiService.createFolder({
name,
parent_id: createFolderParentId,
search_space_id: searchSpaceId,
workspace_id: workspaceId,
});
toast.success("Folder created");
if (createFolderParentId !== null) {
setExpandedFolderMap((prev) => {
const current = new Set(prev[searchSpaceId] ?? []);
const current = new Set(prev[workspaceId] ?? []);
current.add(createFolderParentId);
return { ...prev, [searchSpaceId]: [...current] };
return { ...prev, [workspaceId]: [...current] };
});
}
} catch (e: unknown) {
toast.error((e as Error)?.message || "Failed to create folder");
}
},
[createFolderParentId, searchSpaceId, setExpandedFolderMap]
[createFolderParentId, workspaceId, setExpandedFolderMap]
);
const handleRescanFolder = useCallback(
@ -444,7 +456,7 @@ function AuthenticatedDocumentsSidebarBase({
await uploadFolderScan({
folderPath: matched.path,
folderName: matched.name,
searchSpaceId,
searchSpaceId: workspaceId,
excludePatterns: matched.excludePatterns ?? DEFAULT_EXCLUDE_PATTERNS,
fileExtensions:
matched.fileExtensions ?? Array.from(getSupportedExtensionsSet(undefined, etlService)),
@ -455,7 +467,7 @@ function AuthenticatedDocumentsSidebarBase({
toast.error((err as Error)?.message || "Failed to re-scan folder");
}
},
[searchSpaceId, electronAPI, etlService]
[workspaceId, electronAPI, etlService]
);
const handleStopWatching = useCallback(
@ -577,7 +589,7 @@ function AuthenticatedDocumentsSidebarBase({
.trim()
.slice(0, 80) || "folder";
await doExport(
buildBackendUrl(`/api/v1/search-spaces/${searchSpaceId}/export`, {
buildBackendUrl(`/api/v1/workspaces/${workspaceId}/export`, {
folder_id: ctx.folder.id,
}),
`${safeName}.zip`
@ -590,7 +602,7 @@ function AuthenticatedDocumentsSidebarBase({
isExportingKBRef.current = false;
}
setExportWarningContext(null);
}, [exportWarningContext, searchSpaceId, doExport]);
}, [exportWarningContext, workspaceId, doExport]);
const getPendingCountInSubtree = useCallback(
(folderId: number): number => {
@ -631,7 +643,7 @@ function AuthenticatedDocumentsSidebarBase({
.trim()
.slice(0, 80) || "folder";
await doExport(
buildBackendUrl(`/api/v1/search-spaces/${searchSpaceId}/export`, {
buildBackendUrl(`/api/v1/workspaces/${workspaceId}/export`, {
folder_id: folder.id,
}),
`${safeName}.zip`
@ -644,7 +656,7 @@ function AuthenticatedDocumentsSidebarBase({
isExportingKBRef.current = false;
}
},
[searchSpaceId, getPendingCountInSubtree, doExport]
[workspaceId, getPendingCountInSubtree, doExport]
);
const handleExportDocument = useCallback(
@ -654,7 +666,7 @@ function AuthenticatedDocumentsSidebarBase({
const endpoint =
doc.document_type === "USER_MEMORY"
? buildBackendUrl("/api/v1/users/me/memory")
: buildBackendUrl(`/api/v1/workspaces/${searchSpaceId}/memory`);
: buildBackendUrl(`/api/v1/workspaces/${workspaceId}/memory`);
const response = await authenticatedFetch(endpoint, { method: "GET" });
if (!response.ok) {
const errorData = await response.json().catch(() => ({ detail: "Export failed" }));
@ -682,7 +694,7 @@ function AuthenticatedDocumentsSidebarBase({
try {
const response = await authenticatedFetch(
buildBackendUrl(`/api/v1/search-spaces/${searchSpaceId}/documents/${doc.id}/export`, {
buildBackendUrl(`/api/v1/workspaces/${workspaceId}/documents/${doc.id}/export`, {
format,
}),
{ method: "GET" }
@ -707,7 +719,7 @@ function AuthenticatedDocumentsSidebarBase({
toast.error(err instanceof Error ? err.message : `Export failed`);
}
},
[searchSpaceId]
[workspaceId]
);
const handleFolderPickerSelect = useCallback(
@ -830,7 +842,7 @@ function AuthenticatedDocumentsSidebarBase({
openEditorPanel({
kind: "memory",
memoryScope: "user",
searchSpaceId,
searchSpaceId: workspaceId,
title: doc.title,
});
return true;
@ -839,14 +851,14 @@ function AuthenticatedDocumentsSidebarBase({
openEditorPanel({
kind: "memory",
memoryScope: "team",
searchSpaceId,
searchSpaceId: workspaceId,
title: doc.title,
});
return true;
}
return false;
},
[openEditorPanel, searchSpaceId]
[openEditorPanel, workspaceId]
);
const handleResetMemoryDocument = useCallback(
@ -858,7 +870,7 @@ function AuthenticatedDocumentsSidebarBase({
const endpoint =
doc.document_type === "USER_MEMORY"
? buildBackendUrl("/api/v1/users/me/memory/reset")
: buildBackendUrl(`/api/v1/workspaces/${searchSpaceId}/memory/reset`);
: buildBackendUrl(`/api/v1/workspaces/${workspaceId}/memory/reset`);
try {
const response = await authenticatedFetch(endpoint, { method: "POST" });
if (!response.ok) {
@ -871,7 +883,7 @@ function AuthenticatedDocumentsSidebarBase({
toast.error((error as Error)?.message || `Failed to reset ${doc.title.toLowerCase()}`);
}
},
[openMemoryDocument, searchSpaceId]
[openMemoryDocument, workspaceId]
);
const typeCounts = useMemo(() => {
@ -1008,7 +1020,7 @@ function AuthenticatedDocumentsSidebarBase({
if (openMemoryDocument(doc)) return;
openEditorPanel({
documentId: doc.id,
searchSpaceId,
searchSpaceId: workspaceId,
title: doc.title,
});
}}
@ -1016,7 +1028,7 @@ function AuthenticatedDocumentsSidebarBase({
if (openMemoryDocument(doc)) return;
openEditorPanel({
documentId: doc.id,
searchSpaceId,
searchSpaceId: workspaceId,
title: doc.title,
});
}}
@ -1098,7 +1110,7 @@ function AuthenticatedDocumentsSidebarBase({
{deletableSelectedIds.length === 1
? "This document"
: `These ${deletableSelectedIds.length} documents`}{" "}
will be permanently deleted from your search space.
will be permanently deleted from your workspace.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>

View file

@ -208,7 +208,7 @@ export function InboxSidebarContent({
queryFn: () =>
notificationsApiService.getNotifications({
queryParams: {
search_space_id: searchSpaceId ?? undefined,
workspace_id: searchSpaceId ?? undefined,
type: searchTypeFilter,
search: debouncedSearch.trim(),
limit: 50,
@ -364,7 +364,7 @@ export function InboxSidebarContent({
if (item.type === "new_mention") {
if (isNewMentionMetadata(item.metadata)) {
const searchSpaceId = item.search_space_id;
const searchSpaceId = item.workspace_id;
const threadId = item.metadata.thread_id;
const commentId = item.metadata.comment_id;
@ -382,7 +382,7 @@ export function InboxSidebarContent({
}
} else if (item.type === "comment_reply") {
if (isCommentReplyMetadata(item.metadata)) {
const searchSpaceId = item.search_space_id;
const searchSpaceId = item.workspace_id;
const threadId = item.metadata.thread_id;
const replyId = item.metadata.reply_id;

View file

@ -5,7 +5,7 @@ import { Button } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Sheet, SheetContent, SheetTitle } from "@/components/ui/sheet";
import type { ChatItem, NavItem, PageUsage, SearchSpace, User } from "../../types/layout.types";
import { SearchSpaceAvatar } from "../icon-rail/SearchSpaceAvatar";
import { SearchSpaceAvatar } from "../icon-rail/WorkspaceAvatar";
import { Sidebar } from "./Sidebar";
interface MobileSidebarProps {

View file

@ -104,7 +104,7 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen
try {
const response = await authenticatedFetch(
buildBackendUrl(
`/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/editor-content`
`/api/v1/workspaces/${searchSpaceId}/documents/${documentId}/editor-content`
),
{ method: "GET" }
);
@ -154,7 +154,7 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen
setSaving(true);
try {
const response = await authenticatedFetch(
buildBackendUrl(`/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/save`),
buildBackendUrl(`/api/v1/workspaces/${searchSpaceId}/documents/${documentId}/save`),
{
method: "POST",
headers: { "Content-Type": "application/json" },
@ -313,7 +313,7 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen
try {
const response = await authenticatedFetch(
buildBackendUrl(
`/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/download-markdown`
`/api/v1/workspaces/${searchSpaceId}/documents/${documentId}/download-markdown`
),
{ method: "GET" }
);

View file

@ -51,7 +51,7 @@ export function ChatShareButton({ thread, onVisibilityChange, className }: ChatS
// Use Jotai atom for visibility (single source of truth)
const currentThreadState = useAtomValue(currentThreadAtom);
const { mutateAsync: updateVisibility } = useUpdateThreadVisibility(thread?.search_space_id ?? 0);
const { mutateAsync: updateVisibility } = useUpdateThreadVisibility(thread?.workspace_id ?? 0);
// Snapshot creation mutation
const { mutateAsync: createSnapshot, isPending: isCreatingSnapshot } = useAtomValue(
@ -139,9 +139,7 @@ export function ChatShareButton({ thread, onVisibilityChange, className }: ChatS
variant="ghost"
size="icon"
onClick={() =>
router.push(
`/dashboard/${thread.search_space_id}/search-space-settings/public-links`
)
router.push(`/dashboard/${thread.workspace_id}/workspace-settings/public-links`)
}
className="size-8 bg-muted/50 hover:bg-accent hover:text-accent-foreground"
>

View file

@ -316,7 +316,7 @@ export const DocumentMentionPicker = forwardRef<
const titleSearchParams = useMemo(
() => ({
search_space_id: searchSpaceId,
workspace_id: searchSpaceId,
page: 0,
page_size: PAGE_SIZE,
...(isSearchValid ? { title: debouncedSearch.trim() } : {}),
@ -362,7 +362,7 @@ export const DocumentMentionPicker = forwardRef<
try {
const queryParams = {
search_space_id: searchSpaceId,
workspace_id: searchSpaceId,
page: nextPage,
page_size: PAGE_SIZE,
...(isSearchValid ? { title: debouncedSearch.trim() } : {}),

View file

@ -146,7 +146,7 @@ export function ImageModelSelector({
function manageModelConnections() {
setOpen(false);
router.push(`/dashboard/${searchSpaceId}/search-space-settings/models`);
router.push(`/dashboard/${searchSpaceId}/workspace-settings/models`);
}
const handleScroll = useCallback((event: UIEvent<HTMLDivElement>) => {

View file

@ -149,7 +149,7 @@ export function ModelSelector({
function manageModelConnections() {
setOpen(false);
router.push(`/dashboard/${searchSpaceId}/search-space-settings/models`);
router.push(`/dashboard/${searchSpaceId}/workspace-settings/models`);
}
const handleScroll = useCallback((event: UIEvent<HTMLDivElement>) => {

View file

@ -7,8 +7,8 @@ import { useTheme } from "next-themes";
import { useCallback, useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms";
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
import { activeWorkspaceIdAtom } from "@/atoms/workspaces/workspace-query.atoms";
import { Button } from "@/components/ui/button";
import { useIsMobile } from "@/hooks/use-mobile";
import { useZeroDocumentTypeCounts } from "@/hooks/use-zero-document-type-counts";
@ -391,7 +391,7 @@ export function OnboardingTour() {
// Get user data
const { data: user } = useAtomValue(currentUserAtom);
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
const searchSpaceId = useAtomValue(activeWorkspaceIdAtom);
// Fetch threads data
const { data: threadsData } = useQuery({

View file

@ -28,7 +28,7 @@ export function PublicChatFooter({ shareToken }: PublicChatFooterProps) {
});
// Redirect to the new chat page with cloned content
router.push(`/dashboard/${response.search_space_id}/new-chat/${response.thread_id}`);
router.push(`/dashboard/${response.workspace_id}/new-chat/${response.thread_id}`);
} catch (error) {
const message = error instanceof Error ? error.message : "Failed to copy chat";
toast.error(message);

View file

@ -20,7 +20,12 @@ export function OrganizationJsonLd() {
logo: "https://www.surfsense.com/logo.png",
description:
"SurfSense is an open-source competitive intelligence platform. AI agents monitor competitors, track rankings, and listen to your market through one API or MCP server.",
sameAs: ["https://github.com/MODSetter/SurfSense", "https://discord.gg/ejRNvftDp9"],
sameAs: [
"https://github.com/MODSetter/SurfSense",
"https://discord.gg/ejRNvftDp9",
"https://www.reddit.com/r/SurfSense/",
"https://www.linkedin.com/company/surfsense/",
],
contactPoint: {
"@type": "ContactPoint",
email: "rohan@surfsense.com",

View file

@ -80,7 +80,7 @@ export function AutoReloadSettings() {
const setupMutation = useMutation({
mutationFn: () =>
stripeApiService.createAutoReloadSetupSession({ search_space_id: searchSpaceId }),
stripeApiService.createAutoReloadSetupSession({ workspace_id: searchSpaceId }),
onSuccess: (response) => {
window.location.assign(response.checkout_url);
},

View file

@ -177,7 +177,7 @@ export function BuyCreditsContent() {
<Button
className="w-full"
disabled={purchaseMutation.isPending}
onClick={() => purchaseMutation.mutate({ quantity, search_space_id: searchSpaceId })}
onClick={() => purchaseMutation.mutate({ quantity, workspace_id: searchSpaceId })}
>
{purchaseMutation.isPending ? (
<>

View file

@ -8,13 +8,13 @@ import { toast } from "sonner";
import {
updateSearchSpaceApiAccessMutationAtom,
updateSearchSpaceMutationAtom,
} from "@/atoms/search-spaces/search-space-mutation.atoms";
} from "@/atoms/workspaces/workspace-mutation.atoms";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Skeleton } from "@/components/ui/skeleton";
import { Switch } from "@/components/ui/switch";
import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service";
import { searchSpacesApiService } from "@/lib/apis/workspaces-api.service";
import { authenticatedFetch } from "@/lib/auth-fetch";
import { buildBackendUrl } from "@/lib/env-config";
import { cacheKeys } from "@/lib/query-client/cache-keys";
@ -58,7 +58,7 @@ export function GeneralSettingsManager({ workspaceId }: GeneralSettingsManagerPr
setIsExporting(true);
try {
const response = await authenticatedFetch(
buildBackendUrl(`/api/v1/search-spaces/${searchSpaceId}/export`),
buildBackendUrl(`/api/v1/workspaces/${searchSpaceId}/export`),
{ method: "GET" }
);
if (!response.ok) {

View file

@ -148,7 +148,7 @@ export function ConnectionSettingsDialog({
base_url: data.base_url,
api_key: apiKeyForTest,
scope: "SEARCH_SPACE",
search_space_id: connection.search_space_id,
workspace_id: connection.workspace_id,
extra: connection.extra ?? {},
enabled: connection.enabled,
models: [],

View file

@ -143,7 +143,7 @@ export function ModelProviderConnectionsPanel({
base_url: draft.base_url,
api_key: draft.api_key,
scope: "SEARCH_SPACE" as const,
search_space_id: searchSpaceId,
workspace_id: searchSpaceId,
extra: draft.extra,
enabled: true,
models,
@ -181,7 +181,7 @@ export function ModelProviderConnectionsPanel({
base_url: null,
api_key: null,
scope: "SEARCH_SPACE",
search_space_id: searchSpaceId,
workspace_id: searchSpaceId,
extra: {},
enabled: true,
models: [],
@ -205,7 +205,7 @@ export function ModelProviderConnectionsPanel({
base_url: draft.base_url,
api_key: draft.api_key,
scope: "SEARCH_SPACE",
search_space_id: searchSpaceId,
workspace_id: searchSpaceId,
extra: draft.extra,
enabled: true,
models: [],

View file

@ -5,13 +5,13 @@ import { useAtomValue } from "jotai";
import { AlertTriangle, Info } from "lucide-react";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import { updateSearchSpaceMutationAtom } from "@/atoms/search-spaces/search-space-mutation.atoms";
import { updateSearchSpaceMutationAtom } from "@/atoms/workspaces/workspace-mutation.atoms";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Skeleton } from "@/components/ui/skeleton";
import { Textarea } from "@/components/ui/textarea";
import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service";
import { searchSpacesApiService } from "@/lib/apis/workspaces-api.service";
import { cacheKeys } from "@/lib/query-client/cache-keys";
import { Spinner } from "../ui/spinner";

View file

@ -280,7 +280,7 @@ export function RolesManager({ workspaceId }: { workspaceId: number }) {
const { data: roles = [], isLoading: rolesLoading } = useQuery({
queryKey: cacheKeys.roles.all(searchSpaceId.toString()),
queryFn: () => rolesApiService.getRoles({ search_space_id: searchSpaceId }),
queryFn: () => rolesApiService.getRoles({ workspace_id: searchSpaceId }),
enabled: !!searchSpaceId,
});
@ -312,7 +312,7 @@ export function RolesManager({ workspaceId }: { workspaceId: number }) {
}
): Promise<Role> => {
const request: UpdateRoleRequest = {
search_space_id: searchSpaceId,
workspace_id: searchSpaceId,
role_id: roleId,
data: data,
};
@ -324,7 +324,7 @@ export function RolesManager({ workspaceId }: { workspaceId: number }) {
const handleDeleteRole = useCallback(
async (roleId: number): Promise<boolean> => {
const request: DeleteRoleRequest = {
search_space_id: searchSpaceId,
workspace_id: searchSpaceId,
role_id: roleId,
};
await deleteRole(request);
@ -336,7 +336,7 @@ export function RolesManager({ workspaceId }: { workspaceId: number }) {
const handleCreateRole = useCallback(
async (roleData: CreateRoleRequest["data"]): Promise<Role> => {
const request: CreateRoleRequest = {
search_space_id: searchSpaceId,
workspace_id: searchSpaceId,
data: roleData,
};
return await createRole(request);

View file

@ -364,7 +364,7 @@ export function DocumentUploadTab({
batch.map((e) => e.file),
{
folder_name: folderUpload.folderName,
search_space_id: Number(searchSpaceId),
workspace_id: Number(searchSpaceId),
relative_paths: batch.map((e) => e.relativePath),
root_folder_id: rootFolderId,
use_vision_llm: useVisionLlm,
@ -413,7 +413,7 @@ export function DocumentUploadTab({
uploadDocuments(
{
files: rawFiles,
search_space_id: Number(searchSpaceId),
workspace_id: Number(searchSpaceId),
use_vision_llm: useVisionLlm,
processing_mode: processingMode,
},

View file

@ -9,7 +9,7 @@ import {
AutomationModelFields,
type AutomationModelSelection,
} from "@/app/dashboard/[workspace_id]/automations/components/builder/automation-model-fields";
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { activeWorkspaceIdAtom } from "@/atoms/workspaces/workspace-query.atoms";
import { JsonView } from "@/components/json-view";
import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
@ -27,7 +27,7 @@ import {
} from "@/lib/posthog/events";
import { AutomationDraftPreview } from "./automation-draft-preview";
const editArgsSchema = automationCreateRequest.omit({ search_space_id: true });
const editArgsSchema = automationCreateRequest.omit({ workspace_id: true });
// ----------------------------------------------------------------------------
// Result discrimination — mirrors the backend return shapes in
@ -35,7 +35,7 @@ const editArgsSchema = automationCreateRequest.omit({ search_space_id: true });
// ----------------------------------------------------------------------------
type AutomationCreateContext = {
search_space_id?: number;
workspace_id?: number;
};
interface SavedResult {
@ -81,7 +81,7 @@ function hasStatus(value: unknown, status: string): boolean {
//
// Edit toggle reuses the same primitives as the Create-via-JSON page: raw
// textarea, Format, Zod validation against ``AutomationCreate`` (minus the
// ``search_space_id`` field, which the backend injects). Approve dispatches
// ``workspace_id`` field, which the backend injects). Approve dispatches
// an ``edit`` decision with the parsed args when edits are pending, otherwise
// a plain ``approve``. Multi-turn chat refinement still works as a fallback.
// ----------------------------------------------------------------------------
@ -110,7 +110,7 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) {
// Per-automation model selection. The card always supplies models (chosen
// here, not snapshotted from the search space), so Approve dispatches an
// `edit` decision carrying `definition.models`.
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
const searchSpaceId = useAtomValue(activeWorkspaceIdAtom);
const eligibleModels = useAutomationEligibleModels();
const [modelSelection, setModelSelection] = useState<AutomationModelSelection>({
chatModelId: 0,
@ -156,7 +156,7 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) {
const plan = Array.isArray(baseDefinition.plan) ? baseDefinition.plan : [];
const triggers = Array.isArray(baseArgs.triggers) ? baseArgs.triggers : [];
trackAutomationChatApproved({
search_space_id: searchSpaceId ? Number(searchSpaceId) : undefined,
workspace_id: searchSpaceId ? Number(searchSpaceId) : undefined,
edited: pendingEdits !== null,
task_count: plan.length,
trigger_type:
@ -191,7 +191,7 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) {
if (phase !== "pending" || !canReject || isEditing) return;
setRejected();
trackAutomationChatRejected({
search_space_id: searchSpaceId ? Number(searchSpaceId) : undefined,
workspace_id: searchSpaceId ? Number(searchSpaceId) : undefined,
});
onDecision({ type: "reject", message: "User rejected the automation draft." });
}, [phase, canReject, isEditing, setRejected, onDecision, searchSpaceId]);
@ -268,7 +268,7 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) {
setPendingEdits(parsed);
setIsEditing(false);
trackAutomationChatDraftEdited({
search_space_id: searchSpaceId ? Number(searchSpaceId) : undefined,
workspace_id: searchSpaceId ? Number(searchSpaceId) : undefined,
});
}}
onCancel={() => setIsEditing(false)}
@ -385,7 +385,7 @@ function JsonEditor({ initialValue, onSave, onCancel }: JsonEditorProps) {
// ----------------------------------------------------------------------------
function SavedCard({ result }: { result: SavedResult }) {
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
const searchSpaceId = useAtomValue(activeWorkspaceIdAtom);
const tracked = useRef(false);
useEffect(() => {
if (tracked.current) return;
@ -393,7 +393,7 @@ function SavedCard({ result }: { result: SavedResult }) {
trackAutomationChatCreateSucceeded({
automation_id: result.automation_id,
name: result.name,
search_space_id: searchSpaceId ? Number(searchSpaceId) : undefined,
workspace_id: searchSpaceId ? Number(searchSpaceId) : undefined,
});
}, [result.automation_id, result.name, searchSpaceId]);
@ -429,7 +429,7 @@ function SavedCard({ result }: { result: SavedResult }) {
}
function InvalidCard({ result }: { result: InvalidResult }) {
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
const searchSpaceId = useAtomValue(activeWorkspaceIdAtom);
const tracked = useRef(false);
useEffect(() => {
if (tracked.current) return;
@ -437,7 +437,7 @@ function InvalidCard({ result }: { result: InvalidResult }) {
trackAutomationChatCreateFailed({
reason: "invalid",
issue_count: result.issues.length,
search_space_id: searchSpaceId ? Number(searchSpaceId) : undefined,
workspace_id: searchSpaceId ? Number(searchSpaceId) : undefined,
});
}, [result.issues.length, searchSpaceId]);
@ -464,7 +464,7 @@ function InvalidCard({ result }: { result: InvalidResult }) {
}
function ErrorCard({ result }: { result: ErrorResult }) {
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
const searchSpaceId = useAtomValue(activeWorkspaceIdAtom);
const tracked = useRef(false);
useEffect(() => {
if (tracked.current) return;
@ -472,7 +472,7 @@ function ErrorCard({ result }: { result: ErrorResult }) {
trackAutomationChatCreateFailed({
reason: "error",
message: result.message,
search_space_id: searchSpaceId ? Number(searchSpaceId) : undefined,
workspace_id: searchSpaceId ? Number(searchSpaceId) : undefined,
});
}, [result.message, searchSpaceId]);
@ -531,7 +531,7 @@ export const CreateAutomationToolUI = ({
* Project raw args into the shape ``AutomationDraftPreview`` expects.
*
* The args dict is the full ``AutomationCreate`` payload (minus
* ``search_space_id`` which is injected server-side), so we trust the
* ``workspace_id`` which is injected server-side), so we trust the
* top-level fields but defend against missing nested defaults.
*/
function extractDraft(args: Record<string, unknown>) {

View file

@ -87,7 +87,7 @@ External boundaries (LLM, embedding, chunking, Redis) are mocked in both suites.
1. **Database setup** — `TEST_DATABASE_URL` defaults to `surfsense_test`. Tables and extensions (`vector`, `pg_trgm`) are created once per session and dropped after.
2. **Transaction isolation** — Each test runs inside a savepoint that rolls back, so tests don't affect each other.
3. **User creation** — Integration tests register a test user via `POST /auth/register` on first run, then log in for subsequent requests.
4. **Search space discovery** — Tests call `GET /api/v1/searchspaces` and use the first available space.
4. **Search space discovery** — Tests call `GET /api/v1/workspaces` and use the first available space.
5. **Cleanup** — A session fixture purges stale documents before tests run. Per-test cleanup deletes documents via API, falling back to direct DB access for stuck records.
## Writing New Tests

View file

@ -118,7 +118,7 @@ export type Trigger = z.infer<typeof trigger>;
// =============================================================================
export const automationCreateRequest = z.object({
search_space_id: z.number(),
workspace_id: z.number(),
name: z.string().min(1).max(200),
description: z.string().nullable().optional(),
definition: automationDefinition,
@ -136,7 +136,7 @@ export type AutomationUpdateRequest = z.infer<typeof automationUpdateRequest>;
export const automationSummary = z.object({
id: z.number(),
search_space_id: z.number(),
workspace_id: z.number(),
name: z.string(),
description: z.string().nullable().optional(),
status: automationStatus,
@ -159,7 +159,7 @@ export const automationListResponse = z.object({
export type AutomationListResponse = z.infer<typeof automationListResponse>;
export const automationListParams = z.object({
search_space_id: z.number(),
workspace_id: z.number(),
limit: z.number().int().min(1).max(200).default(50),
offset: z.number().int().min(0).default(0),
});

View file

@ -52,8 +52,8 @@ export const mentionContext = z.object({
thread_id: z.number(),
thread_title: z.string(),
message_id: z.number(),
search_space_id: z.number(),
search_space_name: z.string(),
workspace_id: z.number(),
workspace_name: z.string(),
});
export const mentionComment = z.object({
@ -144,7 +144,7 @@ export const deleteCommentResponse = z.object({
* Get mentions
*/
export const getMentionsRequest = z.object({
search_space_id: z.number().optional(),
workspace_id: z.number().optional(),
});
export const getMentionsResponse = z.object({

View file

@ -62,7 +62,7 @@ export const publicChatSnapshotDetail = z.object({
* List public chat snapshots for search space
*/
export const publicChatSnapshotsBySpaceRequest = z.object({
search_space_id: z.number(),
workspace_id: z.number(),
});
export const publicChatSnapshotsBySpaceResponse = z.object({

View file

@ -47,7 +47,7 @@ export const searchSourceConnector = z.object({
periodic_indexing_enabled: z.boolean(),
indexing_frequency_minutes: z.number().nullable(),
next_scheduled_at: z.string().nullable(),
search_space_id: z.number(),
workspace_id: z.number(),
user_id: z.string(),
created_at: z.string(),
});
@ -72,7 +72,7 @@ export const getConnectorsRequest = z.object({
queryParams: paginationQueryParams
.pick({ skip: true, limit: true })
.extend({
search_space_id: z.number().or(z.string()).nullish(),
workspace_id: z.number().or(z.string()).nullish(),
})
.nullish(),
});
@ -103,7 +103,7 @@ export const createConnectorRequest = z.object({
next_scheduled_at: true,
}),
queryParams: z.object({
search_space_id: z.number().or(z.string()),
workspace_id: z.number().or(z.string()),
}),
});
@ -175,7 +175,7 @@ export const googleDriveIndexBody = z.object({
export const indexConnectorRequest = z.object({
connector_id: z.number(),
queryParams: z.object({
search_space_id: z.number().or(z.string()),
workspace_id: z.number().or(z.string()),
start_date: z.string().optional(),
end_date: z.string().optional(),
}),
@ -185,7 +185,7 @@ export const indexConnectorRequest = z.object({
export const indexConnectorResponse = z.object({
message: z.string(),
connector_id: z.number(),
search_space_id: z.number(),
workspace_id: z.number(),
indexing_from: z.string(),
indexing_to: z.string(),
});

View file

@ -46,7 +46,7 @@ export const document = z.object({
unique_identifier_hash: z.string().nullable(),
created_at: z.string(),
updated_at: z.string().nullable(),
search_space_id: z.number(),
workspace_id: z.number(),
created_by_id: z.string().nullable().optional(),
created_by_name: z.string().nullable().optional(),
created_by_email: z.string().nullable().optional(),
@ -85,7 +85,7 @@ export const sortOrderEnum = z.enum(["asc", "desc"]);
export const getDocumentsRequest = z.object({
queryParams: paginationQueryParams
.extend({
search_space_id: z.number().or(z.string()).optional(),
workspace_id: z.number().or(z.string()).optional(),
document_types: z.array(documentTypeEnum).optional(),
sort_by: documentSortByEnum.optional(),
sort_order: sortOrderEnum.optional(),
@ -112,7 +112,7 @@ export const getDocumentResponse = document;
* Create documents
*/
export const createDocumentRequest = document
.pick({ document_type: true, search_space_id: true })
.pick({ document_type: true, workspace_id: true })
.extend({
content: z.string().or(z.array(z.string())).or(z.array(extensionDocumentContent)),
});
@ -129,7 +129,7 @@ export const processingModeEnum = z.enum(["basic", "premium"]);
export const uploadDocumentRequest = z.object({
files: z.array(z.instanceof(File)),
search_space_id: z.number(),
workspace_id: z.number(),
use_vision_llm: z.boolean().default(false),
processing_mode: processingModeEnum.default("basic"),
});
@ -148,7 +148,7 @@ export const uploadDocumentResponse = z.object({
*/
export const getDocumentsStatusRequest = z.object({
queryParams: z.object({
search_space_id: z.number(),
workspace_id: z.number(),
document_ids: z.array(z.number()).min(1),
}),
});
@ -175,7 +175,7 @@ export const getDocumentsStatusResponse = z.object({
export const searchDocumentsRequest = z.object({
queryParams: paginationQueryParams
.extend({
search_space_id: z.number().or(z.string()).optional(),
workspace_id: z.number().or(z.string()).optional(),
document_types: z.array(documentTypeEnum).optional(),
title: z.string().optional(),
})
@ -200,18 +200,12 @@ export const documentTitleRead = z.object({
});
export const searchDocumentTitlesRequest = z.object({
queryParams: z
.object({
search_space_id: z.number().optional(),
workspace_id: z.number().optional(),
title: z.string().optional(),
page: z.number().optional(),
page_size: z.number().optional(),
})
.refine((params) => params.search_space_id !== undefined || params.workspace_id !== undefined, {
message: "workspace_id is required",
path: ["search_space_id"],
}),
queryParams: z.object({
workspace_id: z.number(),
title: z.string().optional(),
page: z.number().optional(),
page_size: z.number().optional(),
}),
});
export const searchDocumentTitlesResponse = z.object({
@ -225,7 +219,7 @@ export const searchDocumentTitlesResponse = z.object({
export const getDocumentTypeCountsRequest = z.object({
queryParams: z
.object({
search_space_id: z.number().or(z.string()).optional(),
workspace_id: z.number().or(z.string()).optional(),
})
.nullish(),
});
@ -272,7 +266,7 @@ export const getDocumentChunksResponse = z.object({
*/
export const updateDocumentRequest = z.object({
id: z.number(),
data: document.pick({ search_space_id: true, document_type: true, content: true }),
data: document.pick({ workspace_id: true, document_type: true, content: true }),
});
export const updateDocumentResponse = document;

View file

@ -5,27 +5,19 @@ const folderBase = z.object({
name: z.string(),
position: z.string(),
parent_id: z.number().nullable(),
search_space_id: z.number(),
workspace_id: z.number(),
created_by_id: z.string().nullable().optional(),
created_at: z.string(),
updated_at: z.string(),
metadata: z.record(z.string(), z.any()).nullable().optional(),
});
export const folder = z.preprocess((value) => {
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
const record = value as Record<string, unknown>;
if (record.search_space_id === undefined && record.workspace_id !== undefined) {
return { ...record, search_space_id: record.workspace_id };
}
}
return value;
}, folderBase);
export const folder = folderBase;
export const folderCreateRequest = z.object({
name: z.string().min(1).max(255),
parent_id: z.number().nullable().optional(),
search_space_id: z.number(),
workspace_id: z.number(),
});
export const folderUpdateRequest = z.object({

View file

@ -7,7 +7,7 @@ import { z } from "zod";
export const imageGenerationListItem = z.object({
id: z.number(),
prompt: z.string(),
search_space_id: z.number(),
workspace_id: z.number(),
created_at: z.string(),
is_success: z.boolean(),
image_count: z.number().nullish(),

View file

@ -152,7 +152,7 @@ export const inboxItemMetadata = z.union([
export const inboxItem = z.object({
id: z.number(),
user_id: z.string(),
search_space_id: z.number().nullable(),
workspace_id: z.number().nullable(),
type: inboxItemTypeEnum,
title: z.string(),
message: z.string(),
@ -210,7 +210,7 @@ export type NotificationCategory = z.infer<typeof notificationCategory>;
*/
export const getNotificationsRequest = z.object({
queryParams: z.object({
search_space_id: z.number().optional(),
workspace_id: z.number().optional(),
type: inboxItemTypeEnum.optional(),
category: notificationCategory.optional(),
source_type: z.string().optional(),
@ -260,7 +260,7 @@ export const markAllNotificationsReadResponse = z.object({
* Request schema for getting unread count
*/
export const getUnreadCountRequest = z.object({
search_space_id: z.number().optional(),
workspace_id: z.number().optional(),
});
/**

View file

@ -5,7 +5,7 @@ export const invite = z.object({
id: z.number(),
name: z.string().max(100).nullable().optional(),
invite_code: z.string(),
search_space_id: z.number(),
workspace_id: z.number(),
created_by_id: z.string().nullable(),
role_id: z.number().nullable(),
expires_at: z.string().nullable(),
@ -20,7 +20,7 @@ export const invite = z.object({
* Create invite
*/
export const createInviteRequest = z.object({
search_space_id: z.number(),
workspace_id: z.number(),
data: z.object({
name: z.string().max(100).optional(),
role_id: z.number().nullable().optional(),
@ -35,7 +35,7 @@ export const createInviteResponse = invite;
* Get invites
*/
export const getInvitesRequest = z.object({
search_space_id: z.number(),
workspace_id: z.number(),
});
export const getInvitesResponse = z.array(invite);
@ -44,7 +44,7 @@ export const getInvitesResponse = z.array(invite);
* Update invite
*/
export const updateInviteRequest = z.object({
search_space_id: z.number(),
workspace_id: z.number(),
invite_id: z.number(),
data: z.object({
name: z.string().max(100).optional(),
@ -61,7 +61,7 @@ export const updateInviteResponse = invite;
* Delete invite
*/
export const deleteInviteRequest = z.object({
search_space_id: z.number(),
workspace_id: z.number(),
invite_id: z.number(),
});
@ -77,7 +77,7 @@ export const getInviteInfoRequest = z.object({
});
export const getInviteInfoResponse = z.object({
search_space_name: z.string(),
workspace_name: z.string(),
role_name: z.string().nullable(),
is_valid: z.boolean(),
message: z.string().nullable(),
@ -92,8 +92,8 @@ export const acceptInviteRequest = z.object({
export const acceptInviteResponse = z.object({
message: z.string(),
search_space_id: z.number(),
search_space_name: z.string(),
workspace_id: z.number(),
workspace_name: z.string(),
role_name: z.string().nullable(),
});

View file

@ -19,7 +19,7 @@ export const log = z.object({
source: z.string().nullable().optional(),
log_metadata: z.record(z.string(), z.any()).nullable().optional(),
created_at: z.string(),
search_space_id: z.number(),
workspace_id: z.number(),
});
export const logBase = log.omit({ id: true, created_at: true });
@ -27,7 +27,7 @@ export const logBase = log.omit({ id: true, created_at: true });
/**
* Create log
*/
export const createLogRequest = logBase.extend({ search_space_id: z.number() });
export const createLogRequest = logBase.extend({ workspace_id: z.number() });
export const createLogResponse = log;
/**
@ -48,7 +48,7 @@ export const deleteLogResponse = z.object({
* Get logs (list)
*/
export const logFilters = z.object({
search_space_id: z.number().optional(),
workspace_id: z.number().optional(),
level: logLevelEnum.optional(),
status: logStatusEnum.optional(),
source: z.string().optional(),
@ -59,7 +59,7 @@ export const logFilters = z.object({
export const getLogsRequest = z.object({
queryParams: paginationQueryParams
.extend({
search_space_id: z.number().optional(),
workspace_id: z.number().optional(),
level: logLevelEnum.optional(),
status: logStatusEnum.optional(),
source: z.string().optional(),
@ -106,7 +106,7 @@ export const logSummary = z.object({
recent_failures: z.array(logFailure),
});
export const getLogSummaryRequest = z.object({
search_space_id: z.number(),
workspace_id: z.number(),
hours: z.number().optional(),
});
export const getLogSummaryResponse = logSummary;

View file

@ -37,7 +37,7 @@ export const mcpConnectorRead = z.object({
name: z.string(),
connector_type: z.literal("MCP_CONNECTOR"),
server_config: mcpServerConfig,
search_space_id: z.number(),
workspace_id: z.number(),
user_id: z.string(),
created_at: z.string(),
updated_at: z.string(),
@ -49,7 +49,7 @@ export const mcpConnectorRead = z.object({
export const createMCPConnectorRequest = z.object({
data: mcpConnectorCreate,
queryParams: z.object({
search_space_id: z.number().or(z.string()),
workspace_id: z.number().or(z.string()),
}),
});
@ -60,7 +60,7 @@ export const updateMCPConnectorRequest = z.object({
export const getMCPConnectorsRequest = z.object({
queryParams: z.object({
search_space_id: z.number().or(z.string()),
workspace_id: z.number().or(z.string()),
}),
});

View file

@ -1,38 +1,27 @@
import { z } from "zod";
import { role } from "./roles.types";
export const membership = z.preprocess(
(value) => {
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
const record = value as Record<string, unknown>;
if (record.search_space_id === undefined && record.workspace_id !== undefined) {
return { ...record, search_space_id: record.workspace_id };
}
}
return value;
},
z.object({
id: z.number(),
user_id: z.string(),
search_space_id: z.number(),
role_id: z.number().nullable(),
is_owner: z.boolean(),
joined_at: z.string(),
created_at: z.string(),
role: role.nullable().optional(),
user_email: z.string().nullable().optional(),
user_display_name: z.string().nullable().optional(),
user_avatar_url: z.string().nullable().optional(),
user_last_login: z.string().nullable().optional(),
user_is_active: z.boolean().nullable().optional(),
})
);
export const membership = z.object({
id: z.number(),
user_id: z.string(),
workspace_id: z.number(),
role_id: z.number().nullable(),
is_owner: z.boolean(),
joined_at: z.string(),
created_at: z.string(),
role: role.nullable().optional(),
user_email: z.string().nullable().optional(),
user_display_name: z.string().nullable().optional(),
user_avatar_url: z.string().nullable().optional(),
user_last_login: z.string().nullable().optional(),
user_is_active: z.boolean().nullable().optional(),
});
/**
* Get members
*/
export const getMembersRequest = z.object({
search_space_id: z.number(),
workspace_id: z.number(),
});
export const getMembersResponse = z.array(membership);
@ -41,7 +30,7 @@ export const getMembersResponse = z.array(membership);
* Update membership
*/
export const updateMembershipRequest = z.object({
search_space_id: z.number(),
workspace_id: z.number(),
membership_id: z.number(),
data: z.object({
role_id: z.number().nullable(),
@ -54,7 +43,7 @@ export const updateMembershipResponse = membership;
* Delete membership
*/
export const deleteMembershipRequest = z.object({
search_space_id: z.number(),
workspace_id: z.number(),
membership_id: z.number(),
});
@ -63,10 +52,10 @@ export const deleteMembershipResponse = z.object({
});
/**
* Leave search space
* Leave workspace
*/
export const leaveSearchSpaceRequest = z.object({
search_space_id: z.number(),
workspace_id: z.number(),
});
export const leaveSearchSpaceResponse = z.object({
@ -77,22 +66,16 @@ export const leaveSearchSpaceResponse = z.object({
* Get my access
*/
export const getMyAccessRequest = z.object({
search_space_id: z.number(),
workspace_id: z.number(),
});
export const getMyAccessResponse = z
.object({
workspace_name: z.string(),
workspace_id: z.number(),
is_owner: z.boolean(),
permissions: z.array(z.string()),
role_name: z.string().nullable(),
})
.transform(({ workspace_id, workspace_name, ...rest }) => ({
...rest,
search_space_id: workspace_id,
search_space_name: workspace_name,
}));
export const getMyAccessResponse = z.object({
workspace_name: z.string(),
workspace_id: z.number(),
is_owner: z.boolean(),
permissions: z.array(z.string()),
role_name: z.string().nullable(),
});
export type Membership = z.infer<typeof membership>;
export type GetMembersRequest = z.infer<typeof getMembersRequest>;

View file

@ -28,7 +28,7 @@ export const connectionRead = z.object({
api_key: z.string().nullable().optional(),
extra: z.record(z.string(), z.any()).default({}),
scope: z.union([connectionScopeEnum, z.string()]),
search_space_id: z.number().nullable().optional(),
workspace_id: z.number().nullable().optional(),
user_id: z.string().nullable().optional(),
enabled: z.boolean(),
has_api_key: z.boolean(),
@ -57,7 +57,7 @@ export const connectionCreateRequest = z.object({
api_key: z.string().nullable().optional(),
extra: z.record(z.string(), z.any()).default({}),
scope: connectionScopeEnum.default("SEARCH_SPACE"),
search_space_id: z.number().nullable().optional(),
workspace_id: z.number().nullable().optional(),
enabled: z.boolean().default(true),
models: z.array(modelSelection).default([]),
});

View file

@ -151,7 +151,7 @@ export const podcastDetail = z.object({
duration_seconds: z.number().nullable(),
error: z.string().nullable(),
created_at: z.string(),
search_space_id: z.number(),
workspace_id: z.number(),
thread_id: z.number().nullable(),
});
export type PodcastDetail = z.infer<typeof podcastDetail>;
@ -162,7 +162,7 @@ export const podcastSummary = z.object({
title: z.string(),
status: podcastStatus,
created_at: z.string(),
search_space_id: z.number(),
workspace_id: z.number(),
thread_id: z.number().nullish(),
});
export type PodcastSummary = z.infer<typeof podcastSummary>;

View file

@ -7,7 +7,7 @@ export const promptRead = z.object({
name: z.string(),
prompt: z.string(),
mode: z.enum(["transform", "explore"]),
search_space_id: z.number().nullable(),
workspace_id: z.number().nullable(),
is_public: z.boolean(),
version: z.number(),
created_at: z.string(),
@ -29,7 +29,7 @@ export const promptCreateRequest = z.object({
name: z.string().min(1).max(200),
prompt: z.string().min(1),
mode: z.enum(["transform", "explore"]),
search_space_id: z.number().nullable().optional(),
workspace_id: z.number().nullable().optional(),
is_public: z.boolean().optional(),
});

View file

@ -47,7 +47,7 @@ export const clonePublicChatRequest = z.object({
export const clonePublicChatResponse = z.object({
thread_id: z.number(),
search_space_id: z.number(),
workspace_id: z.number(),
});
// Type exports

View file

@ -7,25 +7,17 @@ const roleBase = z.object({
permissions: z.array(z.string()),
is_default: z.boolean(),
is_system_role: z.boolean(),
search_space_id: z.number(),
workspace_id: z.number(),
created_at: z.string(),
});
export const role = z.preprocess((value) => {
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
const record = value as Record<string, unknown>;
if (record.search_space_id === undefined && record.workspace_id !== undefined) {
return { ...record, search_space_id: record.workspace_id };
}
}
return value;
}, roleBase);
export const role = roleBase;
/**
* Create role
*/
export const createRoleRequest = z.object({
search_space_id: z.number(),
workspace_id: z.number(),
data: roleBase.pick({
name: true,
description: true,
@ -40,7 +32,7 @@ export const createRoleResponse = role;
* Get roles
*/
export const getRolesRequest = z.object({
search_space_id: z.number(),
workspace_id: z.number(),
});
export const getRolesResponse = z.array(role);
@ -49,7 +41,7 @@ export const getRolesResponse = z.array(role);
* Get role by ID
*/
export const getRoleByIdRequest = z.object({
search_space_id: z.number(),
workspace_id: z.number(),
role_id: z.number(),
});
@ -59,7 +51,7 @@ export const getRoleByIdResponse = role;
* Update role
*/
export const updateRoleRequest = z.object({
search_space_id: z.number(),
workspace_id: z.number(),
role_id: z.number(),
data: roleBase
.pick({
@ -77,7 +69,7 @@ export const updateRoleResponse = role;
* Delete role
*/
export const deleteRoleRequest = z.object({
search_space_id: z.number(),
workspace_id: z.number(),
role_id: z.number(),
});

View file

@ -8,7 +8,7 @@ export const purchaseStatusEnum = z.enum(["pending", "completed", "failed"]);
export const createCreditCheckoutSessionRequest = z.object({
quantity: z.number().int().min(1).max(10_000),
search_space_id: z.number().int().min(1),
workspace_id: z.number().int().min(1),
});
export const createCreditCheckoutSessionResponse = z.object({
@ -90,7 +90,7 @@ export const updateAutoReloadSettingsRequest = z.object({
});
export const createAutoReloadSetupSessionRequest = z.object({
search_space_id: z.number().int().min(1),
workspace_id: z.number().int().min(1),
});
export const createAutoReloadSetupSessionResponse = z.object({

View file

@ -12,7 +12,7 @@ export const videoPresentationListItem = z.object({
title: z.string(),
status: videoPresentationStatus.default("ready"),
created_at: z.string(),
search_space_id: z.number(),
workspace_id: z.number(),
thread_id: z.number().nullish(),
});
export type VideoPresentationListItem = z.infer<typeof videoPresentationListItem>;

Some files were not shown because too many files have changed in this diff Show more