mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-08 22:22:17 +02:00
feat(workspace): refactor search space handling to workspace
This commit is contained in:
parent
e92b7a34ed
commit
c379ab1155
53 changed files with 268 additions and 169 deletions
|
|
@ -34,6 +34,7 @@ import { useElectronAPI } from "@/hooks/use-platform";
|
|||
import { documentsApiService } from "@/lib/apis/documents-api.service";
|
||||
import { getVirtualPathDisplay } from "@/lib/chat/virtual-path-display";
|
||||
import { type CitationUrlMap, preprocessCitationMarkdown } from "@/lib/citations/citation-parser";
|
||||
import { getWorkspaceIdNumber } from "@/lib/route-params";
|
||||
import { tryGetHostname } from "@/lib/url";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
|
|
@ -188,13 +189,7 @@ function FilePathLink({ path, className }: { path: string; className?: string })
|
|||
const openEditorPanel = useSetAtom(openEditorPanelAtom);
|
||||
const params = useParams();
|
||||
const electronAPI = useElectronAPI();
|
||||
const searchSpaceIdParam = params?.search_space_id;
|
||||
const parsedSearchSpaceId = Array.isArray(searchSpaceIdParam)
|
||||
? Number(searchSpaceIdParam[0])
|
||||
: Number(searchSpaceIdParam);
|
||||
const resolvedSearchSpaceId = Number.isFinite(parsedSearchSpaceId)
|
||||
? parsedSearchSpaceId
|
||||
: undefined;
|
||||
const resolvedSearchSpaceId = getWorkspaceIdNumber(params);
|
||||
|
||||
const { displayName, isFolder } = getVirtualPathDisplay(path);
|
||||
const icon = isFolder ? <FolderIcon className="size-3.5" /> : <FileIcon className="size-3.5" />;
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ import { useElectronAPI } from "@/hooks/use-platform";
|
|||
import { captureDisplayToPngDataUrl } from "@/lib/chat/display-media-capture";
|
||||
import { getMentionDocKey } from "@/lib/chat/mention-doc-key";
|
||||
import { slideoutOpenedTickAtom } from "@/lib/layout-events";
|
||||
import { getWorkspaceIdNumber } from "@/lib/route-params";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
DocumentMentionPicker,
|
||||
|
|
@ -458,7 +459,9 @@ const Composer: FC = () => {
|
|||
const prevMentionedDocsRef = useRef<Map<string, MentionedDocumentInfo>>(new Map());
|
||||
const documentPickerRef = useRef<DocumentMentionPickerRef>(null);
|
||||
const promptPickerRef = useRef<PromptPickerRef>(null);
|
||||
const { search_space_id, chat_id } = useParams();
|
||||
const params = useParams();
|
||||
const workspaceId = getWorkspaceIdNumber(params);
|
||||
const chat_id = params.chat_id;
|
||||
const aui = useAui();
|
||||
// Desktop-only auto-focus; on mobile, programmatic focus would
|
||||
// summon the soft keyboard on every picker close / thread switch.
|
||||
|
|
@ -824,7 +827,6 @@ const Composer: FC = () => {
|
|||
|
||||
const handleDocumentsMention = useCallback(
|
||||
(mentions: MentionedDocumentInfo[]) => {
|
||||
const parsedSearchSpaceId = Number(search_space_id);
|
||||
const editorMentionedDocs = editorRef.current?.getMentionedDocuments() ?? [];
|
||||
const editorDocKeys = new Set(editorMentionedDocs.map((doc) => getMentionDocKey(doc)));
|
||||
|
||||
|
|
@ -832,8 +834,8 @@ const Composer: FC = () => {
|
|||
const key = getMentionDocKey(mention);
|
||||
if (editorDocKeys.has(key)) continue;
|
||||
editorRef.current?.insertMentionChip(mention);
|
||||
if (Number.isFinite(parsedSearchSpaceId)) {
|
||||
promoteRecentMention(parsedSearchSpaceId, mention);
|
||||
if (workspaceId) {
|
||||
promoteRecentMention(workspaceId, mention);
|
||||
}
|
||||
// Track within the loop so a duplicate-in-batch can't double-insert.
|
||||
editorDocKeys.add(key);
|
||||
|
|
@ -844,7 +846,7 @@ const Composer: FC = () => {
|
|||
setMentionQuery("");
|
||||
setSuggestionAnchorPoint(null);
|
||||
},
|
||||
[search_space_id]
|
||||
[workspaceId]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -893,7 +895,7 @@ const Composer: FC = () => {
|
|||
<ComposerSuggestionPopoverContent side="top">
|
||||
<DocumentMentionPicker
|
||||
ref={documentPickerRef}
|
||||
searchSpaceId={Number(search_space_id)}
|
||||
searchSpaceId={workspaceId ?? 0}
|
||||
enableChatMentions
|
||||
currentChatId={threadId}
|
||||
onSelectionChange={handleDocumentsMention}
|
||||
|
|
@ -959,7 +961,7 @@ const Composer: FC = () => {
|
|||
</div>
|
||||
<ComposerAction
|
||||
isBlockedByOtherUser={isBlockedByOtherUser}
|
||||
searchSpaceId={Number(search_space_id)}
|
||||
searchSpaceId={workspaceId ?? 0}
|
||||
onChatModelSelected={handleChatModelSelected}
|
||||
/>
|
||||
<ConnectorIndicator showTrigger={false} />
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button
|
|||
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
||||
import { getMentionDocKey } from "@/lib/chat/mention-doc-key";
|
||||
import { parseMentionSegments } from "@/lib/chat/parse-mention-segments";
|
||||
import { getWorkspaceIdNumber } from "@/lib/route-params";
|
||||
|
||||
interface AuthorMetadata {
|
||||
displayName: string | null;
|
||||
|
|
@ -75,13 +76,7 @@ const UserTextPart: FC = () => {
|
|||
const openEditorPanel = useSetAtom(openEditorPanelAtom);
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const searchSpaceIdParam = params?.search_space_id;
|
||||
const parsedSearchSpaceId = Array.isArray(searchSpaceIdParam)
|
||||
? Number(searchSpaceIdParam[0])
|
||||
: Number(searchSpaceIdParam);
|
||||
const resolvedSearchSpaceId = Number.isFinite(parsedSearchSpaceId)
|
||||
? parsedSearchSpaceId
|
||||
: undefined;
|
||||
const resolvedSearchSpaceId = getWorkspaceIdNumber(params);
|
||||
|
||||
const handleOpenDoc = useCallback(
|
||||
(docId: number, title: string) => {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ interface MemoryReadResponse {
|
|||
function getMemoryPath(scope: MemoryScope, searchSpaceId?: number | null) {
|
||||
if (scope === "user") return "/api/v1/users/me/memory";
|
||||
if (!searchSpaceId) throw new Error("Missing search space context");
|
||||
return `/api/v1/searchspaces/${searchSpaceId}/memory`;
|
||||
return `/api/v1/workspaces/${searchSpaceId}/memory`;
|
||||
}
|
||||
|
||||
export function getMemoryLimitState(length: number, limits?: MemoryLimits | null) {
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ import { authenticatedFetch } from "@/lib/auth-fetch";
|
|||
import { getMentionDocKey } from "@/lib/chat/mention-doc-key";
|
||||
import { buildBackendUrl } from "@/lib/env-config";
|
||||
import { uploadFolderScan } from "@/lib/folder-sync-upload";
|
||||
import { getWorkspaceIdNumber } from "@/lib/route-params";
|
||||
import { getSupportedExtensionsSet } from "@/lib/supported-extensions";
|
||||
import { queries } from "@/zero/queries/index";
|
||||
import { SidebarSlideOutPanel } from "./SidebarSlideOutPanel";
|
||||
|
|
@ -228,7 +229,7 @@ function AuthenticatedDocumentsSidebarBase({
|
|||
const platformElectronAPI = useElectronAPI();
|
||||
const electronAPI = desktopFeaturesEnabled ? platformElectronAPI : null;
|
||||
const { etlService } = useRuntimeConfig();
|
||||
const searchSpaceId = Number(params.search_space_id);
|
||||
const searchSpaceId = getWorkspaceIdNumber(params) ?? 0;
|
||||
const setConnectorDialogOpen = useSetAtom(connectorDialogOpenAtom);
|
||||
const openEditorPanel = useSetAtom(openEditorPanelAtom);
|
||||
const { data: agentFlags } = useAtomValue(agentFlagsAtom);
|
||||
|
|
@ -828,7 +829,7 @@ function AuthenticatedDocumentsSidebarBase({
|
|||
const endpoint =
|
||||
doc.document_type === "USER_MEMORY"
|
||||
? buildBackendUrl("/api/v1/users/me/memory")
|
||||
: buildBackendUrl(`/api/v1/searchspaces/${searchSpaceId}/memory`);
|
||||
: buildBackendUrl(`/api/v1/workspaces/${searchSpaceId}/memory`);
|
||||
const response = await authenticatedFetch(endpoint, { method: "GET" });
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({ detail: "Export failed" }));
|
||||
|
|
@ -1038,7 +1039,7 @@ function AuthenticatedDocumentsSidebarBase({
|
|||
const endpoint =
|
||||
doc.document_type === "USER_MEMORY"
|
||||
? buildBackendUrl("/api/v1/users/me/memory/reset")
|
||||
: buildBackendUrl(`/api/v1/searchspaces/${searchSpaceId}/memory/reset`);
|
||||
: buildBackendUrl(`/api/v1/workspaces/${searchSpaceId}/memory/reset`);
|
||||
try {
|
||||
const response = await authenticatedFetch(endpoint, { method: "POST" });
|
||||
if (!response.ok) {
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ import { notificationsApiService } from "@/lib/apis/notifications-api.service";
|
|||
import { convertRenderedToDisplay } from "@/lib/comments/utils";
|
||||
import { getDocumentTypeLabel } from "@/lib/documents/document-type-labels";
|
||||
import { cacheKeys } from "@/lib/query-client/cache-keys";
|
||||
import { getWorkspaceIdNumber } from "@/lib/route-params";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { SidebarSlideOutPanel } from "./SidebarSlideOutPanel";
|
||||
|
||||
|
|
@ -165,7 +166,7 @@ export function InboxSidebarContent({
|
|||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const isMobile = !useMediaQuery("(min-width: 640px)");
|
||||
const searchSpaceId = params?.search_space_id ? Number(params.search_space_id) : null;
|
||||
const searchSpaceId = getWorkspaceIdNumber(params) ?? null;
|
||||
|
||||
const [, setTargetCommentId] = useAtom(setTargetCommentIdAtom);
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { Button } from "@/components/ui/button";
|
|||
import { Progress } from "@/components/ui/progress";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { useIsAnonymous } from "@/contexts/anonymous-mode";
|
||||
import { getWorkspaceIdParam } from "@/lib/route-params";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { SIDEBAR_MIN_WIDTH } from "../../hooks/useSidebarResize";
|
||||
import type { ChatItem, NavItem, PageUsage, SearchSpace, User } from "../../types/layout.types";
|
||||
|
|
@ -377,7 +378,7 @@ function SidebarUsageFooter({
|
|||
onNavigate?: () => void;
|
||||
}) {
|
||||
const params = useParams();
|
||||
const searchSpaceId = params?.search_space_id ?? "";
|
||||
const searchSpaceId = getWorkspaceIdParam(params) ?? "";
|
||||
const isAnonymous = useIsAnonymous();
|
||||
|
||||
if (isCollapsed) return null;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import {
|
|||
ComposerSuggestionSeparator,
|
||||
ComposerSuggestionSkeleton,
|
||||
} from "@/components/new-chat/composer-suggestion-popup";
|
||||
import { getWorkspaceIdParam } from "@/lib/route-params";
|
||||
|
||||
export interface PromptPickerRef {
|
||||
selectHighlighted: () => void;
|
||||
|
|
@ -69,9 +70,7 @@ export const PromptPicker = forwardRef<PromptPickerRef, PromptPickerProps>(funct
|
|||
|
||||
const createPromptIndex = filtered.length;
|
||||
const totalItems = filtered.length + 1;
|
||||
const searchSpaceId = Array.isArray(params?.search_space_id)
|
||||
? params.search_space_id[0]
|
||||
: params?.search_space_id;
|
||||
const searchSpaceId = getWorkspaceIdParam(params);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(index: number) => {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export function I18nProvider({ children }: { children: React.ReactNode }) {
|
|||
const { locale, messages } = useLocaleContext();
|
||||
|
||||
return (
|
||||
<NextIntlClientProvider messages={messages} locale={locale}>
|
||||
<NextIntlClientProvider messages={messages} locale={locale} timeZone="UTC">
|
||||
{children}
|
||||
</NextIntlClientProvider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { Spinner } from "@/components/ui/spinner";
|
|||
import { Switch } from "@/components/ui/switch";
|
||||
import { stripeApiService } from "@/lib/apis/stripe-api.service";
|
||||
import { AppError } from "@/lib/error";
|
||||
import { getWorkspaceIdNumber } from "@/lib/route-params";
|
||||
import { queries } from "@/zero/queries";
|
||||
|
||||
const microsToDollars = (micros: number | null | undefined): string => {
|
||||
|
|
@ -38,7 +39,7 @@ export function AutoReloadSettings() {
|
|||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const queryClient = useQueryClient();
|
||||
const searchSpaceId = Number(params?.search_space_id);
|
||||
const searchSpaceId = getWorkspaceIdNumber(params) ?? 0;
|
||||
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const [thresholdInput, setThresholdInput] = useState("");
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { Button } from "@/components/ui/button";
|
|||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { stripeApiService } from "@/lib/apis/stripe-api.service";
|
||||
import { AppError } from "@/lib/error";
|
||||
import { getWorkspaceIdNumber } from "@/lib/route-params";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { queries } from "@/zero/queries";
|
||||
|
||||
|
|
@ -37,7 +38,7 @@ const formatUsd = (micros: number) => {
|
|||
|
||||
export function BuyCreditsContent() {
|
||||
const params = useParams();
|
||||
const searchSpaceId = Number(params?.search_space_id);
|
||||
const searchSpaceId = getWorkspaceIdNumber(params) ?? 0;
|
||||
const [quantity, setQuantity] = useState(1);
|
||||
// Raw text of the amount field so the user can clear it while typing;
|
||||
// committed back to a clamped integer on blur.
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import {
|
|||
trackIncentiveTaskClicked,
|
||||
trackIncentiveTaskCompleted,
|
||||
} from "@/lib/posthog/events";
|
||||
import { getWorkspaceIdParam } from "@/lib/route-params";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
// Compact dollar label for a task's reward (e.g. "+$0.03").
|
||||
|
|
@ -32,7 +33,7 @@ const formatRewardUsd = (micros: number) => {
|
|||
export function EarnCreditsContent() {
|
||||
const params = useParams();
|
||||
const queryClient = useQueryClient();
|
||||
const searchSpaceId = params?.search_space_id ?? "";
|
||||
const searchSpaceId = getWorkspaceIdParam(params) ?? "";
|
||||
|
||||
useEffect(() => {
|
||||
trackIncentivePageViewed();
|
||||
|
|
|
|||
|
|
@ -126,6 +126,11 @@ export function ModelProviderConnectionsPanel({
|
|||
// Each provider connect form builds its own credential payload; the backend
|
||||
// resolver (`to_litellm`) forwards `extra.litellm_params` straight to LiteLLM.
|
||||
function handleCreate(draft: ConnectionDraft) {
|
||||
if (!Number.isFinite(searchSpaceId) || searchSpaceId <= 0) {
|
||||
toast.error("Workspace is still loading. Please try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
const models = connectionModelsForDraft(draft);
|
||||
const testModel = representativeTestModel(models);
|
||||
if (!testModel) {
|
||||
|
|
@ -165,6 +170,11 @@ export function ModelProviderConnectionsPanel({
|
|||
setProvider(providerId);
|
||||
setIsAddProviderOpen(true);
|
||||
if (providerId === "vertex_ai") {
|
||||
if (!Number.isFinite(searchSpaceId) || searchSpaceId <= 0) {
|
||||
toast.error("Workspace is still loading. Please try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
previewModels.mutate(
|
||||
{
|
||||
provider: providerId,
|
||||
|
|
@ -184,6 +194,11 @@ export function ModelProviderConnectionsPanel({
|
|||
}
|
||||
|
||||
function refreshConnectModels(draft: ConnectionDraft) {
|
||||
if (!Number.isFinite(searchSpaceId) || searchSpaceId <= 0) {
|
||||
toast.error("Workspace is still loading. Please try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
previewModels.mutate(
|
||||
{
|
||||
provider,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue