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

@ -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" }
);