mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-08 22:22:17 +02:00
feat(rename): complete searchSpace to workspace transition across frontend and backend
- Introduced a comprehensive specification for renaming `searchSpace` to `workspace` in `surfsense_web` and `surfsense_desktop`, ensuring all TypeScript identifiers, React props, and local data structures are updated. - Implemented migration shims for persisted local state to prevent data loss during the transition. - Updated observability metrics and IPC channels to reflect the new naming convention. - Removed legacy `active-search-space` module and replaced it with `active-workspace` to maintain consistency. - Ensured no behavioral changes or data loss for users during the renaming process.
This commit is contained in:
parent
2a020629c5
commit
a8c1fb660d
259 changed files with 5480 additions and 2285 deletions
|
|
@ -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(activeWorkspaceIdAtom);
|
||||
const workspaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
const dbMessageId = parseMessageId(messageId);
|
||||
const commentsEnabled = useAtomValue(commentsEnabledAtom);
|
||||
|
||||
|
|
@ -520,7 +520,7 @@ export const AssistantMessage: FC = () => {
|
|||
const commentCount = commentsData?.total_count ?? 0;
|
||||
const hasComments = commentCount > 0;
|
||||
|
||||
const showCommentTrigger = searchSpaceId && commentsEnabled && !isMessageStreaming && dbMessageId;
|
||||
const showCommentTrigger = workspaceId && commentsEnabled && !isMessageStreaming && dbMessageId;
|
||||
|
||||
// Close floating panel when clicking outside (but not on portaled popover/dropdown content)
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -36,10 +36,10 @@ interface ConnectorIndicatorProps {
|
|||
|
||||
export const ConnectorIndicator = forwardRef<ConnectorIndicatorHandle, ConnectorIndicatorProps>(
|
||||
(_props, ref) => {
|
||||
const searchSpaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
const workspaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
|
||||
// Real-time document type counts via Zero (updates instantly as docs are indexed)
|
||||
const documentTypeCounts = useZeroDocumentTypeCounts(searchSpaceId);
|
||||
const documentTypeCounts = useZeroDocumentTypeCounts(workspaceId);
|
||||
// Read status inbox items from shared atom (populated by LayoutDataProvider)
|
||||
// instead of creating a duplicate useInbox("status") hook.
|
||||
const statusInboxItems = useAtomValue(statusInboxItemsAtom);
|
||||
|
|
@ -124,7 +124,7 @@ export const ConnectorIndicator = forwardRef<ConnectorIndicatorHandle, Connector
|
|||
loading: connectorsLoading,
|
||||
error: connectorsError,
|
||||
refreshConnectors: refreshConnectorsSync,
|
||||
} = useConnectorsSync(searchSpaceId);
|
||||
} = useConnectorsSync(workspaceId);
|
||||
|
||||
const useSyncData = connectorsFromSync.length > 0 || (connectorsLoading && !connectorsError);
|
||||
const connectors = useSyncData ? connectorsFromSync : allConnectors || [];
|
||||
|
|
@ -142,7 +142,7 @@ export const ConnectorIndicator = forwardRef<ConnectorIndicatorHandle, Connector
|
|||
inboxItems
|
||||
);
|
||||
|
||||
// Get document types that have documents in the search space
|
||||
// Get document types that have documents in the workspace
|
||||
const activeDocumentTypes = documentTypeCounts
|
||||
? Object.entries(documentTypeCounts).filter(([, count]) => count > 0)
|
||||
: [];
|
||||
|
|
@ -163,7 +163,7 @@ export const ConnectorIndicator = forwardRef<ConnectorIndicatorHandle, Connector
|
|||
open: () => handleOpenChange(true),
|
||||
}));
|
||||
|
||||
if (!searchSpaceId) return null;
|
||||
if (!workspaceId) return null;
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} modal={false} onOpenChange={handleOpenChange}>
|
||||
|
|
@ -191,8 +191,8 @@ export const ConnectorIndicator = forwardRef<ConnectorIndicatorHandle, Connector
|
|||
>
|
||||
<DialogTitle className="sr-only">Manage Connectors</DialogTitle>
|
||||
{/* YouTube Crawler View - shown when adding YouTube videos */}
|
||||
{isYouTubeView && searchSpaceId ? (
|
||||
<YouTubeCrawlerView searchSpaceId={searchSpaceId} onBack={handleBackFromYouTube} />
|
||||
{isYouTubeView && workspaceId ? (
|
||||
<YouTubeCrawlerView workspaceId={workspaceId} onBack={handleBackFromYouTube} />
|
||||
) : viewingMCPList ? (
|
||||
<ConnectorAccountsListView
|
||||
connectorType="MCP_CONNECTOR"
|
||||
|
|
@ -253,7 +253,7 @@ export const ConnectorIndicator = forwardRef<ConnectorIndicatorHandle, Connector
|
|||
isSaving={isSaving}
|
||||
isDisconnecting={isDisconnecting}
|
||||
isIndexing={indexingConnectorIds.has(editingConnector.id)}
|
||||
searchSpaceId={searchSpaceId?.toString()}
|
||||
workspaceId={workspaceId?.toString()}
|
||||
onStartDateChange={setStartDate}
|
||||
onEndDateChange={setEndDate}
|
||||
onPeriodicEnabledChange={setPeriodicEnabled}
|
||||
|
|
@ -346,7 +346,7 @@ export const ConnectorIndicator = forwardRef<ConnectorIndicatorHandle, Connector
|
|||
<TabsContent value="all" className="m-0">
|
||||
<AllConnectorsTab
|
||||
searchQuery={searchQuery}
|
||||
searchSpaceId={searchSpaceId}
|
||||
workspaceId={workspaceId}
|
||||
connectedTypes={connectedTypes}
|
||||
connectingId={connectingId}
|
||||
allConnectors={connectors}
|
||||
|
|
|
|||
|
|
@ -159,17 +159,17 @@ export const ObsidianConnectForm: FC<ConnectFormProps> = ({ onBack }) => {
|
|||
|
||||
<div className="h-px bg-border/60" />
|
||||
|
||||
{/* Step 4 — Pick search space */}
|
||||
{/* Step 4 — Pick workspace */}
|
||||
<article>
|
||||
<header className="mb-3 flex items-center gap-2">
|
||||
<div className="flex size-7 items-center justify-center rounded-md border border-slate-400/30 text-xs font-medium">
|
||||
4
|
||||
</div>
|
||||
<h3 className="text-sm font-medium sm:text-base">Pick this search space</h3>
|
||||
<h3 className="text-sm font-medium sm:text-base">Pick this workspace</h3>
|
||||
</header>
|
||||
<p className="text-[11px] text-muted-foreground sm:text-xs">
|
||||
In the plugin's <span className="font-medium">Search space</span> setting, choose the
|
||||
search space you want this vault to sync into. The connector will appear here
|
||||
workspace you want this vault to sync into. The connector will appear here
|
||||
automatically once the plugin makes its first sync.
|
||||
</p>
|
||||
</article>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export function getConnectorBenefits(connectorType: string): string[] | null {
|
|||
LINEAR_CONNECTOR: [
|
||||
"Search through all your Linear issues and comments",
|
||||
"Access issue titles, descriptions, and full discussion threads",
|
||||
"Connect your team's project management directly to your search space",
|
||||
"Connect your team's project management directly to your workspace",
|
||||
"Keep your search results up-to-date with latest Linear content",
|
||||
"Index your Linear issues for enhanced search capabilities",
|
||||
],
|
||||
|
|
@ -36,63 +36,63 @@ export function getConnectorBenefits(connectorType: string): string[] | null {
|
|||
SLACK_CONNECTOR: [
|
||||
"Search through all your Slack messages and conversations",
|
||||
"Access messages from public and private channels",
|
||||
"Connect your team's communications directly to your search space",
|
||||
"Connect your team's communications directly to your workspace",
|
||||
"Keep your search results up-to-date with latest Slack content",
|
||||
"Index your Slack conversations for enhanced search capabilities",
|
||||
],
|
||||
DISCORD_CONNECTOR: [
|
||||
"Search through all your Discord messages and conversations",
|
||||
"Access messages from all accessible channels",
|
||||
"Connect your community's communications directly to your search space",
|
||||
"Connect your community's communications directly to your workspace",
|
||||
"Keep your search results up-to-date with latest Discord content",
|
||||
"Index your Discord conversations for enhanced search capabilities",
|
||||
],
|
||||
NOTION_CONNECTOR: [
|
||||
"Search through all your Notion pages and databases",
|
||||
"Access page content, properties, and metadata",
|
||||
"Connect your knowledge base directly to your search space",
|
||||
"Connect your knowledge base directly to your workspace",
|
||||
"Keep your search results up-to-date with latest Notion content",
|
||||
"Index your Notion workspace for enhanced search capabilities",
|
||||
],
|
||||
CONFLUENCE_CONNECTOR: [
|
||||
"Search through all your Confluence pages and spaces",
|
||||
"Access page content, comments, and attachments",
|
||||
"Connect your team's documentation directly to your search space",
|
||||
"Connect your team's documentation directly to your workspace",
|
||||
"Keep your search results up-to-date with latest Confluence content",
|
||||
"Index your Confluence workspace for enhanced search capabilities",
|
||||
],
|
||||
BOOKSTACK_CONNECTOR: [
|
||||
"Search through all your BookStack pages and books",
|
||||
"Access page content, chapters, and documentation",
|
||||
"Connect your documentation directly to your search space",
|
||||
"Connect your documentation directly to your workspace",
|
||||
"Keep your search results up-to-date with latest BookStack content",
|
||||
"Index your BookStack instance for enhanced search capabilities",
|
||||
],
|
||||
GITHUB_CONNECTOR: [
|
||||
"Search through code, issues, and documentation from GitHub repositories",
|
||||
"Access repository content, pull requests, and discussions",
|
||||
"Connect your codebase directly to your search space",
|
||||
"Connect your codebase directly to your workspace",
|
||||
"Keep your search results up-to-date with latest GitHub content",
|
||||
"Index your GitHub repositories for enhanced search capabilities",
|
||||
],
|
||||
JIRA_CONNECTOR: [
|
||||
"Search through all your Jira issues and tickets",
|
||||
"Access issue descriptions, comments, and project data",
|
||||
"Connect your project management directly to your search space",
|
||||
"Connect your project management directly to your workspace",
|
||||
"Keep your search results up-to-date with latest Jira content",
|
||||
"Index your Jira projects for enhanced search capabilities",
|
||||
],
|
||||
CLICKUP_CONNECTOR: [
|
||||
"Search through all your ClickUp tasks and projects",
|
||||
"Access task descriptions, comments, and project data",
|
||||
"Connect your task management directly to your search space",
|
||||
"Connect your task management directly to your workspace",
|
||||
"Keep your search results up-to-date with latest ClickUp content",
|
||||
"Index your ClickUp workspace for enhanced search capabilities",
|
||||
],
|
||||
LUMA_CONNECTOR: [
|
||||
"Search through all your Luma events",
|
||||
"Access event details, descriptions, and attendee information",
|
||||
"Connect your events directly to your search space",
|
||||
"Connect your events directly to your workspace",
|
||||
"Keep your search results up-to-date with latest Luma content",
|
||||
"Index your Luma events for enhanced search capabilities",
|
||||
],
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ export const CirclebackConfig: FC<CirclebackConfigProps> = ({ connector, onNameC
|
|||
<AlertDescription>
|
||||
Configure this URL in Circleback Settings → Automations → Create automation → Send
|
||||
webhook request. The webhook will automatically send meeting notes, transcripts, and
|
||||
action items to this search space.
|
||||
action items to this workspace.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export interface ConnectorConfigProps {
|
|||
connector: SearchSourceConnector;
|
||||
onConfigChange?: (config: Record<string, unknown>) => void;
|
||||
onNameChange?: (name: string) => void;
|
||||
searchSpaceId?: string;
|
||||
workspaceId?: string;
|
||||
}
|
||||
|
||||
export type ConnectorConfigComponent = FC<ConnectorConfigProps>;
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ interface ConnectorEditViewProps {
|
|||
isSaving: boolean;
|
||||
isDisconnecting: boolean;
|
||||
isIndexing?: boolean;
|
||||
searchSpaceId?: string;
|
||||
workspaceId?: string;
|
||||
onStartDateChange: (date: Date | undefined) => void;
|
||||
onEndDateChange: (date: Date | undefined) => void;
|
||||
onPeriodicEnabledChange: (enabled: boolean) => void;
|
||||
|
|
@ -65,7 +65,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
|||
isSaving,
|
||||
isDisconnecting,
|
||||
isIndexing = false,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
onStartDateChange,
|
||||
onEndDateChange,
|
||||
onPeriodicEnabledChange,
|
||||
|
|
@ -78,7 +78,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
|||
onConfigChange,
|
||||
onNameChange,
|
||||
}) => {
|
||||
const searchSpaceIdAtom = useAtomValue(activeWorkspaceIdAtom);
|
||||
const workspaceIdAtom = useAtomValue(activeWorkspaceIdAtom);
|
||||
const isAuthExpired = connector.config?.auth_expired === true;
|
||||
const reauthEndpoint = getReauthEndpoint(connector);
|
||||
const [reauthing, setReauthing] = useState(false);
|
||||
|
|
@ -91,7 +91,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
|||
(connector.is_indexable || connector.connector_type === EnumConnectorName.OBSIDIAN_CONNECTOR);
|
||||
|
||||
const handleReauth = useCallback(async () => {
|
||||
const spaceId = searchSpaceId ?? searchSpaceIdAtom;
|
||||
const spaceId = workspaceId ?? workspaceIdAtom;
|
||||
if (!spaceId || !reauthEndpoint) return;
|
||||
setReauthing(true);
|
||||
try {
|
||||
|
|
@ -119,7 +119,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
|||
} finally {
|
||||
setReauthing(false);
|
||||
}
|
||||
}, [searchSpaceId, searchSpaceIdAtom, reauthEndpoint, connector.id]);
|
||||
}, [workspaceId, workspaceIdAtom, reauthEndpoint, connector.id]);
|
||||
|
||||
// Get connector-specific config component (MCP-backed connectors use a generic view)
|
||||
const ConnectorConfigComponent = useMemo(() => {
|
||||
|
|
@ -273,7 +273,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
|||
connector={connector}
|
||||
onConfigChange={onConfigChange}
|
||||
onNameChange={onNameChange}
|
||||
searchSpaceId={searchSpaceId}
|
||||
workspaceId={workspaceId}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ function clearOAuthResultCookie(): void {
|
|||
}
|
||||
|
||||
export const useConnectorDialog = () => {
|
||||
const searchSpaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
const workspaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
const { data: allConnectors, refetch: refetchAllConnectors } = useAtomValue(connectorsAtom);
|
||||
const { mutateAsync: indexConnector } = useAtomValue(indexConnectorMutationAtom);
|
||||
const { mutateAsync: updateConnector } = useAtomValue(updateConnectorMutationAtom);
|
||||
|
|
@ -140,7 +140,7 @@ export const useConnectorDialog = () => {
|
|||
|
||||
const handleAutoIndex = useCallback(
|
||||
async (connector: SearchSourceConnector, connectorTitle: string, connectorType: string) => {
|
||||
if (!searchSpaceId || isAutoIndexingRef.current) return;
|
||||
if (!workspaceId || isAutoIndexingRef.current) return;
|
||||
isAutoIndexingRef.current = true;
|
||||
|
||||
const defaults = AUTO_INDEX_DEFAULTS[connectorType];
|
||||
|
|
@ -165,13 +165,13 @@ export const useConnectorDialog = () => {
|
|||
await indexConnector({
|
||||
connector_id: connector.id,
|
||||
queryParams: {
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
start_date: format(startDate, "yyyy-MM-dd"),
|
||||
end_date: format(endDate, "yyyy-MM-dd"),
|
||||
},
|
||||
});
|
||||
|
||||
trackIndexWithDateRangeStarted(Number(searchSpaceId), connectorType, connector.id, {
|
||||
trackIndexWithDateRangeStarted(Number(workspaceId), connectorType, connector.id, {
|
||||
hasStartDate: true,
|
||||
hasEndDate: true,
|
||||
});
|
||||
|
|
@ -188,13 +188,13 @@ export const useConnectorDialog = () => {
|
|||
});
|
||||
} finally {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: cacheKeys.logs.summary(Number(searchSpaceId)),
|
||||
queryKey: cacheKeys.logs.summary(Number(workspaceId)),
|
||||
});
|
||||
await refetchAllConnectors();
|
||||
isAutoIndexingRef.current = false;
|
||||
}
|
||||
},
|
||||
[searchSpaceId, indexConnector, updateConnector, refetchAllConnectors]
|
||||
[workspaceId, indexConnector, updateConnector, refetchAllConnectors]
|
||||
);
|
||||
|
||||
// YouTube view state
|
||||
|
|
@ -206,7 +206,7 @@ export const useConnectorDialog = () => {
|
|||
// Consume OAuth result from cookie (set by /connectors/callback route handler)
|
||||
useEffect(() => {
|
||||
const raw = readOAuthResultCookie();
|
||||
if (!raw || !searchSpaceId) return;
|
||||
if (!raw || !workspaceId) return;
|
||||
clearOAuthResultCookie();
|
||||
|
||||
const result = parseOAuthCallbackResult(raw);
|
||||
|
|
@ -221,7 +221,7 @@ export const useConnectorDialog = () => {
|
|||
|
||||
if (oauthConnector) {
|
||||
trackConnectorSetupFailure(
|
||||
Number(searchSpaceId),
|
||||
Number(workspaceId),
|
||||
oauthConnector.connectorType,
|
||||
result.error,
|
||||
"oauth_callback"
|
||||
|
|
@ -292,7 +292,7 @@ export const useConnectorDialog = () => {
|
|||
const connectorValidation = searchSourceConnector.safeParse(newConnector);
|
||||
if (connectorValidation.success) {
|
||||
trackConnectorConnected(
|
||||
Number(searchSpaceId),
|
||||
Number(workspaceId),
|
||||
oauthConnector.connectorType,
|
||||
newConnector.id
|
||||
);
|
||||
|
|
@ -338,20 +338,20 @@ export const useConnectorDialog = () => {
|
|||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [searchSpaceId, handleAutoIndex, refetchAllConnectors, setIsOpen]);
|
||||
}, [workspaceId, handleAutoIndex, refetchAllConnectors, setIsOpen]);
|
||||
|
||||
// Handle OAuth connection
|
||||
const handleConnectOAuth = useCallback(
|
||||
async (connector: (typeof OAUTH_CONNECTORS)[number] | (typeof COMPOSIO_CONNECTORS)[number]) => {
|
||||
if (!searchSpaceId || !connector.authEndpoint) return;
|
||||
if (!workspaceId || !connector.authEndpoint) return;
|
||||
|
||||
// Set connecting state immediately to disable button and show spinner
|
||||
setConnectingId(connector.id);
|
||||
|
||||
trackConnectorSetupStarted(Number(searchSpaceId), connector.connectorType, "oauth_click");
|
||||
trackConnectorSetupStarted(Number(workspaceId), connector.connectorType, "oauth_click");
|
||||
|
||||
try {
|
||||
const url = buildBackendUrl(connector.authEndpoint, { space_id: searchSpaceId });
|
||||
const url = buildBackendUrl(connector.authEndpoint, { space_id: workspaceId });
|
||||
|
||||
const response = await authenticatedFetch(url, { method: "GET" });
|
||||
|
||||
|
|
@ -370,7 +370,7 @@ export const useConnectorDialog = () => {
|
|||
} catch (error) {
|
||||
console.error(`Error connecting to ${connector.title}:`, error);
|
||||
trackConnectorSetupFailure(
|
||||
Number(searchSpaceId),
|
||||
Number(workspaceId),
|
||||
connector.connectorType,
|
||||
error instanceof Error ? error.message : "oauth_initiation_failed",
|
||||
"oauth_init"
|
||||
|
|
@ -384,22 +384,22 @@ export const useConnectorDialog = () => {
|
|||
setConnectingId(null);
|
||||
}
|
||||
},
|
||||
[searchSpaceId]
|
||||
[workspaceId]
|
||||
);
|
||||
|
||||
// Handle creating YouTube crawler (not a connector, shows view in popup)
|
||||
const handleCreateYouTubeCrawler = useCallback(() => {
|
||||
if (!searchSpaceId) return;
|
||||
if (!workspaceId) return;
|
||||
setIsYouTubeView(true);
|
||||
}, [searchSpaceId]);
|
||||
}, [workspaceId]);
|
||||
|
||||
// Handle creating webcrawler connector
|
||||
const handleCreateWebcrawler = useCallback(async () => {
|
||||
if (!searchSpaceId) return;
|
||||
if (!workspaceId) return;
|
||||
|
||||
setConnectingId("webcrawler-connector");
|
||||
trackConnectorSetupStarted(
|
||||
Number(searchSpaceId),
|
||||
Number(workspaceId),
|
||||
EnumConnectorName.WEBCRAWLER_CONNECTOR,
|
||||
"webcrawler_quick_add"
|
||||
);
|
||||
|
|
@ -418,7 +418,7 @@ export const useConnectorDialog = () => {
|
|||
enable_vision_llm: false,
|
||||
},
|
||||
queryParams: {
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -433,7 +433,7 @@ export const useConnectorDialog = () => {
|
|||
if (connectorValidation.success) {
|
||||
// Track webcrawler connector connected
|
||||
trackConnectorConnected(
|
||||
Number(searchSpaceId),
|
||||
Number(workspaceId),
|
||||
EnumConnectorName.WEBCRAWLER_CONNECTOR,
|
||||
connector.id
|
||||
);
|
||||
|
|
@ -453,7 +453,7 @@ export const useConnectorDialog = () => {
|
|||
} catch (error) {
|
||||
console.error("Error creating webcrawler connector:", error);
|
||||
trackConnectorSetupFailure(
|
||||
Number(searchSpaceId),
|
||||
Number(workspaceId),
|
||||
EnumConnectorName.WEBCRAWLER_CONNECTOR,
|
||||
error instanceof Error ? error.message : "webcrawler_create_failed",
|
||||
"webcrawler_quick_add"
|
||||
|
|
@ -462,18 +462,18 @@ export const useConnectorDialog = () => {
|
|||
} finally {
|
||||
setConnectingId(null);
|
||||
}
|
||||
}, [searchSpaceId, createConnector, refetchAllConnectors, setIsOpen]);
|
||||
}, [workspaceId, createConnector, refetchAllConnectors, setIsOpen]);
|
||||
|
||||
// Handle connecting non-OAuth connectors (like Tavily API, Obsidian plugin, etc.)
|
||||
const handleConnectNonOAuth = useCallback(
|
||||
(connectorType: string) => {
|
||||
if (!searchSpaceId) return;
|
||||
if (!workspaceId) return;
|
||||
|
||||
trackConnectorSetupStarted(Number(searchSpaceId), connectorType, "non_oauth_click");
|
||||
trackConnectorSetupStarted(Number(workspaceId), connectorType, "non_oauth_click");
|
||||
|
||||
setConnectingConnectorType(connectorType);
|
||||
},
|
||||
[searchSpaceId]
|
||||
[workspaceId]
|
||||
);
|
||||
|
||||
// Handle submitting connect form
|
||||
|
|
@ -495,7 +495,7 @@ export const useConnectorDialog = () => {
|
|||
},
|
||||
onIndexingStart?: (connectorId: number) => void
|
||||
) => {
|
||||
if (!searchSpaceId || !connectingConnectorType) {
|
||||
if (!workspaceId || !connectingConnectorType) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -519,7 +519,7 @@ export const useConnectorDialog = () => {
|
|||
enable_vision_llm: false,
|
||||
},
|
||||
queryParams: {
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
},
|
||||
});
|
||||
// Refetch connectors to get the new one
|
||||
|
|
@ -536,7 +536,7 @@ export const useConnectorDialog = () => {
|
|||
const currentConnectorType = connectingConnectorType;
|
||||
|
||||
// Track connector connected event for non-OAuth connectors
|
||||
trackConnectorConnected(Number(searchSpaceId), currentConnectorType, connector.id);
|
||||
trackConnectorConnected(Number(workspaceId), currentConnectorType, connector.id);
|
||||
|
||||
// Find connector title from constants
|
||||
const connectorInfo = OTHER_CONNECTORS.find(
|
||||
|
|
@ -612,7 +612,7 @@ export const useConnectorDialog = () => {
|
|||
await indexConnector({
|
||||
connector_id: connector.id,
|
||||
queryParams: {
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
start_date: startDateStr,
|
||||
end_date: endDateStr,
|
||||
},
|
||||
|
|
@ -631,7 +631,7 @@ export const useConnectorDialog = () => {
|
|||
setIndexingConnectorConfig(null);
|
||||
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: cacheKeys.logs.summary(Number(searchSpaceId)),
|
||||
queryKey: cacheKeys.logs.summary(Number(workspaceId)),
|
||||
});
|
||||
|
||||
await refetchAllConnectors();
|
||||
|
|
@ -684,7 +684,7 @@ export const useConnectorDialog = () => {
|
|||
} catch (error) {
|
||||
console.error("Error creating connector:", error);
|
||||
trackConnectorSetupFailure(
|
||||
Number(searchSpaceId),
|
||||
Number(workspaceId),
|
||||
connectingConnectorType ?? formData.connector_type,
|
||||
error instanceof Error ? error.message : "connector_create_failed",
|
||||
"non_oauth_form"
|
||||
|
|
@ -698,7 +698,7 @@ export const useConnectorDialog = () => {
|
|||
},
|
||||
[
|
||||
connectingConnectorType,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
createConnector,
|
||||
refetchAllConnectors,
|
||||
updateConnector,
|
||||
|
|
@ -724,7 +724,7 @@ export const useConnectorDialog = () => {
|
|||
// Handle viewing accounts list for OAuth connector type
|
||||
const handleViewAccountsList = useCallback(
|
||||
(connectorType: string, _connectorTitle?: string) => {
|
||||
if (!searchSpaceId) return;
|
||||
if (!workspaceId) return;
|
||||
|
||||
const oauthConnector =
|
||||
OAUTH_CONNECTORS.find((c) => c.connectorType === connectorType) ||
|
||||
|
|
@ -736,7 +736,7 @@ export const useConnectorDialog = () => {
|
|||
});
|
||||
}
|
||||
},
|
||||
[searchSpaceId]
|
||||
[workspaceId]
|
||||
);
|
||||
|
||||
// Handle going back from accounts list view
|
||||
|
|
@ -746,9 +746,9 @@ export const useConnectorDialog = () => {
|
|||
|
||||
// Handle viewing MCP list
|
||||
const handleViewMCPList = useCallback(() => {
|
||||
if (!searchSpaceId) return;
|
||||
if (!workspaceId) return;
|
||||
setViewingMCPList(true);
|
||||
}, [searchSpaceId]);
|
||||
}, [workspaceId]);
|
||||
|
||||
// Handle going back from MCP list view
|
||||
const handleBackFromMCPList = useCallback(() => {
|
||||
|
|
@ -765,7 +765,7 @@ export const useConnectorDialog = () => {
|
|||
// Handle starting indexing
|
||||
const handleStartIndexing = useCallback(
|
||||
async (refreshConnectors: () => void) => {
|
||||
if (!indexingConfig || !searchSpaceId) return;
|
||||
if (!indexingConfig || !workspaceId) return;
|
||||
|
||||
// Validate date range (skip for Google Drive, Composio Drive, OneDrive, Dropbox, and Webcrawler)
|
||||
if (
|
||||
|
|
@ -847,7 +847,7 @@ export const useConnectorDialog = () => {
|
|||
await indexConnector({
|
||||
connector_id: indexingConfig.connectorId,
|
||||
queryParams: {
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
},
|
||||
body: {
|
||||
folders: selectedFolders || [],
|
||||
|
|
@ -870,14 +870,14 @@ export const useConnectorDialog = () => {
|
|||
await indexConnector({
|
||||
connector_id: indexingConfig.connectorId,
|
||||
queryParams: {
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await indexConnector({
|
||||
connector_id: indexingConfig.connectorId,
|
||||
queryParams: {
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
start_date: startDateStr,
|
||||
end_date: endDateStr,
|
||||
},
|
||||
|
|
@ -886,7 +886,7 @@ export const useConnectorDialog = () => {
|
|||
|
||||
// Track index with date range started event
|
||||
trackIndexWithDateRangeStarted(
|
||||
Number(searchSpaceId),
|
||||
Number(workspaceId),
|
||||
indexingConfig.connectorType,
|
||||
indexingConfig.connectorId,
|
||||
{
|
||||
|
|
@ -898,7 +898,7 @@ export const useConnectorDialog = () => {
|
|||
// Track periodic indexing started if enabled
|
||||
if (periodicEnabled) {
|
||||
trackPeriodicIndexingStarted(
|
||||
Number(searchSpaceId),
|
||||
Number(workspaceId),
|
||||
indexingConfig.connectorType,
|
||||
indexingConfig.connectorId,
|
||||
parseInt(frequencyMinutes, 10)
|
||||
|
|
@ -915,7 +915,7 @@ export const useConnectorDialog = () => {
|
|||
|
||||
refreshConnectors();
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: cacheKeys.logs.summary(Number(searchSpaceId)),
|
||||
queryKey: cacheKeys.logs.summary(Number(workspaceId)),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error starting indexing:", error);
|
||||
|
|
@ -926,7 +926,7 @@ export const useConnectorDialog = () => {
|
|||
},
|
||||
[
|
||||
indexingConfig,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
startDate,
|
||||
endDate,
|
||||
indexConnector,
|
||||
|
|
@ -951,7 +951,7 @@ export const useConnectorDialog = () => {
|
|||
// Handle starting edit mode
|
||||
const handleStartEdit = useCallback(
|
||||
(connector: SearchSourceConnector) => {
|
||||
if (!searchSpaceId) return;
|
||||
if (!workspaceId) return;
|
||||
|
||||
// For MCP connectors from "All Connectors" tab, show the list view instead of directly editing
|
||||
// (unless we're already in the MCP list view or on the Active tab where individual MCPs are shown)
|
||||
|
|
@ -986,11 +986,7 @@ export const useConnectorDialog = () => {
|
|||
|
||||
// Track index with date range opened event
|
||||
if (connector.is_indexable) {
|
||||
trackIndexWithDateRangeOpened(
|
||||
Number(searchSpaceId),
|
||||
connector.connector_type,
|
||||
connector.id
|
||||
);
|
||||
trackIndexWithDateRangeOpened(Number(workspaceId), connector.connector_type, connector.id);
|
||||
}
|
||||
|
||||
setEditingConnector(connector);
|
||||
|
|
@ -1001,13 +997,13 @@ export const useConnectorDialog = () => {
|
|||
setStartDate(undefined);
|
||||
setEndDate(undefined);
|
||||
},
|
||||
[searchSpaceId, viewingAccountsType, viewingMCPList, handleViewMCPList, activeTab]
|
||||
[workspaceId, viewingAccountsType, viewingMCPList, handleViewMCPList, activeTab]
|
||||
);
|
||||
|
||||
// Handle saving connector changes
|
||||
const handleSaveConnector = useCallback(
|
||||
async (refreshConnectors: () => void) => {
|
||||
if (!editingConnector || !searchSpaceId || isSaving) return;
|
||||
if (!editingConnector || !workspaceId || isSaving) return;
|
||||
|
||||
// Validate date range (skip for Google Drive/OneDrive/Dropbox which uses folder selection, Webcrawler which uses config, and non-indexable connectors)
|
||||
if (
|
||||
|
|
@ -1114,7 +1110,7 @@ export const useConnectorDialog = () => {
|
|||
await indexConnector({
|
||||
connector_id: editingConnector.id,
|
||||
queryParams: {
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
},
|
||||
body: {
|
||||
folders: selectedFolders || [],
|
||||
|
|
@ -1134,7 +1130,7 @@ export const useConnectorDialog = () => {
|
|||
await indexConnector({
|
||||
connector_id: editingConnector.id,
|
||||
queryParams: {
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
},
|
||||
});
|
||||
indexingDescription = "Re-indexing started with updated configuration.";
|
||||
|
|
@ -1143,7 +1139,7 @@ export const useConnectorDialog = () => {
|
|||
await indexConnector({
|
||||
connector_id: editingConnector.id,
|
||||
queryParams: {
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
start_date: startDateStr,
|
||||
end_date: endDateStr,
|
||||
},
|
||||
|
|
@ -1157,7 +1153,7 @@ export const useConnectorDialog = () => {
|
|||
(indexingDescription.includes("Re-indexing") || indexingDescription.includes("indexing"))
|
||||
) {
|
||||
trackIndexWithDateRangeStarted(
|
||||
Number(searchSpaceId),
|
||||
Number(workspaceId),
|
||||
editingConnector.connector_type,
|
||||
editingConnector.id,
|
||||
{
|
||||
|
|
@ -1170,7 +1166,7 @@ export const useConnectorDialog = () => {
|
|||
// Track periodic indexing if enabled
|
||||
if (periodicEnabled && editingConnector.is_indexable) {
|
||||
trackPeriodicIndexingStarted(
|
||||
Number(searchSpaceId),
|
||||
Number(workspaceId),
|
||||
editingConnector.connector_type,
|
||||
editingConnector.id,
|
||||
frequency || parseInt(frequencyMinutes, 10)
|
||||
|
|
@ -1190,7 +1186,7 @@ export const useConnectorDialog = () => {
|
|||
|
||||
refreshConnectors();
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: cacheKeys.logs.summary(Number(searchSpaceId)),
|
||||
queryKey: cacheKeys.logs.summary(Number(workspaceId)),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error saving connector:", error);
|
||||
|
|
@ -1201,7 +1197,7 @@ export const useConnectorDialog = () => {
|
|||
},
|
||||
[
|
||||
editingConnector,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
isSaving,
|
||||
startDate,
|
||||
endDate,
|
||||
|
|
@ -1220,7 +1216,7 @@ export const useConnectorDialog = () => {
|
|||
// Handle disconnecting connector
|
||||
const handleDisconnectConnector = useCallback(
|
||||
async (refreshConnectors: () => void) => {
|
||||
if (!editingConnector || !searchSpaceId) return;
|
||||
if (!editingConnector || !workspaceId) return;
|
||||
|
||||
setIsDisconnecting(true);
|
||||
try {
|
||||
|
|
@ -1230,7 +1226,7 @@ export const useConnectorDialog = () => {
|
|||
|
||||
// Track connector deleted event
|
||||
trackConnectorDeleted(
|
||||
Number(searchSpaceId),
|
||||
Number(workspaceId),
|
||||
editingConnector.connector_type,
|
||||
editingConnector.id
|
||||
);
|
||||
|
|
@ -1255,7 +1251,7 @@ export const useConnectorDialog = () => {
|
|||
|
||||
refreshConnectors();
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: cacheKeys.logs.summary(Number(searchSpaceId)),
|
||||
queryKey: cacheKeys.logs.summary(Number(workspaceId)),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error disconnecting connector:", error);
|
||||
|
|
@ -1264,7 +1260,7 @@ export const useConnectorDialog = () => {
|
|||
setIsDisconnecting(false);
|
||||
}
|
||||
},
|
||||
[editingConnector, searchSpaceId, deleteConnector, cameFromMCPList, setIsOpen]
|
||||
[editingConnector, workspaceId, deleteConnector, cameFromMCPList, setIsOpen]
|
||||
);
|
||||
|
||||
// Handle quick index (index with selected date range, or backend defaults if none selected)
|
||||
|
|
@ -1276,7 +1272,7 @@ export const useConnectorDialog = () => {
|
|||
startDate?: Date,
|
||||
endDate?: Date
|
||||
) => {
|
||||
if (!searchSpaceId) {
|
||||
if (!workspaceId) {
|
||||
if (stopIndexing) {
|
||||
stopIndexing(connectorId);
|
||||
}
|
||||
|
|
@ -1285,7 +1281,7 @@ export const useConnectorDialog = () => {
|
|||
|
||||
// Track quick index clicked event
|
||||
if (connectorType) {
|
||||
trackQuickIndexClicked(Number(searchSpaceId), connectorType, connectorId);
|
||||
trackQuickIndexClicked(Number(workspaceId), connectorType, connectorId);
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -1296,7 +1292,7 @@ export const useConnectorDialog = () => {
|
|||
await indexConnector({
|
||||
connector_id: connectorId,
|
||||
queryParams: {
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
start_date: startDateStr,
|
||||
end_date: endDateStr,
|
||||
},
|
||||
|
|
@ -1305,7 +1301,7 @@ export const useConnectorDialog = () => {
|
|||
|
||||
// Invalidate queries to refresh data
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: cacheKeys.logs.summary(Number(searchSpaceId)),
|
||||
queryKey: cacheKeys.logs.summary(Number(workspaceId)),
|
||||
});
|
||||
// Note: Don't call stopIndexing here - let useIndexingConnectors hook
|
||||
// detect when last_indexed_at changes via real-time sync
|
||||
|
|
@ -1318,7 +1314,7 @@ export const useConnectorDialog = () => {
|
|||
}
|
||||
}
|
||||
},
|
||||
[searchSpaceId, indexConnector]
|
||||
[workspaceId, indexConnector]
|
||||
);
|
||||
|
||||
// Handle going back from edit view
|
||||
|
|
@ -1406,7 +1402,7 @@ export const useConnectorDialog = () => {
|
|||
periodicEnabled,
|
||||
frequencyMinutes,
|
||||
enableVisionLlm,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
allConnectors,
|
||||
viewingAccountsType,
|
||||
viewingMCPList,
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export function getConnectorDisplayName(fullName: string): string {
|
|||
|
||||
interface AllConnectorsTabProps {
|
||||
searchQuery: string;
|
||||
searchSpaceId: string;
|
||||
workspaceId: string;
|
||||
connectedTypes: Set<string>;
|
||||
connectingId: string | null;
|
||||
allConnectors: SearchSourceConnector[] | undefined;
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
|||
isConnecting = false,
|
||||
addButtonText,
|
||||
}) => {
|
||||
const searchSpaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
const workspaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
const [reauthingId, setReauthingId] = useState<number | null>(null);
|
||||
const [confirmDisconnectId, setConfirmDisconnectId] = useState<number | null>(null);
|
||||
const [disconnectingId, setDisconnectingId] = useState<number | null>(null);
|
||||
|
|
@ -58,13 +58,13 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
|||
const handleReauth = useCallback(
|
||||
async (connector: SearchSourceConnector) => {
|
||||
const endpoint = getReauthEndpoint(connector);
|
||||
if (!searchSpaceId || !endpoint) return;
|
||||
if (!workspaceId || !endpoint) return;
|
||||
setReauthingId(connector.id);
|
||||
try {
|
||||
const response = await authenticatedFetch(
|
||||
buildBackendUrl(endpoint, {
|
||||
connector_id: connector.id,
|
||||
space_id: searchSpaceId,
|
||||
space_id: workspaceId,
|
||||
return_url: window.location.pathname,
|
||||
})
|
||||
);
|
||||
|
|
@ -86,7 +86,7 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
|||
setReauthingId(null);
|
||||
}
|
||||
},
|
||||
[searchSpaceId]
|
||||
[workspaceId]
|
||||
);
|
||||
|
||||
// Filter connectors to only show those of this type
|
||||
|
|
|
|||
|
|
@ -38,11 +38,11 @@ function extractYoutubeUrls(text: string): string[] {
|
|||
}
|
||||
|
||||
interface YouTubeCrawlerViewProps {
|
||||
searchSpaceId: string;
|
||||
workspaceId: string;
|
||||
onBack: () => void;
|
||||
}
|
||||
|
||||
export const YouTubeCrawlerView: FC<YouTubeCrawlerViewProps> = ({ searchSpaceId, onBack }) => {
|
||||
export const YouTubeCrawlerView: FC<YouTubeCrawlerViewProps> = ({ workspaceId, onBack }) => {
|
||||
const t = useTranslations("add_youtube");
|
||||
const [videoTags, setVideoTags] = useState<TagType[]>([]);
|
||||
const [activeTagIndex, setActiveTagIndex] = useState<number | null>(null);
|
||||
|
|
@ -165,7 +165,7 @@ export const YouTubeCrawlerView: FC<YouTubeCrawlerViewProps> = ({ searchSpaceId,
|
|||
{
|
||||
document_type: "YOUTUBE_VIDEO",
|
||||
content: videoUrls,
|
||||
workspace_id: parseInt(searchSpaceId, 10),
|
||||
workspace_id: parseInt(workspaceId, 10),
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
|
|
|
|||
|
|
@ -90,9 +90,9 @@ const DocumentUploadPopupContent: FC<{
|
|||
isOpen: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}> = ({ isOpen, onOpenChange }) => {
|
||||
const searchSpaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
const workspaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
|
||||
if (!searchSpaceId) return null;
|
||||
if (!workspaceId) return null;
|
||||
|
||||
const handleSuccess = () => {
|
||||
onOpenChange(false);
|
||||
|
|
@ -112,12 +112,12 @@ const DocumentUploadPopupContent: FC<{
|
|||
Upload Documents
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-xs sm:text-base text-muted-foreground/80 line-clamp-1">
|
||||
Upload and sync your documents to your search space
|
||||
Upload and sync your documents to your workspace
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="px-4 sm:px-6 pb-4 sm:pb-6">
|
||||
<DocumentUploadTab searchSpaceId={searchSpaceId} onSuccess={handleSuccess} />
|
||||
<DocumentUploadTab workspaceId={workspaceId} onSuccess={handleSuccess} />
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ function FilePathLink({ path, className }: { path: string; className?: string })
|
|||
const openEditorPanel = useSetAtom(openEditorPanelAtom);
|
||||
const params = useParams();
|
||||
const electronAPI = useElectronAPI();
|
||||
const resolvedSearchSpaceId = getWorkspaceIdNumber(params);
|
||||
const resolvedWorkspaceId = getWorkspaceIdNumber(params);
|
||||
|
||||
const { displayName, isFolder } = getVirtualPathDisplay(path);
|
||||
const icon = isFolder ? <FolderIcon className="size-3.5" /> : <FileIcon className="size-3.5" />;
|
||||
|
|
@ -204,7 +204,7 @@ function FilePathLink({ path, className }: { path: string; className?: string })
|
|||
if (electronAPI.getAgentFilesystemMounts) {
|
||||
try {
|
||||
const mounts = (await electronAPI.getAgentFilesystemMounts(
|
||||
resolvedSearchSpaceId
|
||||
resolvedWorkspaceId
|
||||
)) as AgentFilesystemMount[];
|
||||
resolvedLocalPath = normalizeLocalVirtualPathForEditor(path, mounts);
|
||||
} catch {
|
||||
|
|
@ -215,21 +215,21 @@ function FilePathLink({ path, className }: { path: string; className?: string })
|
|||
kind: "local_file",
|
||||
localFilePath: resolvedLocalPath,
|
||||
title: resolvedLocalPath.split("/").pop() || resolvedLocalPath,
|
||||
searchSpaceId: resolvedSearchSpaceId,
|
||||
workspaceId: resolvedWorkspaceId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!resolvedSearchSpaceId || !path.startsWith("/documents/")) return;
|
||||
if (!resolvedWorkspaceId || !path.startsWith("/documents/")) return;
|
||||
try {
|
||||
const doc = await documentsApiService.getDocumentByVirtualPath({
|
||||
workspace_id: resolvedSearchSpaceId,
|
||||
workspace_id: resolvedWorkspaceId,
|
||||
virtual_path: path,
|
||||
});
|
||||
openEditorPanel({
|
||||
kind: "document",
|
||||
documentId: doc.id,
|
||||
searchSpaceId: resolvedSearchSpaceId,
|
||||
workspaceId: resolvedWorkspaceId,
|
||||
title: doc.title,
|
||||
});
|
||||
} catch {
|
||||
|
|
@ -237,7 +237,7 @@ function FilePathLink({ path, className }: { path: string; className?: string })
|
|||
}
|
||||
})();
|
||||
},
|
||||
[electronAPI, openEditorPanel, path, resolvedSearchSpaceId]
|
||||
[electronAPI, openEditorPanel, path, resolvedWorkspaceId]
|
||||
);
|
||||
|
||||
// Folders cannot open in the editor panel — keep them as visual chips.
|
||||
|
|
|
|||
|
|
@ -895,7 +895,7 @@ const Composer: FC = () => {
|
|||
<ComposerSuggestionPopoverContent side="top">
|
||||
<DocumentMentionPicker
|
||||
ref={documentPickerRef}
|
||||
searchSpaceId={workspaceId ?? 0}
|
||||
workspaceId={workspaceId ?? 0}
|
||||
enableChatMentions
|
||||
currentChatId={threadId}
|
||||
onSelectionChange={handleDocumentsMention}
|
||||
|
|
@ -961,7 +961,7 @@ const Composer: FC = () => {
|
|||
</div>
|
||||
<ComposerAction
|
||||
isBlockedByOtherUser={isBlockedByOtherUser}
|
||||
searchSpaceId={workspaceId ?? 0}
|
||||
workspaceId={workspaceId ?? 0}
|
||||
onChatModelSelected={handleChatModelSelected}
|
||||
/>
|
||||
<ConnectorIndicator showTrigger={false} />
|
||||
|
|
@ -982,13 +982,13 @@ const Composer: FC = () => {
|
|||
|
||||
interface ComposerActionProps {
|
||||
isBlockedByOtherUser?: boolean;
|
||||
searchSpaceId: number;
|
||||
workspaceId: number;
|
||||
onChatModelSelected?: () => void;
|
||||
}
|
||||
|
||||
const ComposerAction: FC<ComposerActionProps> = ({
|
||||
isBlockedByOtherUser = false,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
onChatModelSelected,
|
||||
}) => {
|
||||
const mentionedDocuments = useAtomValue(mentionedDocumentsAtom);
|
||||
|
|
@ -1564,7 +1564,7 @@ const ComposerAction: FC<ComposerActionProps> = ({
|
|||
)}
|
||||
<div className="ml-auto flex min-w-0 shrink-0 items-center gap-2">
|
||||
<ChatHeader
|
||||
searchSpaceId={searchSpaceId}
|
||||
workspaceId={workspaceId}
|
||||
className="h-9 max-w-[44vw] px-2 sm:max-w-[220px] sm:px-3"
|
||||
onChatModelSelected={onChatModelSelected}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -76,33 +76,33 @@ const UserTextPart: FC = () => {
|
|||
const openEditorPanel = useSetAtom(openEditorPanelAtom);
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const resolvedSearchSpaceId = getWorkspaceIdNumber(params);
|
||||
const resolvedWorkspaceId = getWorkspaceIdNumber(params);
|
||||
|
||||
const handleOpenDoc = useCallback(
|
||||
(docId: number, title: string) => {
|
||||
if (!resolvedSearchSpaceId) {
|
||||
toast.error("Cannot open document outside a search space.");
|
||||
if (!resolvedWorkspaceId) {
|
||||
toast.error("Cannot open document outside a workspace.");
|
||||
return;
|
||||
}
|
||||
openEditorPanel({
|
||||
kind: "document",
|
||||
documentId: docId,
|
||||
searchSpaceId: resolvedSearchSpaceId,
|
||||
workspaceId: resolvedWorkspaceId,
|
||||
title,
|
||||
});
|
||||
},
|
||||
[openEditorPanel, resolvedSearchSpaceId]
|
||||
[openEditorPanel, resolvedWorkspaceId]
|
||||
);
|
||||
|
||||
const handleOpenThread = useCallback(
|
||||
(threadId: number) => {
|
||||
if (!resolvedSearchSpaceId) {
|
||||
toast.error("Cannot open chat outside a search space.");
|
||||
if (!resolvedWorkspaceId) {
|
||||
toast.error("Cannot open chat outside a workspace.");
|
||||
return;
|
||||
}
|
||||
router.push(`/dashboard/${resolvedSearchSpaceId}/new-chat/${threadId}`);
|
||||
router.push(`/dashboard/${resolvedWorkspaceId}/new-chat/${threadId}`);
|
||||
},
|
||||
[resolvedSearchSpaceId, router]
|
||||
[resolvedWorkspaceId, router]
|
||||
);
|
||||
|
||||
const segments = parseMentionSegments(text, mentionedDocs);
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ export const CitationPanelContent: FC<CitationPanelContentProps> = ({
|
|||
if (!data) return;
|
||||
openEditorPanel({
|
||||
documentId: data.id,
|
||||
searchSpaceId: data.workspace_id,
|
||||
workspaceId: data.workspace_id,
|
||||
title: data.title,
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export interface FolderDisplay {
|
|||
name: string;
|
||||
position: string;
|
||||
parentId: number | null;
|
||||
searchSpaceId: number;
|
||||
workspaceId: number;
|
||||
metadata?: Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ export function EditorPanelContent({
|
|||
documentId,
|
||||
localFilePath,
|
||||
memoryScope,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
title,
|
||||
onClose,
|
||||
}: {
|
||||
|
|
@ -154,7 +154,7 @@ export function EditorPanelContent({
|
|||
documentId?: number;
|
||||
localFilePath?: string;
|
||||
memoryScope?: "user" | "team";
|
||||
searchSpaceId?: number;
|
||||
workspaceId?: number;
|
||||
title: string | null;
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
|
|
@ -186,14 +186,14 @@ export function EditorPanelContent({
|
|||
}
|
||||
try {
|
||||
const mounts = (await electronAPI.getAgentFilesystemMounts(
|
||||
searchSpaceId
|
||||
workspaceId
|
||||
)) as AgentFilesystemMount[];
|
||||
return normalizeLocalVirtualPathForEditor(candidatePath, mounts);
|
||||
} catch {
|
||||
return candidatePath;
|
||||
}
|
||||
},
|
||||
[electronAPI, searchSpaceId]
|
||||
[electronAPI, workspaceId]
|
||||
);
|
||||
|
||||
const plateMaxBytes = editorDoc?.editor_plate_max_bytes ?? LARGE_DOCUMENT_THRESHOLD;
|
||||
|
|
@ -234,7 +234,7 @@ export function EditorPanelContent({
|
|||
const resolvedLocalPath = await resolveLocalVirtualPath(localFilePath);
|
||||
const readResult = await electronAPI.readAgentLocalFileText(
|
||||
resolvedLocalPath,
|
||||
searchSpaceId
|
||||
workspaceId
|
||||
);
|
||||
if (!readResult.ok) {
|
||||
throw new Error(readResult.error || "Failed to read local file");
|
||||
|
|
@ -257,7 +257,7 @@ export function EditorPanelContent({
|
|||
if (!memoryScope) throw new Error("Missing memory context");
|
||||
const { document, limits } = await fetchMemoryEditorDocument({
|
||||
scope: memoryScope,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
title,
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
|
@ -271,12 +271,12 @@ export function EditorPanelContent({
|
|||
return;
|
||||
}
|
||||
|
||||
if (!documentId || !searchSpaceId) {
|
||||
if (!documentId || !workspaceId) {
|
||||
throw new Error("Missing document context");
|
||||
}
|
||||
const response = await authenticatedFetch(
|
||||
buildBackendUrl(
|
||||
`/api/v1/workspaces/${searchSpaceId}/documents/${documentId}/editor-content`
|
||||
`/api/v1/workspaces/${workspaceId}/documents/${documentId}/editor-content`
|
||||
),
|
||||
{ method: "GET" }
|
||||
);
|
||||
|
|
@ -323,7 +323,7 @@ export function EditorPanelContent({
|
|||
localFilePath,
|
||||
memoryScope,
|
||||
resolveLocalVirtualPath,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
title,
|
||||
]);
|
||||
|
||||
|
|
@ -382,7 +382,7 @@ export function EditorPanelContent({
|
|||
const writeResult = await electronAPI.writeAgentLocalFileText(
|
||||
resolvedLocalPath,
|
||||
contentToSave,
|
||||
searchSpaceId
|
||||
workspaceId
|
||||
);
|
||||
if (!writeResult.ok) {
|
||||
throw new Error(writeResult.error || "Failed to save local file");
|
||||
|
|
@ -395,7 +395,7 @@ export function EditorPanelContent({
|
|||
if (!memoryScope) throw new Error("Missing memory context");
|
||||
const { markdown: savedContent, limits } = await saveMemoryMarkdown({
|
||||
scope: memoryScope,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
markdown: markdownRef.current,
|
||||
});
|
||||
markdownRef.current = savedContent;
|
||||
|
|
@ -408,11 +408,11 @@ export function EditorPanelContent({
|
|||
return true;
|
||||
}
|
||||
|
||||
if (!searchSpaceId || !documentId) {
|
||||
if (!workspaceId || !documentId) {
|
||||
throw new Error("Missing document context");
|
||||
}
|
||||
const response = await authenticatedFetch(
|
||||
buildBackendUrl(`/api/v1/workspaces/${searchSpaceId}/documents/${documentId}/save`),
|
||||
buildBackendUrl(`/api/v1/workspaces/${workspaceId}/documents/${documentId}/save`),
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
|
|
@ -460,7 +460,7 @@ export function EditorPanelContent({
|
|||
plateMaxBytes,
|
||||
plateMaxLines,
|
||||
resolveLocalVirtualPath,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
@ -515,12 +515,12 @@ export function EditorPanelContent({
|
|||
}, [editorDoc?.source_markdown]);
|
||||
|
||||
const handleDownloadMarkdown = useCallback(async () => {
|
||||
if (!searchSpaceId || !documentId) return;
|
||||
if (!workspaceId || !documentId) return;
|
||||
setDownloading(true);
|
||||
try {
|
||||
const response = await authenticatedFetch(
|
||||
buildBackendUrl(
|
||||
`/api/v1/workspaces/${searchSpaceId}/documents/${documentId}/download-markdown`
|
||||
`/api/v1/workspaces/${workspaceId}/documents/${documentId}/download-markdown`
|
||||
),
|
||||
{ method: "GET" }
|
||||
);
|
||||
|
|
@ -542,7 +542,7 @@ export function EditorPanelContent({
|
|||
} finally {
|
||||
setDownloading(false);
|
||||
}
|
||||
}, [documentId, editorDoc?.title, searchSpaceId]);
|
||||
}, [documentId, editorDoc?.title, workspaceId]);
|
||||
|
||||
const largeDocAlert = viewerMode === "monaco" && !isLocalFileMode && editorDoc && (
|
||||
<Alert className="m-4 shrink-0">
|
||||
|
|
@ -890,7 +890,7 @@ function DesktopEditorPanel() {
|
|||
|
||||
const hasTarget =
|
||||
panelState.kind === "document"
|
||||
? !!panelState.documentId && !!panelState.searchSpaceId
|
||||
? !!panelState.documentId && !!panelState.workspaceId
|
||||
: panelState.kind === "local_file"
|
||||
? !!panelState.localFilePath
|
||||
: !!panelState.memoryScope;
|
||||
|
|
@ -903,7 +903,7 @@ function DesktopEditorPanel() {
|
|||
documentId={panelState.documentId ?? undefined}
|
||||
localFilePath={panelState.localFilePath ?? undefined}
|
||||
memoryScope={panelState.memoryScope ?? undefined}
|
||||
searchSpaceId={panelState.searchSpaceId ?? undefined}
|
||||
workspaceId={panelState.workspaceId ?? undefined}
|
||||
title={panelState.title}
|
||||
onClose={closePanel}
|
||||
/>
|
||||
|
|
@ -919,7 +919,7 @@ function MobileEditorDrawer() {
|
|||
|
||||
const hasTarget =
|
||||
panelState.kind === "document"
|
||||
? !!panelState.documentId && !!panelState.searchSpaceId
|
||||
? !!panelState.documentId && !!panelState.workspaceId
|
||||
: !!panelState.memoryScope;
|
||||
if (!hasTarget) return null;
|
||||
|
||||
|
|
@ -943,7 +943,7 @@ function MobileEditorDrawer() {
|
|||
documentId={panelState.documentId ?? undefined}
|
||||
localFilePath={panelState.localFilePath ?? undefined}
|
||||
memoryScope={panelState.memoryScope ?? undefined}
|
||||
searchSpaceId={panelState.searchSpaceId ?? undefined}
|
||||
workspaceId={panelState.workspaceId ?? undefined}
|
||||
title={panelState.title}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -957,7 +957,7 @@ export function EditorPanel() {
|
|||
const isDesktop = useMediaQuery("(min-width: 1024px)");
|
||||
const hasTarget =
|
||||
panelState.kind === "document"
|
||||
? !!panelState.documentId && !!panelState.searchSpaceId
|
||||
? !!panelState.documentId && !!panelState.workspaceId
|
||||
: panelState.kind === "local_file"
|
||||
? !!panelState.localFilePath
|
||||
: !!panelState.memoryScope;
|
||||
|
|
@ -977,7 +977,7 @@ export function MobileEditorPanel() {
|
|||
const isDesktop = useMediaQuery("(min-width: 1024px)");
|
||||
const hasTarget =
|
||||
panelState.kind === "document"
|
||||
? !!panelState.documentId && !!panelState.searchSpaceId
|
||||
? !!panelState.documentId && !!panelState.workspaceId
|
||||
: panelState.kind === "local_file"
|
||||
? !!panelState.localFilePath
|
||||
: !!panelState.memoryScope;
|
||||
|
|
|
|||
|
|
@ -24,10 +24,10 @@ interface MemoryReadResponse {
|
|||
limits?: MemoryLimits;
|
||||
}
|
||||
|
||||
function getMemoryPath(scope: MemoryScope, searchSpaceId?: number | null) {
|
||||
function getMemoryPath(scope: MemoryScope, workspaceId?: number | null) {
|
||||
if (scope === "user") return "/api/v1/users/me/memory";
|
||||
if (!searchSpaceId) throw new Error("Missing search space context");
|
||||
return `/api/v1/workspaces/${searchSpaceId}/memory`;
|
||||
if (!workspaceId) throw new Error("Missing workspace context");
|
||||
return `/api/v1/workspaces/${workspaceId}/memory`;
|
||||
}
|
||||
|
||||
export function getMemoryLimitState(length: number, limits?: MemoryLimits | null) {
|
||||
|
|
@ -53,16 +53,16 @@ export function getMemoryLimitState(length: number, limits?: MemoryLimits | null
|
|||
|
||||
export async function fetchMemoryEditorDocument({
|
||||
scope,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
title,
|
||||
signal,
|
||||
}: {
|
||||
scope: MemoryScope;
|
||||
searchSpaceId?: number | null;
|
||||
workspaceId?: number | null;
|
||||
title?: string | null;
|
||||
signal?: AbortSignal;
|
||||
}) {
|
||||
const response = await authenticatedFetch(buildBackendUrl(getMemoryPath(scope, searchSpaceId)), {
|
||||
const response = await authenticatedFetch(buildBackendUrl(getMemoryPath(scope, workspaceId)), {
|
||||
method: "GET",
|
||||
signal,
|
||||
});
|
||||
|
|
@ -87,14 +87,14 @@ export async function fetchMemoryEditorDocument({
|
|||
|
||||
export async function saveMemoryMarkdown({
|
||||
scope,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
markdown,
|
||||
}: {
|
||||
scope: MemoryScope;
|
||||
searchSpaceId?: number | null;
|
||||
workspaceId?: number | null;
|
||||
markdown: string;
|
||||
}) {
|
||||
const response = await authenticatedFetch(buildBackendUrl(getMemoryPath(scope, searchSpaceId)), {
|
||||
const response = await authenticatedFetch(buildBackendUrl(getMemoryPath(scope, workspaceId)), {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ memory_md: markdown }),
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ export type {
|
|||
IconRailProps,
|
||||
NavItem,
|
||||
PageUsage,
|
||||
SearchSpace,
|
||||
SidebarSectionProps,
|
||||
User,
|
||||
Workspace,
|
||||
} from "./types/layout.types";
|
||||
export {
|
||||
ChatListItem,
|
||||
CreateSearchSpaceDialog,
|
||||
CreateWorkspaceDialog,
|
||||
CreditBalanceDisplay,
|
||||
Header,
|
||||
IconRail,
|
||||
|
|
@ -20,10 +20,10 @@ export {
|
|||
MobileSidebarTrigger,
|
||||
NavIcon,
|
||||
NavSection,
|
||||
SearchSpaceAvatar,
|
||||
Sidebar,
|
||||
SidebarCollapseButton,
|
||||
SidebarHeader,
|
||||
SidebarSection,
|
||||
SidebarUserProfile,
|
||||
WorkspaceAvatar,
|
||||
} from "./ui";
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@ import { useLoginGate } from "@/contexts/login-gate";
|
|||
import { useAnnouncements } from "@/hooks/use-announcements";
|
||||
import { useIsMobile } from "@/hooks/use-mobile";
|
||||
import { anonymousChatApiService } from "@/lib/apis/anonymous-chat-api.service";
|
||||
import type { ChatItem, NavItem, PageUsage, SearchSpace } from "../types/layout.types";
|
||||
import type { ChatItem, NavItem, PageUsage, Workspace } from "../types/layout.types";
|
||||
import { LayoutShell } from "../ui/shell";
|
||||
|
||||
interface FreeLayoutDataProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const GUEST_SPACE: SearchSpace = {
|
||||
const GUEST_SPACE: Workspace = {
|
||||
id: 0,
|
||||
name: "SurfSense Free",
|
||||
description: "Free AI chat without login",
|
||||
|
|
@ -94,19 +94,16 @@ export function FreeLayoutDataProvider({ children }: FreeLayoutDataProviderProps
|
|||
|
||||
const handleAnnouncements = useCallback(() => gate("see what's new"), [gate]);
|
||||
|
||||
const handleSearchSpaceSelect = useCallback(
|
||||
(_id: number) => gate("switch search spaces"),
|
||||
[gate]
|
||||
);
|
||||
const handleWorkspaceSelect = useCallback((_id: number) => gate("switch workspaces"), [gate]);
|
||||
|
||||
return (
|
||||
<LayoutShell
|
||||
searchSpaces={[GUEST_SPACE]}
|
||||
activeSearchSpaceId={0}
|
||||
onSearchSpaceSelect={handleSearchSpaceSelect}
|
||||
onSearchSpaceSettings={gatedAction("search space settings")}
|
||||
onAddSearchSpace={gatedAction("create search spaces")}
|
||||
searchSpace={GUEST_SPACE}
|
||||
workspaces={[GUEST_SPACE]}
|
||||
activeWorkspaceId={0}
|
||||
onWorkspaceSelect={handleWorkspaceSelect}
|
||||
onWorkspaceSettings={gatedAction("workspace settings")}
|
||||
onAddWorkspace={gatedAction("create workspaces")}
|
||||
workspace={GUEST_SPACE}
|
||||
navItems={navItems}
|
||||
onNavItemClick={handleNavItemClick}
|
||||
chats={[]}
|
||||
|
|
@ -121,7 +118,7 @@ export function FreeLayoutDataProvider({ children }: FreeLayoutDataProviderProps
|
|||
email: "Guest",
|
||||
name: "Guest",
|
||||
}}
|
||||
onSettings={gatedAction("search space settings")}
|
||||
onSettings={gatedAction("workspace settings")}
|
||||
onManageMembers={gatedAction("team management")}
|
||||
onUserSettings={gatedAction("account settings")}
|
||||
onAnnouncements={handleAnnouncements}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ import { announcementsDialogAtom } from "@/atoms/layout/dialogs.atom";
|
|||
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 { deleteWorkspaceMutationAtom } from "@/atoms/workspaces/workspace-mutation.atoms";
|
||||
import { workspacesAtom } 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";
|
||||
|
|
@ -47,17 +47,17 @@ 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/workspaces-api.service";
|
||||
import { workspacesApiService } 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";
|
||||
import { cacheKeys } from "@/lib/query-client/cache-keys";
|
||||
import type { ChatItem, NavItem, SearchSpace } from "../types/layout.types";
|
||||
import { CreateSearchSpaceDialog } from "../ui/dialogs";
|
||||
import type { ChatItem, NavItem, Workspace } from "../types/layout.types";
|
||||
import { CreateWorkspaceDialog } from "../ui/dialogs";
|
||||
import { LayoutShell } from "../ui/shell";
|
||||
|
||||
interface LayoutDataProviderProps {
|
||||
searchSpaceId: string;
|
||||
workspaceId: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ function formatInboxCount(count: number): string {
|
|||
return `${thousands}k+`;
|
||||
}
|
||||
|
||||
export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProviderProps) {
|
||||
export function LayoutDataProvider({ workspaceId, children }: LayoutDataProviderProps) {
|
||||
const t = useTranslations("dashboard");
|
||||
const tCommon = useTranslations("common");
|
||||
const tSidebar = useTranslations("sidebar");
|
||||
|
|
@ -88,19 +88,19 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
// Atoms
|
||||
const { data: user } = useAtomValue(currentUserAtom);
|
||||
const {
|
||||
data: searchSpacesData,
|
||||
refetch: refetchSearchSpaces,
|
||||
isSuccess: searchSpacesLoaded,
|
||||
} = useAtomValue(searchSpacesAtom);
|
||||
const { mutateAsync: deleteSearchSpace } = useAtomValue(deleteSearchSpaceMutationAtom);
|
||||
data: workspacesData,
|
||||
refetch: refetchWorkspaces,
|
||||
isSuccess: workspacesLoaded,
|
||||
} = useAtomValue(workspacesAtom);
|
||||
const { mutateAsync: deleteWorkspace } = useAtomValue(deleteWorkspaceMutationAtom);
|
||||
const currentThreadState = useAtomValue(currentThreadAtom);
|
||||
const resetCurrentThread = useSetAtom(resetCurrentThreadAtom);
|
||||
const syncChatTab = useSetAtom(syncChatTabAtom);
|
||||
const removeChatTab = useSetAtom(removeChatTabAtom);
|
||||
const { activateChatThread, prefetchChatThread } = useActivateChatThread();
|
||||
const { mutateAsync: archiveThread } = useArchiveThread(searchSpaceId);
|
||||
const { mutateAsync: deleteThread } = useDeleteThread(searchSpaceId);
|
||||
const { mutateAsync: renameThread } = useRenameThread(searchSpaceId);
|
||||
const { mutateAsync: archiveThread } = useArchiveThread(workspaceId);
|
||||
const { mutateAsync: deleteThread } = useDeleteThread(workspaceId);
|
||||
const { mutateAsync: renameThread } = useRenameThread(workspaceId);
|
||||
|
||||
// Key used to force-remount the page component (e.g. after deleting the active chat
|
||||
// when the router is out of sync due to replaceState)
|
||||
|
|
@ -113,16 +113,16 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
|
||||
// Fetch current workspace as a fallback for the selector while the full list catches up.
|
||||
const { data: currentWorkspace } = useQuery({
|
||||
queryKey: cacheKeys.searchSpaces.detail(searchSpaceId),
|
||||
queryFn: () => searchSpacesApiService.getSearchSpace({ id: Number(searchSpaceId) }),
|
||||
enabled: !!searchSpaceId,
|
||||
queryKey: cacheKeys.workspaces.detail(workspaceId),
|
||||
queryFn: () => workspacesApiService.getWorkspace({ id: Number(workspaceId) }),
|
||||
enabled: !!workspaceId,
|
||||
});
|
||||
|
||||
// Fetch threads (40 total to allow up to 20 per section - shared/private)
|
||||
const { data: threadsData, isPending: isLoadingThreads } = useQuery({
|
||||
queryKey: ["threads", searchSpaceId, { limit: 40 }],
|
||||
queryFn: () => fetchThreads(Number(searchSpaceId), 40),
|
||||
enabled: !!searchSpaceId,
|
||||
queryKey: ["threads", workspaceId, { limit: 40 }],
|
||||
queryFn: () => fetchThreads(Number(workspaceId), 40),
|
||||
enabled: !!workspaceId,
|
||||
});
|
||||
|
||||
// Unified slide-out panel state (only one can be open at a time)
|
||||
|
|
@ -147,10 +147,10 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
}, [setIsDocumentsSidebarOpen]);
|
||||
|
||||
// Search space dialog state
|
||||
const [isCreateSearchSpaceDialogOpen, setIsCreateSearchSpaceDialogOpen] = useState(false);
|
||||
const [isCreateWorkspaceDialogOpen, setIsCreateWorkspaceDialogOpen] = useState(false);
|
||||
|
||||
const userId = user?.id ? String(user.id) : null;
|
||||
const numericSpaceId = Number(searchSpaceId) || null;
|
||||
const numericSpaceId = Number(workspaceId) || null;
|
||||
|
||||
// Batch-fetch unread counts for all categories in a single request
|
||||
// instead of 2 separate /unread-count calls.
|
||||
|
|
@ -218,11 +218,11 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
icon: <AlertTriangle className="h-5 w-5 text-amber-500" />,
|
||||
action: {
|
||||
label: "Buy credits",
|
||||
onClick: () => router.push(`/dashboard/${searchSpaceId}/buy-more`),
|
||||
onClick: () => router.push(`/dashboard/${workspaceId}/buy-more`),
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [statusInbox.inboxItems, statusInbox.loading, searchSpaceId, router]);
|
||||
}, [statusInbox.inboxItems, statusInbox.loading, workspaceId, router]);
|
||||
|
||||
// Delete dialogs state
|
||||
const [showDeleteChatDialog, setShowDeleteChatDialog] = useState(false);
|
||||
|
|
@ -235,28 +235,28 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
const [newChatTitle, setNewChatTitle] = useState("");
|
||||
const [isRenamingChat, setIsRenamingChat] = useState(false);
|
||||
|
||||
// Delete/Leave search space dialog state
|
||||
const [showDeleteSearchSpaceDialog, setShowDeleteSearchSpaceDialog] = useState(false);
|
||||
const [showLeaveSearchSpaceDialog, setShowLeaveSearchSpaceDialog] = useState(false);
|
||||
const [searchSpaceToDelete, setSearchSpaceToDelete] = useState<SearchSpace | null>(null);
|
||||
const [searchSpaceToLeave, setSearchSpaceToLeave] = useState<SearchSpace | null>(null);
|
||||
const [isDeletingSearchSpace, setIsDeletingSearchSpace] = useState(false);
|
||||
const [isLeavingSearchSpace, setIsLeavingSearchSpace] = useState(false);
|
||||
// Delete/Leave workspace dialog state
|
||||
const [showDeleteWorkspaceDialog, setShowDeleteWorkspaceDialog] = useState(false);
|
||||
const [showLeaveWorkspaceDialog, setShowLeaveWorkspaceDialog] = useState(false);
|
||||
const [workspaceToDelete, setWorkspaceToDelete] = useState<Workspace | null>(null);
|
||||
const [workspaceToLeave, setWorkspaceToLeave] = useState<Workspace | null>(null);
|
||||
const [isDeletingWorkspace, setIsDeletingWorkspace] = useState(false);
|
||||
const [isLeavingWorkspace, setIsLeavingWorkspace] = useState(false);
|
||||
|
||||
// Reset transient slide-out panels when switching search spaces.
|
||||
// Reset transient slide-out panels when switching workspaces.
|
||||
// Tabs intentionally persist across spaces — opening tabs from multiple
|
||||
// search spaces is a supported flow (browser-tab semantics).
|
||||
const prevSearchSpaceIdRef = useRef(searchSpaceId);
|
||||
// workspaces is a supported flow (browser-tab semantics).
|
||||
const prevWorkspaceIdRef = useRef(workspaceId);
|
||||
useEffect(() => {
|
||||
if (prevSearchSpaceIdRef.current !== searchSpaceId) {
|
||||
prevSearchSpaceIdRef.current = searchSpaceId;
|
||||
if (prevWorkspaceIdRef.current !== workspaceId) {
|
||||
prevWorkspaceIdRef.current = workspaceId;
|
||||
setActiveSlideoutPanel(null);
|
||||
}
|
||||
}, [searchSpaceId]);
|
||||
}, [workspaceId]);
|
||||
|
||||
const searchSpaces: SearchSpace[] = useMemo(() => {
|
||||
if (!searchSpacesData || !Array.isArray(searchSpacesData)) return [];
|
||||
return searchSpacesData.map((space) => ({
|
||||
const workspaces: Workspace[] = useMemo(() => {
|
||||
if (!workspacesData || !Array.isArray(workspacesData)) return [];
|
||||
return workspacesData.map((space) => ({
|
||||
id: space.id,
|
||||
name: space.name,
|
||||
description: space.description,
|
||||
|
|
@ -264,15 +264,15 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
memberCount: space.member_count || 0,
|
||||
createdAt: space.created_at,
|
||||
}));
|
||||
}, [searchSpacesData]);
|
||||
}, [workspacesData]);
|
||||
|
||||
// Find active workspace from list, falling back to the route-scoped detail query.
|
||||
const activeSearchSpace: SearchSpace | null = useMemo(() => {
|
||||
if (!searchSpaceId) return null;
|
||||
const searchSpaceIdNumber = Number(searchSpaceId);
|
||||
const listedSpace = searchSpaces.find((s) => s.id === searchSpaceIdNumber);
|
||||
const activeWorkspace: Workspace | null = useMemo(() => {
|
||||
if (!workspaceId) return null;
|
||||
const workspaceIdNumber = Number(workspaceId);
|
||||
const listedSpace = workspaces.find((s) => s.id === workspaceIdNumber);
|
||||
if (listedSpace) return listedSpace;
|
||||
if (!currentWorkspace || currentWorkspace.id !== searchSpaceIdNumber) return null;
|
||||
if (!currentWorkspace || currentWorkspace.id !== workspaceIdNumber) return null;
|
||||
return {
|
||||
id: currentWorkspace.id,
|
||||
name: currentWorkspace.name,
|
||||
|
|
@ -281,25 +281,24 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
memberCount: 0,
|
||||
createdAt: currentWorkspace.created_at,
|
||||
};
|
||||
}, [currentWorkspace, searchSpaceId, searchSpaces]);
|
||||
}, [currentWorkspace, workspaceId, workspaces]);
|
||||
|
||||
// Safety redirect: if the current search space is no longer in the user's list
|
||||
// Safety redirect: if the current workspace is no longer in the user's list
|
||||
// (e.g. deleted by background task, membership revoked), redirect to a valid space.
|
||||
useEffect(() => {
|
||||
if (!searchSpacesLoaded || !searchSpaceId || isDeletingSearchSpace || isLeavingSearchSpace)
|
||||
return;
|
||||
if (searchSpaces.length > 0 && !activeSearchSpace) {
|
||||
router.replace(`/dashboard/${searchSpaces[0].id}/new-chat`);
|
||||
} else if (searchSpaces.length === 0 && searchSpacesLoaded && !activeSearchSpace) {
|
||||
if (!workspacesLoaded || !workspaceId || isDeletingWorkspace || isLeavingWorkspace) return;
|
||||
if (workspaces.length > 0 && !activeWorkspace) {
|
||||
router.replace(`/dashboard/${workspaces[0].id}/new-chat`);
|
||||
} else if (workspaces.length === 0 && workspacesLoaded && !activeWorkspace) {
|
||||
router.replace("/dashboard");
|
||||
}
|
||||
}, [
|
||||
searchSpacesLoaded,
|
||||
searchSpaceId,
|
||||
searchSpaces,
|
||||
activeSearchSpace,
|
||||
isDeletingSearchSpace,
|
||||
isLeavingSearchSpace,
|
||||
workspacesLoaded,
|
||||
workspaceId,
|
||||
workspaces,
|
||||
activeWorkspace,
|
||||
isDeletingWorkspace,
|
||||
isLeavingWorkspace,
|
||||
router,
|
||||
]);
|
||||
|
||||
|
|
@ -307,18 +306,18 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
useEffect(() => {
|
||||
const chatId = currentChatId ?? null;
|
||||
const chatUrl = chatId
|
||||
? `/dashboard/${searchSpaceId}/new-chat/${chatId}`
|
||||
: `/dashboard/${searchSpaceId}/new-chat`;
|
||||
? `/dashboard/${workspaceId}/new-chat/${chatId}`
|
||||
: `/dashboard/${workspaceId}/new-chat`;
|
||||
const thread = threadsData?.threads?.find((t) => t.id === chatId);
|
||||
syncChatTab({
|
||||
chatId,
|
||||
// Avoid overwriting live SSE-updated tab titles with fallback values.
|
||||
title: chatId ? (thread?.title ?? undefined) : "New Chat",
|
||||
chatUrl,
|
||||
searchSpaceId: Number(searchSpaceId),
|
||||
workspaceId: Number(workspaceId),
|
||||
...(thread?.visibility !== undefined ? { visibility: thread.visibility } : {}),
|
||||
});
|
||||
}, [currentChatId, searchSpaceId, threadsData?.threads, syncChatTab]);
|
||||
}, [currentChatId, workspaceId, threadsData?.threads, syncChatTab]);
|
||||
|
||||
const chats = useMemo(() => {
|
||||
if (!threadsData?.threads) return [];
|
||||
|
|
@ -326,12 +325,12 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
return threadsData.threads.map<ChatItem>((thread) => ({
|
||||
id: thread.id,
|
||||
name: thread.title || `Chat ${thread.id}`,
|
||||
url: `/dashboard/${searchSpaceId}/new-chat/${thread.id}`,
|
||||
url: `/dashboard/${workspaceId}/new-chat/${thread.id}`,
|
||||
visibility: thread.visibility,
|
||||
isOwnThread: thread.is_own_thread,
|
||||
archived: thread.archived,
|
||||
}));
|
||||
}, [threadsData, searchSpaceId]);
|
||||
}, [threadsData, workspaceId]);
|
||||
|
||||
// Navigation items
|
||||
// Inbox, Automations, and Artifacts are rendered explicitly below "New chat"
|
||||
|
|
@ -352,13 +351,13 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
},
|
||||
{
|
||||
title: "Automations",
|
||||
url: `/dashboard/${searchSpaceId}/automations`,
|
||||
url: `/dashboard/${workspaceId}/automations`,
|
||||
icon: AlarmClock,
|
||||
isActive: isAutomationsActive,
|
||||
},
|
||||
{
|
||||
title: "Artifacts",
|
||||
url: `/dashboard/${searchSpaceId}/artifacts`,
|
||||
url: `/dashboard/${workspaceId}/artifacts`,
|
||||
icon: Boxes,
|
||||
isActive: isArtifactsActive,
|
||||
},
|
||||
|
|
@ -377,63 +376,63 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
isInboxSidebarOpen,
|
||||
isDocumentsSidebarOpen,
|
||||
totalUnreadCount,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
isAutomationsActive,
|
||||
isArtifactsActive,
|
||||
]
|
||||
);
|
||||
|
||||
// Handlers
|
||||
const handleSearchSpaceSelect = useCallback(
|
||||
const handleWorkspaceSelect = useCallback(
|
||||
(id: number) => {
|
||||
router.push(`/dashboard/${id}/new-chat`);
|
||||
},
|
||||
[router]
|
||||
);
|
||||
|
||||
const handleAddSearchSpace = useCallback(() => {
|
||||
setIsCreateSearchSpaceDialogOpen(true);
|
||||
const handleAddWorkspace = useCallback(() => {
|
||||
setIsCreateWorkspaceDialogOpen(true);
|
||||
}, []);
|
||||
|
||||
const setAnnouncementsDialog = useSetAtom(announcementsDialogAtom);
|
||||
|
||||
const handleUserSettings = useCallback(() => {
|
||||
router.push(`/dashboard/${searchSpaceId}/user-settings/profile`);
|
||||
}, [router, searchSpaceId]);
|
||||
router.push(`/dashboard/${workspaceId}/user-settings/profile`);
|
||||
}, [router, workspaceId]);
|
||||
|
||||
const handleAnnouncements = useCallback(() => {
|
||||
setAnnouncementsDialog(true);
|
||||
}, [setAnnouncementsDialog]);
|
||||
|
||||
const handleSearchSpaceSettings = useCallback(
|
||||
(space: SearchSpace) => {
|
||||
const handleWorkspaceSettings = useCallback(
|
||||
(space: Workspace) => {
|
||||
router.push(`/dashboard/${space.id}/workspace-settings`);
|
||||
},
|
||||
[router]
|
||||
);
|
||||
|
||||
const handleSearchSpaceDeleteClick = useCallback((space: SearchSpace) => {
|
||||
const handleWorkspaceDeleteClick = useCallback((space: Workspace) => {
|
||||
// If user is owner, show delete dialog; otherwise show leave dialog
|
||||
if (space.isOwner) {
|
||||
setSearchSpaceToDelete(space);
|
||||
setShowDeleteSearchSpaceDialog(true);
|
||||
setWorkspaceToDelete(space);
|
||||
setShowDeleteWorkspaceDialog(true);
|
||||
} else {
|
||||
setSearchSpaceToLeave(space);
|
||||
setShowLeaveSearchSpaceDialog(true);
|
||||
setWorkspaceToLeave(space);
|
||||
setShowLeaveWorkspaceDialog(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const confirmDeleteSearchSpace = useCallback(async () => {
|
||||
if (!searchSpaceToDelete) return;
|
||||
setIsDeletingSearchSpace(true);
|
||||
const confirmDeleteWorkspace = useCallback(async () => {
|
||||
if (!workspaceToDelete) return;
|
||||
setIsDeletingWorkspace(true);
|
||||
try {
|
||||
await deleteSearchSpace({ id: searchSpaceToDelete.id });
|
||||
await deleteWorkspace({ id: workspaceToDelete.id });
|
||||
|
||||
const isCurrentSpace = Number(searchSpaceId) === searchSpaceToDelete.id;
|
||||
const isCurrentSpace = Number(workspaceId) === workspaceToDelete.id;
|
||||
|
||||
// Await refetch so we have the freshest list (backend now hides [DELETING] spaces)
|
||||
const result = await refetchSearchSpaces();
|
||||
const updatedSpaces = (result.data ?? []).filter((s) => s.id !== searchSpaceToDelete.id);
|
||||
const result = await refetchWorkspaces();
|
||||
const updatedSpaces = (result.data ?? []).filter((s) => s.id !== workspaceToDelete.id);
|
||||
|
||||
if (isCurrentSpace) {
|
||||
if (updatedSpaces.length > 0) {
|
||||
|
|
@ -443,27 +442,27 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error deleting search space:", error);
|
||||
console.error("Error deleting workspace:", error);
|
||||
toast.error(
|
||||
t.has("delete_space_error") ? t("delete_space_error") : "Failed to delete search space"
|
||||
t.has("delete_space_error") ? t("delete_space_error") : "Failed to delete workspace"
|
||||
);
|
||||
} finally {
|
||||
setIsDeletingSearchSpace(false);
|
||||
setShowDeleteSearchSpaceDialog(false);
|
||||
setSearchSpaceToDelete(null);
|
||||
setIsDeletingWorkspace(false);
|
||||
setShowDeleteWorkspaceDialog(false);
|
||||
setWorkspaceToDelete(null);
|
||||
}
|
||||
}, [searchSpaceToDelete, deleteSearchSpace, refetchSearchSpaces, searchSpaceId, router, t]);
|
||||
}, [workspaceToDelete, deleteWorkspace, refetchWorkspaces, workspaceId, router, t]);
|
||||
|
||||
const confirmLeaveSearchSpace = useCallback(async () => {
|
||||
if (!searchSpaceToLeave) return;
|
||||
setIsLeavingSearchSpace(true);
|
||||
const confirmLeaveWorkspace = useCallback(async () => {
|
||||
if (!workspaceToLeave) return;
|
||||
setIsLeavingWorkspace(true);
|
||||
try {
|
||||
await searchSpacesApiService.leaveSearchSpace(searchSpaceToLeave.id);
|
||||
await workspacesApiService.leaveWorkspace(workspaceToLeave.id);
|
||||
|
||||
const isCurrentSpace = Number(searchSpaceId) === searchSpaceToLeave.id;
|
||||
const isCurrentSpace = Number(workspaceId) === workspaceToLeave.id;
|
||||
|
||||
const result = await refetchSearchSpaces();
|
||||
const updatedSpaces = (result.data ?? []).filter((s) => s.id !== searchSpaceToLeave.id);
|
||||
const result = await refetchWorkspaces();
|
||||
const updatedSpaces = (result.data ?? []).filter((s) => s.id !== workspaceToLeave.id);
|
||||
|
||||
if (isCurrentSpace) {
|
||||
if (updatedSpaces.length > 0) {
|
||||
|
|
@ -473,14 +472,14 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error leaving search space:", error);
|
||||
toast.error(t.has("leave_error") ? t("leave_error") : "Failed to leave search space");
|
||||
console.error("Error leaving workspace:", error);
|
||||
toast.error(t.has("leave_error") ? t("leave_error") : "Failed to leave workspace");
|
||||
} finally {
|
||||
setIsLeavingSearchSpace(false);
|
||||
setShowLeaveSearchSpaceDialog(false);
|
||||
setSearchSpaceToLeave(null);
|
||||
setIsLeavingWorkspace(false);
|
||||
setShowLeaveWorkspaceDialog(false);
|
||||
setWorkspaceToLeave(null);
|
||||
}
|
||||
}, [searchSpaceToLeave, refetchSearchSpaces, searchSpaceId, router, t]);
|
||||
}, [workspaceToLeave, refetchWorkspaces, workspaceId, router, t]);
|
||||
|
||||
const handleTabSwitch = useCallback(
|
||||
(tab: Tab) => {
|
||||
|
|
@ -489,14 +488,14 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
id: tab.chatId ?? null,
|
||||
title: tab.title,
|
||||
url: tab.chatUrl,
|
||||
searchSpaceId: tab.searchSpaceId ?? searchSpaceId,
|
||||
workspaceId: tab.workspaceId ?? workspaceId,
|
||||
...(tab.visibility !== undefined ? { visibility: tab.visibility } : {}),
|
||||
...(tab.hasComments !== undefined ? { hasComments: tab.hasComments } : {}),
|
||||
});
|
||||
}
|
||||
// Document tabs are handled in-place by LayoutShell — no navigation needed
|
||||
},
|
||||
[activateChatThread, searchSpaceId]
|
||||
[activateChatThread, workspaceId]
|
||||
);
|
||||
|
||||
const handleTabPrefetch = useCallback(
|
||||
|
|
@ -542,15 +541,15 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
if (isOutOfSync) {
|
||||
resetCurrentThread();
|
||||
// Immediately set the browser URL so the page remounts with a clean /new-chat path
|
||||
window.history.replaceState(null, "", `/dashboard/${searchSpaceId}/new-chat`);
|
||||
window.history.replaceState(null, "", `/dashboard/${workspaceId}/new-chat`);
|
||||
// Force-remount the page component to reset all React state synchronously
|
||||
setChatResetKey((k) => k + 1);
|
||||
// Sync Next.js router internals so useParams/usePathname stay correct going forward
|
||||
router.replace(`/dashboard/${searchSpaceId}/new-chat`);
|
||||
router.replace(`/dashboard/${workspaceId}/new-chat`);
|
||||
} else {
|
||||
router.push(`/dashboard/${searchSpaceId}/new-chat`);
|
||||
router.push(`/dashboard/${workspaceId}/new-chat`);
|
||||
}
|
||||
}, [router, searchSpaceId, currentThreadState.id, params?.chat_id, resetCurrentThread]);
|
||||
}, [router, workspaceId, currentThreadState.id, params?.chat_id, resetCurrentThread]);
|
||||
|
||||
const handleChatSelect = useCallback(
|
||||
(chat: ChatItem) => {
|
||||
|
|
@ -558,11 +557,11 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
id: chat.id,
|
||||
title: chat.name,
|
||||
url: chat.url,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
...(chat.visibility !== undefined ? { visibility: chat.visibility } : {}),
|
||||
});
|
||||
},
|
||||
[activateChatThread, searchSpaceId]
|
||||
[activateChatThread, workspaceId]
|
||||
);
|
||||
|
||||
const handleChatDelete = useCallback((chat: ChatItem) => {
|
||||
|
|
@ -595,12 +594,12 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
);
|
||||
|
||||
const handleSettings = useCallback(() => {
|
||||
router.push(`/dashboard/${searchSpaceId}/workspace-settings`);
|
||||
}, [router, searchSpaceId]);
|
||||
router.push(`/dashboard/${workspaceId}/workspace-settings`);
|
||||
}, [router, workspaceId]);
|
||||
|
||||
const handleManageMembers = useCallback(() => {
|
||||
router.push(`/dashboard/${searchSpaceId}/team`);
|
||||
}, [router, searchSpaceId]);
|
||||
router.push(`/dashboard/${workspaceId}/team`);
|
||||
}, [router, workspaceId]);
|
||||
|
||||
const handleLogout = useCallback(async () => {
|
||||
try {
|
||||
|
|
@ -634,7 +633,7 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
id: fallbackTab.chatId ?? null,
|
||||
title: fallbackTab.title,
|
||||
url: fallbackTab.chatUrl,
|
||||
searchSpaceId: fallbackTab.searchSpaceId ?? searchSpaceId,
|
||||
workspaceId: fallbackTab.workspaceId ?? workspaceId,
|
||||
...(fallbackTab.visibility !== undefined ? { visibility: fallbackTab.visibility } : {}),
|
||||
...(fallbackTab.hasComments !== undefined
|
||||
? { hasComments: fallbackTab.hasComments }
|
||||
|
|
@ -643,10 +642,10 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
} else {
|
||||
const isOutOfSync = currentThreadState.id !== null && !params?.chat_id;
|
||||
if (isOutOfSync) {
|
||||
window.history.replaceState(null, "", `/dashboard/${searchSpaceId}/new-chat`);
|
||||
window.history.replaceState(null, "", `/dashboard/${workspaceId}/new-chat`);
|
||||
setChatResetKey((k) => k + 1);
|
||||
} else {
|
||||
router.push(`/dashboard/${searchSpaceId}/new-chat`);
|
||||
router.push(`/dashboard/${workspaceId}/new-chat`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -660,7 +659,7 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
}, [
|
||||
chatToDelete,
|
||||
deleteThread,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
resetCurrentThread,
|
||||
currentChatId,
|
||||
currentThreadState.id,
|
||||
|
|
@ -695,7 +694,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("/workspace-settings") === true;
|
||||
const isWorkspaceSettingsPage = pathname?.includes("/workspace-settings") === true;
|
||||
const isTeamPage = pathname?.endsWith("/team") === true;
|
||||
const isAutomationsPage = pathname?.includes("/automations") === true;
|
||||
const isArtifactsPage = pathname?.endsWith("/artifacts") === true;
|
||||
|
|
@ -703,15 +702,15 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
const handleViewAllChats = useCallback(() => {
|
||||
setActiveSlideoutPanel(null);
|
||||
router.push(
|
||||
isAllChatsPage ? `/dashboard/${searchSpaceId}/new-chat` : `/dashboard/${searchSpaceId}/chats`
|
||||
isAllChatsPage ? `/dashboard/${workspaceId}/new-chat` : `/dashboard/${workspaceId}/chats`
|
||||
);
|
||||
}, [isAllChatsPage, router, searchSpaceId]);
|
||||
}, [isAllChatsPage, router, workspaceId]);
|
||||
const useWorkspacePanel =
|
||||
pathname?.endsWith("/buy-more") === true ||
|
||||
pathname?.endsWith("/earn-credits") === true ||
|
||||
pathname?.endsWith("/more-pages") === true ||
|
||||
isUserSettingsPage ||
|
||||
isSearchSpaceSettingsPage ||
|
||||
isWorkspaceSettingsPage ||
|
||||
isTeamPage ||
|
||||
isAutomationsPage ||
|
||||
isArtifactsPage ||
|
||||
|
|
@ -720,13 +719,13 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
return (
|
||||
<>
|
||||
<LayoutShell
|
||||
searchSpaces={searchSpaces}
|
||||
activeSearchSpaceId={Number(searchSpaceId)}
|
||||
onSearchSpaceSelect={handleSearchSpaceSelect}
|
||||
onSearchSpaceDelete={handleSearchSpaceDeleteClick}
|
||||
onSearchSpaceSettings={handleSearchSpaceSettings}
|
||||
onAddSearchSpace={handleAddSearchSpace}
|
||||
searchSpace={activeSearchSpace}
|
||||
workspaces={workspaces}
|
||||
activeWorkspaceId={Number(workspaceId)}
|
||||
onWorkspaceSelect={handleWorkspaceSelect}
|
||||
onWorkspaceDelete={handleWorkspaceDeleteClick}
|
||||
onWorkspaceSettings={handleWorkspaceSettings}
|
||||
onAddWorkspace={handleAddWorkspace}
|
||||
workspace={activeWorkspace}
|
||||
navItems={navItems}
|
||||
onNavItemClick={handleNavItemClick}
|
||||
chats={chats}
|
||||
|
|
@ -756,7 +755,7 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
useWorkspacePanel={useWorkspacePanel}
|
||||
workspacePanelViewportClassName={
|
||||
isUserSettingsPage ||
|
||||
isSearchSpaceSettingsPage ||
|
||||
isWorkspaceSettingsPage ||
|
||||
isTeamPage ||
|
||||
isAutomationsPage ||
|
||||
isArtifactsPage ||
|
||||
|
|
@ -769,7 +768,7 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
? "max-w-none select-none"
|
||||
: isAllChatsPage
|
||||
? "max-w-5xl"
|
||||
: isUserSettingsPage || isSearchSpaceSettingsPage || isTeamPage || isArtifactsPage
|
||||
: isUserSettingsPage || isWorkspaceSettingsPage || isTeamPage || isArtifactsPage
|
||||
? "max-w-5xl"
|
||||
: undefined
|
||||
}
|
||||
|
|
@ -888,66 +887,64 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Delete Search Space Dialog */}
|
||||
<AlertDialog open={showDeleteSearchSpaceDialog} onOpenChange={setShowDeleteSearchSpaceDialog}>
|
||||
{/* Delete Workspace Dialog */}
|
||||
<AlertDialog open={showDeleteWorkspaceDialog} onOpenChange={setShowDeleteWorkspaceDialog}>
|
||||
<AlertDialogContent className="sm:max-w-md">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{t("delete_search_space")}</AlertDialogTitle>
|
||||
<AlertDialogTitle>{t("delete_workspace")}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t("delete_space_confirm", { name: searchSpaceToDelete?.name || "" })}
|
||||
{t("delete_space_confirm", { name: workspaceToDelete?.name || "" })}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={isDeletingSearchSpace}>
|
||||
<AlertDialogCancel disabled={isDeletingWorkspace}>
|
||||
{tCommon("cancel")}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
confirmDeleteSearchSpace();
|
||||
confirmDeleteWorkspace();
|
||||
}}
|
||||
disabled={isDeletingSearchSpace}
|
||||
disabled={isDeletingWorkspace}
|
||||
className="relative bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
<span className={isDeletingSearchSpace ? "opacity-0" : ""}>{tCommon("delete")}</span>
|
||||
{isDeletingSearchSpace && <Spinner size="sm" className="absolute" />}
|
||||
<span className={isDeletingWorkspace ? "opacity-0" : ""}>{tCommon("delete")}</span>
|
||||
{isDeletingWorkspace && <Spinner size="sm" className="absolute" />}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{/* Leave Search Space Dialog */}
|
||||
<AlertDialog open={showLeaveSearchSpaceDialog} onOpenChange={setShowLeaveSearchSpaceDialog}>
|
||||
{/* Leave Workspace Dialog */}
|
||||
<AlertDialog open={showLeaveWorkspaceDialog} onOpenChange={setShowLeaveWorkspaceDialog}>
|
||||
<AlertDialogContent className="sm:max-w-md">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{t("leave_title")}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t("leave_confirm", { name: searchSpaceToLeave?.name || "" })}
|
||||
{t("leave_confirm", { name: workspaceToLeave?.name || "" })}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={isLeavingSearchSpace}>
|
||||
{tCommon("cancel")}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogCancel disabled={isLeavingWorkspace}>{tCommon("cancel")}</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
confirmLeaveSearchSpace();
|
||||
confirmLeaveWorkspace();
|
||||
}}
|
||||
disabled={isLeavingSearchSpace}
|
||||
disabled={isLeavingWorkspace}
|
||||
className="relative bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
<span className={isLeavingSearchSpace ? "opacity-0" : ""}>{t("leave")}</span>
|
||||
{isLeavingSearchSpace && <Spinner size="sm" className="absolute" />}
|
||||
<span className={isLeavingWorkspace ? "opacity-0" : ""}>{t("leave")}</span>
|
||||
{isLeavingWorkspace && <Spinner size="sm" className="absolute" />}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{/* Create Search Space Dialog */}
|
||||
<CreateSearchSpaceDialog
|
||||
open={isCreateSearchSpaceDialogOpen}
|
||||
onOpenChange={setIsCreateSearchSpaceDialogOpen}
|
||||
{/* Create Workspace Dialog */}
|
||||
<CreateWorkspaceDialog
|
||||
open={isCreateWorkspaceDialogOpen}
|
||||
onOpenChange={setIsCreateWorkspaceDialogOpen}
|
||||
/>
|
||||
|
||||
<AnnouncementsDialog />
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { LucideIcon } from "lucide-react";
|
||||
import type { DocumentsProcessingStatus } from "@/hooks/use-documents-processing";
|
||||
|
||||
export interface SearchSpace {
|
||||
export interface Workspace {
|
||||
id: number;
|
||||
name: string;
|
||||
description?: string | null;
|
||||
|
|
@ -41,15 +41,15 @@ export interface PageUsage {
|
|||
}
|
||||
|
||||
export interface IconRailProps {
|
||||
searchSpaces: SearchSpace[];
|
||||
activeSearchSpaceId: number | null;
|
||||
onSearchSpaceSelect: (id: number) => void;
|
||||
onAddSearchSpace: () => void;
|
||||
workspaces: Workspace[];
|
||||
activeWorkspaceId: number | null;
|
||||
onWorkspaceSelect: (id: number) => void;
|
||||
onAddWorkspace: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface SidebarHeaderProps {
|
||||
searchSpace: SearchSpace | null;
|
||||
workspace: Workspace | null;
|
||||
onSettings?: () => void;
|
||||
}
|
||||
|
||||
|
|
@ -71,23 +71,23 @@ export interface ChatsSectionProps {
|
|||
onChatSelect: (chat: ChatItem) => void;
|
||||
onChatDelete?: (chat: ChatItem) => void;
|
||||
onViewAllChats?: () => void;
|
||||
searchSpaceId?: string;
|
||||
workspaceId?: string;
|
||||
}
|
||||
|
||||
export interface SidebarUserProfileProps {
|
||||
user: User;
|
||||
searchSpaceId?: string;
|
||||
workspaceId?: string;
|
||||
onSettings?: () => void;
|
||||
onManageMembers?: () => void;
|
||||
onSwitchSearchSpace?: () => void;
|
||||
onSwitchWorkspace?: () => void;
|
||||
onToggleTheme?: () => void;
|
||||
onLogout?: () => void;
|
||||
theme?: string;
|
||||
}
|
||||
|
||||
export interface SidebarProps {
|
||||
searchSpace: SearchSpace | null;
|
||||
searchSpaceId?: string;
|
||||
workspace: Workspace | null;
|
||||
workspaceId?: string;
|
||||
navItems: NavItem[];
|
||||
chats: ChatItem[];
|
||||
activeChatId?: number | null;
|
||||
|
|
@ -106,10 +106,10 @@ export interface SidebarProps {
|
|||
}
|
||||
|
||||
export interface LayoutShellProps {
|
||||
searchSpaces: SearchSpace[];
|
||||
activeSearchSpaceId: number | null;
|
||||
onSearchSpaceSelect: (id: number) => void;
|
||||
onAddSearchSpace: () => void;
|
||||
workspaces: Workspace[];
|
||||
activeWorkspaceId: number | null;
|
||||
onWorkspaceSelect: (id: number) => void;
|
||||
onAddWorkspace: () => void;
|
||||
sidebarProps: Omit<SidebarProps, "className">;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
|
|
|
|||
|
|
@ -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/workspaces/workspace-mutation.atoms";
|
||||
import { createWorkspaceMutationAtom } from "@/atoms/workspaces/workspace-mutation.atoms";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
|
|
@ -27,7 +27,7 @@ import {
|
|||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { trackSearchSpaceCreated } from "@/lib/posthog/events";
|
||||
import { trackWorkspaceCreated } from "@/lib/posthog/events";
|
||||
|
||||
const formSchema = z.object({
|
||||
name: z.string().min(1, "Name is required"),
|
||||
|
|
@ -36,18 +36,18 @@ const formSchema = z.object({
|
|||
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
interface CreateSearchSpaceDialogProps {
|
||||
interface CreateWorkspaceDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export function CreateSearchSpaceDialog({ open, onOpenChange }: CreateSearchSpaceDialogProps) {
|
||||
const t = useTranslations("searchSpace");
|
||||
export function CreateWorkspaceDialog({ open, onOpenChange }: CreateWorkspaceDialogProps) {
|
||||
const t = useTranslations("workspace");
|
||||
const tCommon = useTranslations("common");
|
||||
const router = useRouter();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const { mutateAsync: createSearchSpace } = useAtomValue(createSearchSpaceMutationAtom);
|
||||
const { mutateAsync: createWorkspace } = useAtomValue(createWorkspaceMutationAtom);
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
resolver: zodResolver(formSchema),
|
||||
|
|
@ -60,16 +60,16 @@ export function CreateSearchSpaceDialog({ open, onOpenChange }: CreateSearchSpac
|
|||
const handleSubmit = async (values: FormValues) => {
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
const result = await createSearchSpace({
|
||||
const result = await createWorkspace({
|
||||
name: values.name,
|
||||
description: values.description || "",
|
||||
});
|
||||
|
||||
trackSearchSpaceCreated(result.id, values.name);
|
||||
trackWorkspaceCreated(result.id, values.name);
|
||||
|
||||
router.push(`/dashboard/${result.id}/new-chat`);
|
||||
} catch (error) {
|
||||
console.error("Failed to create search space:", error);
|
||||
console.error("Failed to create workspace:", error);
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export { CreateSearchSpaceDialog } from "./CreateWorkspaceDialog";
|
||||
export { CreateWorkspaceDialog } from "./CreateWorkspaceDialog";
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ interface HeaderProps {
|
|||
|
||||
export function Header({ mobileMenuTrigger }: HeaderProps) {
|
||||
const pathname = usePathname();
|
||||
const searchSpaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
const workspaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
const activeTab = useAtomValue(activeTabAtom);
|
||||
|
||||
const isFreePage = pathname?.startsWith("/free") ?? false;
|
||||
|
|
@ -26,14 +26,14 @@ export function Header({ mobileMenuTrigger }: HeaderProps) {
|
|||
const currentThreadState = useAtomValue(currentThreadAtom);
|
||||
|
||||
const hasThread = isChatPage && !isDocumentTab && currentThreadState.id !== null;
|
||||
const activeSearchSpaceId = searchSpaceId ? Number(searchSpaceId) : null;
|
||||
const activeWorkspaceId = workspaceId ? Number(workspaceId) : null;
|
||||
const canRenderShareButton =
|
||||
hasThread &&
|
||||
currentThreadState.id !== null &&
|
||||
currentThreadState.visibility !== null &&
|
||||
currentThreadState.searchSpaceId !== null &&
|
||||
activeSearchSpaceId !== null &&
|
||||
currentThreadState.searchSpaceId === activeSearchSpaceId;
|
||||
currentThreadState.workspaceId !== null &&
|
||||
activeWorkspaceId !== null &&
|
||||
currentThreadState.workspaceId === activeWorkspaceId;
|
||||
|
||||
// Free chat pages have their own header with model selector; only render mobile trigger
|
||||
if (isFreePage) {
|
||||
|
|
@ -50,13 +50,13 @@ export function Header({ mobileMenuTrigger }: HeaderProps) {
|
|||
canRenderShareButton &&
|
||||
currentThreadState.id !== null &&
|
||||
currentThreadState.visibility !== null &&
|
||||
currentThreadState.searchSpaceId !== null
|
||||
currentThreadState.workspaceId !== null
|
||||
) {
|
||||
threadForButton = {
|
||||
id: currentThreadState.id,
|
||||
visibility: currentThreadState.visibility,
|
||||
created_by_id: null,
|
||||
workspace_id: currentThreadState.searchSpaceId,
|
||||
workspace_id: currentThreadState.workspaceId,
|
||||
title: "",
|
||||
archived: false,
|
||||
created_at: "",
|
||||
|
|
|
|||
|
|
@ -5,17 +5,17 @@ import { Button } from "@/components/ui/button";
|
|||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { NavItem, SearchSpace, User } from "../../types/layout.types";
|
||||
import type { NavItem, User, Workspace } from "../../types/layout.types";
|
||||
import { SidebarUserProfile } from "../sidebar/SidebarUserProfile";
|
||||
import { SearchSpaceAvatar } from "./WorkspaceAvatar";
|
||||
import { WorkspaceAvatar } from "./WorkspaceAvatar";
|
||||
|
||||
interface IconRailProps {
|
||||
searchSpaces: SearchSpace[];
|
||||
activeSearchSpaceId: number | null;
|
||||
onSearchSpaceSelect: (id: number) => void;
|
||||
onSearchSpaceDelete?: (searchSpace: SearchSpace) => void;
|
||||
onSearchSpaceSettings?: (searchSpace: SearchSpace) => void;
|
||||
onAddSearchSpace: () => void;
|
||||
workspaces: Workspace[];
|
||||
activeWorkspaceId: number | null;
|
||||
onWorkspaceSelect: (id: number) => void;
|
||||
onWorkspaceDelete?: (workspace: Workspace) => void;
|
||||
onWorkspaceSettings?: (workspace: Workspace) => void;
|
||||
onAddWorkspace: () => void;
|
||||
isSingleRailMode?: boolean;
|
||||
onNewChat?: () => void;
|
||||
navItems?: NavItem[];
|
||||
|
|
@ -31,12 +31,12 @@ interface IconRailProps {
|
|||
}
|
||||
|
||||
export function IconRail({
|
||||
searchSpaces,
|
||||
activeSearchSpaceId,
|
||||
onSearchSpaceSelect,
|
||||
onSearchSpaceDelete,
|
||||
onSearchSpaceSettings,
|
||||
onAddSearchSpace,
|
||||
workspaces,
|
||||
activeWorkspaceId,
|
||||
onWorkspaceSelect,
|
||||
onWorkspaceDelete,
|
||||
onWorkspaceSettings,
|
||||
onAddWorkspace,
|
||||
isSingleRailMode = false,
|
||||
onNewChat,
|
||||
navItems = [],
|
||||
|
|
@ -77,18 +77,16 @@ export function IconRail({
|
|||
<div className={cn("flex h-full w-14 min-h-0 flex-col items-center", className)}>
|
||||
<ScrollArea className="w-full min-h-0 flex-1">
|
||||
<div className="flex flex-col items-center gap-2 px-1.5 py-3">
|
||||
{searchSpaces.map((searchSpace) => (
|
||||
<SearchSpaceAvatar
|
||||
key={searchSpace.id}
|
||||
name={searchSpace.name}
|
||||
isActive={searchSpace.id === activeSearchSpaceId}
|
||||
isShared={searchSpace.memberCount > 1}
|
||||
isOwner={searchSpace.isOwner}
|
||||
onClick={() => onSearchSpaceSelect(searchSpace.id)}
|
||||
onDelete={onSearchSpaceDelete ? () => onSearchSpaceDelete(searchSpace) : undefined}
|
||||
onSettings={
|
||||
onSearchSpaceSettings ? () => onSearchSpaceSettings(searchSpace) : undefined
|
||||
}
|
||||
{workspaces.map((workspace) => (
|
||||
<WorkspaceAvatar
|
||||
key={workspace.id}
|
||||
name={workspace.name}
|
||||
isActive={workspace.id === activeWorkspaceId}
|
||||
isShared={workspace.memberCount > 1}
|
||||
isOwner={workspace.isOwner}
|
||||
onClick={() => onWorkspaceSelect(workspace.id)}
|
||||
onDelete={onWorkspaceDelete ? () => onWorkspaceDelete(workspace) : undefined}
|
||||
onSettings={onWorkspaceSettings ? () => onWorkspaceSettings(workspace) : undefined}
|
||||
size="md"
|
||||
/>
|
||||
))}
|
||||
|
|
@ -98,7 +96,7 @@ export function IconRail({
|
|||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={onAddSearchSpace}
|
||||
onClick={onAddWorkspace}
|
||||
className="h-10 w-10 rounded-lg border-2 border-dashed border-muted-foreground/30 hover:border-muted-foreground/50"
|
||||
>
|
||||
<Plus className="h-5 w-5 text-muted-foreground" />
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import {
|
|||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface SearchSpaceAvatarProps {
|
||||
interface WorkspaceAvatarProps {
|
||||
name: string;
|
||||
isActive?: boolean;
|
||||
isShared?: boolean;
|
||||
|
|
@ -27,7 +27,7 @@ interface SearchSpaceAvatarProps {
|
|||
}
|
||||
|
||||
/**
|
||||
* Generates a consistent color based on search space name
|
||||
* Generates a consistent color based on workspace name
|
||||
*/
|
||||
function stringToColor(str: string): string {
|
||||
let hash = 0;
|
||||
|
|
@ -48,7 +48,7 @@ function stringToColor(str: string): string {
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets initials from search space name (max 2 chars)
|
||||
* Gets initials from workspace name (max 2 chars)
|
||||
*/
|
||||
function getInitials(name: string): string {
|
||||
const words = name.trim().split(/\s+/);
|
||||
|
|
@ -58,7 +58,7 @@ function getInitials(name: string): string {
|
|||
return name.slice(0, 2).toUpperCase();
|
||||
}
|
||||
|
||||
export function SearchSpaceAvatar({
|
||||
export function WorkspaceAvatar({
|
||||
name,
|
||||
isActive,
|
||||
isShared,
|
||||
|
|
@ -68,8 +68,8 @@ export function SearchSpaceAvatar({
|
|||
onSettings,
|
||||
size = "md",
|
||||
disableTooltip = false,
|
||||
}: SearchSpaceAvatarProps) {
|
||||
const t = useTranslations("searchSpace");
|
||||
}: WorkspaceAvatarProps) {
|
||||
const t = useTranslations("workspace");
|
||||
const tCommon = useTranslations("common");
|
||||
const bgColor = stringToColor(name);
|
||||
const initials = getInitials(name);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
export { IconRail } from "./IconRail";
|
||||
export { NavIcon } from "./NavIcon";
|
||||
export { SearchSpaceAvatar } from "./WorkspaceAvatar";
|
||||
export { WorkspaceAvatar } from "./WorkspaceAvatar";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export { CreateSearchSpaceDialog } from "./dialogs";
|
||||
export { CreateWorkspaceDialog } from "./dialogs";
|
||||
export { Header } from "./header";
|
||||
export { IconRail, NavIcon, SearchSpaceAvatar } from "./icon-rail";
|
||||
export { IconRail, NavIcon, WorkspaceAvatar } from "./icon-rail";
|
||||
export { LayoutShell } from "./shell";
|
||||
export {
|
||||
ChatListItem,
|
||||
|
|
|
|||
|
|
@ -274,7 +274,7 @@ export function RightPanel({ showTopBorder = false }: RightPanelProps) {
|
|||
documentId={editorState.documentId ?? undefined}
|
||||
localFilePath={editorState.localFilePath ?? undefined}
|
||||
memoryScope={editorState.memoryScope ?? undefined}
|
||||
searchSpaceId={editorState.searchSpaceId ?? undefined}
|
||||
workspaceId={editorState.workspaceId ?? undefined}
|
||||
title={editorState.title}
|
||||
onClose={closeEditor}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import {
|
|||
SIDEBAR_MIN_WIDTH,
|
||||
useSidebarResize,
|
||||
} from "../../hooks/useSidebarResize";
|
||||
import type { ChatItem, NavItem, PageUsage, SearchSpace, User } from "../../types/layout.types";
|
||||
import type { ChatItem, NavItem, PageUsage, User, Workspace } from "../../types/layout.types";
|
||||
import { Header } from "../header";
|
||||
import { IconRail } from "../icon-rail";
|
||||
import {
|
||||
|
|
@ -102,13 +102,13 @@ interface InboxProps {
|
|||
}
|
||||
|
||||
interface LayoutShellProps {
|
||||
searchSpaces: SearchSpace[];
|
||||
activeSearchSpaceId: number | null;
|
||||
onSearchSpaceSelect: (id: number) => void;
|
||||
onSearchSpaceDelete?: (searchSpace: SearchSpace) => void;
|
||||
onSearchSpaceSettings?: (searchSpace: SearchSpace) => void;
|
||||
onAddSearchSpace: () => void;
|
||||
searchSpace: SearchSpace | null;
|
||||
workspaces: Workspace[];
|
||||
activeWorkspaceId: number | null;
|
||||
onWorkspaceSelect: (id: number) => void;
|
||||
onWorkspaceDelete?: (workspace: Workspace) => void;
|
||||
onWorkspaceSettings?: (workspace: Workspace) => void;
|
||||
onAddWorkspace: () => void;
|
||||
workspace: Workspace | null;
|
||||
navItems: NavItem[];
|
||||
onNavItemClick?: (item: NavItem) => void;
|
||||
chats: ChatItem[];
|
||||
|
|
@ -186,12 +186,12 @@ function MainContentPanel({
|
|||
<div className="relative flex flex-1 flex-col bg-panel overflow-hidden min-w-0">
|
||||
<Header />
|
||||
|
||||
{isDocumentTab && activeTab.documentId && activeTab.searchSpaceId ? (
|
||||
{isDocumentTab && activeTab.documentId && activeTab.workspaceId ? (
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<DocumentTabContent
|
||||
key={activeTab.documentId}
|
||||
documentId={activeTab.documentId}
|
||||
searchSpaceId={activeTab.searchSpaceId}
|
||||
workspaceId={activeTab.workspaceId}
|
||||
title={activeTab.title}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -210,13 +210,13 @@ function DesktopWorkspaceRegion({ children }: { children: React.ReactNode }) {
|
|||
}
|
||||
|
||||
export function LayoutShell({
|
||||
searchSpaces,
|
||||
activeSearchSpaceId,
|
||||
onSearchSpaceSelect,
|
||||
onSearchSpaceDelete,
|
||||
onSearchSpaceSettings,
|
||||
onAddSearchSpace,
|
||||
searchSpace,
|
||||
workspaces,
|
||||
activeWorkspaceId,
|
||||
onWorkspaceSelect,
|
||||
onWorkspaceDelete,
|
||||
onWorkspaceSettings,
|
||||
onAddWorkspace,
|
||||
workspace,
|
||||
navItems,
|
||||
onNavItemClick,
|
||||
chats,
|
||||
|
|
@ -295,11 +295,11 @@ export function LayoutShell({
|
|||
<MobileSidebar
|
||||
isOpen={mobileMenuOpen}
|
||||
onOpenChange={setMobileMenuOpen}
|
||||
searchSpaces={searchSpaces}
|
||||
activeSearchSpaceId={activeSearchSpaceId}
|
||||
onSearchSpaceSelect={onSearchSpaceSelect}
|
||||
onAddSearchSpace={onAddSearchSpace}
|
||||
searchSpace={searchSpace}
|
||||
workspaces={workspaces}
|
||||
activeWorkspaceId={activeWorkspaceId}
|
||||
onWorkspaceSelect={onWorkspaceSelect}
|
||||
onAddWorkspace={onAddWorkspace}
|
||||
workspace={workspace}
|
||||
navItems={navItems}
|
||||
onNavItemClick={onNavItemClick}
|
||||
chats={chats}
|
||||
|
|
@ -392,12 +392,12 @@ export function LayoutShell({
|
|||
)}
|
||||
>
|
||||
<IconRail
|
||||
searchSpaces={searchSpaces}
|
||||
activeSearchSpaceId={activeSearchSpaceId}
|
||||
onSearchSpaceSelect={onSearchSpaceSelect}
|
||||
onSearchSpaceDelete={onSearchSpaceDelete}
|
||||
onSearchSpaceSettings={onSearchSpaceSettings}
|
||||
onAddSearchSpace={onAddSearchSpace}
|
||||
workspaces={workspaces}
|
||||
activeWorkspaceId={activeWorkspaceId}
|
||||
onWorkspaceSelect={onWorkspaceSelect}
|
||||
onWorkspaceDelete={onWorkspaceDelete}
|
||||
onWorkspaceSettings={onWorkspaceSettings}
|
||||
onAddWorkspace={onAddWorkspace}
|
||||
isSingleRailMode={false}
|
||||
user={user}
|
||||
onUserSettings={onUserSettings}
|
||||
|
|
@ -417,7 +417,7 @@ export function LayoutShell({
|
|||
)}
|
||||
>
|
||||
<Sidebar
|
||||
searchSpace={searchSpace}
|
||||
workspace={workspace}
|
||||
isCollapsed={isCollapsed}
|
||||
onToggleCollapse={toggleCollapsed}
|
||||
navItems={navItems}
|
||||
|
|
|
|||
|
|
@ -48,11 +48,11 @@ import { formatThreadTimestamp } from "@/lib/format-date";
|
|||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface AllChatsContentProps {
|
||||
searchSpaceId: string;
|
||||
workspaceId: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function AllChatsContent({ searchSpaceId, className }: AllChatsContentProps) {
|
||||
function AllChatsContent({ workspaceId, className }: AllChatsContentProps) {
|
||||
const t = useTranslations("sidebar");
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
|
|
@ -60,9 +60,9 @@ function AllChatsContent({ searchSpaceId, className }: AllChatsContentProps) {
|
|||
const isMobile = useIsMobile();
|
||||
const removeChatTab = useSetAtom(removeChatTabAtom);
|
||||
const { activateChatThread, prefetchChatThread } = useActivateChatThread();
|
||||
const { mutateAsync: deleteThread } = useDeleteThread(searchSpaceId);
|
||||
const { mutateAsync: archiveThread } = useArchiveThread(searchSpaceId);
|
||||
const { mutateAsync: renameThread } = useRenameThread(searchSpaceId);
|
||||
const { mutateAsync: deleteThread } = useDeleteThread(workspaceId);
|
||||
const { mutateAsync: archiveThread } = useArchiveThread(workspaceId);
|
||||
const { mutateAsync: renameThread } = useRenameThread(workspaceId);
|
||||
|
||||
const currentChatId = Array.isArray(params.chat_id)
|
||||
? Number(params.chat_id[0])
|
||||
|
|
@ -96,10 +96,10 @@ function AllChatsContent({ searchSpaceId, className }: AllChatsContentProps) {
|
|||
error: threadsError,
|
||||
isLoading: isLoadingThreads,
|
||||
} = useQuery({
|
||||
queryKey: ["all-threads", searchSpaceId],
|
||||
queryFn: () => fetchThreads(Number(searchSpaceId)),
|
||||
enabled: !!searchSpaceId && !isSearchMode,
|
||||
placeholderData: () => queryClient.getQueryData(["threads", searchSpaceId, { limit: 40 }]),
|
||||
queryKey: ["all-threads", workspaceId],
|
||||
queryFn: () => fetchThreads(Number(workspaceId)),
|
||||
enabled: !!workspaceId && !isSearchMode,
|
||||
placeholderData: () => queryClient.getQueryData(["threads", workspaceId, { limit: 40 }]),
|
||||
});
|
||||
|
||||
const {
|
||||
|
|
@ -107,9 +107,9 @@ function AllChatsContent({ searchSpaceId, className }: AllChatsContentProps) {
|
|||
error: searchError,
|
||||
isLoading: isLoadingSearch,
|
||||
} = useQuery({
|
||||
queryKey: ["search-threads", searchSpaceId, debouncedSearchQuery],
|
||||
queryFn: () => searchThreads(Number(searchSpaceId), debouncedSearchQuery.trim()),
|
||||
enabled: !!searchSpaceId && isSearchMode,
|
||||
queryKey: ["search-threads", workspaceId, debouncedSearchQuery],
|
||||
queryFn: () => searchThreads(Number(workspaceId), debouncedSearchQuery.trim()),
|
||||
enabled: !!workspaceId && isSearchMode,
|
||||
});
|
||||
|
||||
const threads = useMemo(() => {
|
||||
|
|
@ -127,11 +127,11 @@ function AllChatsContent({ searchSpaceId, className }: AllChatsContentProps) {
|
|||
activateChatThread({
|
||||
id: thread.id,
|
||||
title: thread.title || "New Chat",
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
visibility: thread.visibility,
|
||||
});
|
||||
},
|
||||
[activateChatThread, searchSpaceId]
|
||||
[activateChatThread, workspaceId]
|
||||
);
|
||||
|
||||
const handleDeleteThread = useCallback(
|
||||
|
|
@ -153,7 +153,7 @@ function AllChatsContent({ searchSpaceId, className }: AllChatsContentProps) {
|
|||
id: fallbackTab.chatId ?? null,
|
||||
title: fallbackTab.title,
|
||||
url: fallbackTab.chatUrl,
|
||||
searchSpaceId: fallbackTab.searchSpaceId ?? searchSpaceId,
|
||||
workspaceId: fallbackTab.workspaceId ?? workspaceId,
|
||||
...(fallbackTab.visibility !== undefined
|
||||
? { visibility: fallbackTab.visibility }
|
||||
: {}),
|
||||
|
|
@ -163,7 +163,7 @@ function AllChatsContent({ searchSpaceId, className }: AllChatsContentProps) {
|
|||
});
|
||||
return;
|
||||
}
|
||||
router.push(`/dashboard/${searchSpaceId}/new-chat`);
|
||||
router.push(`/dashboard/${workspaceId}/new-chat`);
|
||||
}, 250);
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -173,7 +173,7 @@ function AllChatsContent({ searchSpaceId, className }: AllChatsContentProps) {
|
|||
setDeletingThreadId(null);
|
||||
}
|
||||
},
|
||||
[activateChatThread, deleteThread, t, currentChatId, router, removeChatTab, searchSpaceId]
|
||||
[activateChatThread, deleteThread, t, currentChatId, router, removeChatTab, workspaceId]
|
||||
);
|
||||
|
||||
const handleToggleArchive = useCallback(
|
||||
|
|
@ -548,10 +548,10 @@ function AllChatsContent({ searchSpaceId, className }: AllChatsContentProps) {
|
|||
);
|
||||
}
|
||||
|
||||
export function AllChatsWorkspaceContent({ searchSpaceId }: { searchSpaceId: string }) {
|
||||
export function AllChatsWorkspaceContent({ workspaceId }: { workspaceId: string }) {
|
||||
return (
|
||||
<div className="flex h-full min-h-0 w-full overflow-hidden text-sidebar-foreground">
|
||||
<AllChatsContent searchSpaceId={searchSpaceId} />
|
||||
<AllChatsContent workspaceId={workspaceId} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ interface DesktopLocalTabContentProps {
|
|||
localRootPaths: string[];
|
||||
canAddMoreLocalRoots: boolean;
|
||||
maxLocalFilesystemRoots: number;
|
||||
searchSpaceId: number;
|
||||
workspaceId: number;
|
||||
onPickFilesystemRoot: () => Promise<void> | void;
|
||||
onRemoveFilesystemRoot: (rootPath: string) => Promise<void> | void;
|
||||
onClearFilesystemRoots: () => Promise<void> | void;
|
||||
|
|
@ -37,7 +37,7 @@ export function DesktopLocalTabContent({
|
|||
localRootPaths,
|
||||
canAddMoreLocalRoots,
|
||||
maxLocalFilesystemRoots,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
onPickFilesystemRoot,
|
||||
onRemoveFilesystemRoot,
|
||||
onClearFilesystemRoots,
|
||||
|
|
@ -49,17 +49,17 @@ export function DesktopLocalTabContent({
|
|||
const localSearchInputRef = useRef<HTMLInputElement>(null);
|
||||
const [expandedFolderKeyMap, setExpandedFolderKeyMap] = useAtom(localExpandedFolderKeysAtom);
|
||||
const expandedFolderKeys = useMemo(
|
||||
() => new Set(expandedFolderKeyMap[searchSpaceId] ?? []),
|
||||
[expandedFolderKeyMap, searchSpaceId]
|
||||
() => new Set(expandedFolderKeyMap[workspaceId] ?? []),
|
||||
[expandedFolderKeyMap, workspaceId]
|
||||
);
|
||||
const handleExpandedFolderKeysChange = useCallback(
|
||||
(nextExpandedKeys: Set<string>) => {
|
||||
setExpandedFolderKeyMap((prev) => ({
|
||||
...prev,
|
||||
[searchSpaceId]: Array.from(nextExpandedKeys),
|
||||
[workspaceId]: Array.from(nextExpandedKeys),
|
||||
}));
|
||||
},
|
||||
[searchSpaceId, setExpandedFolderKeyMap]
|
||||
[workspaceId, setExpandedFolderKeyMap]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
@ -199,7 +199,7 @@ export function DesktopLocalTabContent({
|
|||
</div>
|
||||
<LocalFilesystemBrowser
|
||||
rootPaths={localRootPaths}
|
||||
searchSpaceId={searchSpaceId}
|
||||
workspaceId={workspaceId}
|
||||
active
|
||||
searchQuery={debouncedLocalSearch.trim() || undefined}
|
||||
onOpenFile={onOpenLocalFile}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import {
|
|||
folderWatchDialogOpenAtom,
|
||||
folderWatchInitialFolderAtom,
|
||||
} from "@/atoms/folder-sync/folder-sync.atoms";
|
||||
import { searchSpacesAtom } from "@/atoms/workspaces/workspace-query.atoms";
|
||||
import { workspacesAtom } 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";
|
||||
|
|
@ -57,7 +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 { workspacesApiService } 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";
|
||||
|
|
@ -202,7 +202,7 @@ interface WatchedFolderEntry {
|
|||
excludePatterns: string[];
|
||||
fileExtensions: string[] | null;
|
||||
rootFolderId: number | null;
|
||||
searchSpaceId: number;
|
||||
workspaceId: number;
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -264,7 +264,7 @@ function AuthenticatedDocumentsSidebarBase({
|
|||
path: meta.folder_path as string,
|
||||
name: bf.name,
|
||||
rootFolderId: bf.id,
|
||||
searchSpaceId: bf.workspace_id,
|
||||
workspaceId: bf.workspace_id,
|
||||
excludePatterns: (meta.exclude_patterns as string[]) ?? [],
|
||||
fileExtensions: (meta.file_extensions as string[] | null) ?? null,
|
||||
active: true,
|
||||
|
|
@ -322,10 +322,10 @@ function AuthenticatedDocumentsSidebarBase({
|
|||
|
||||
// Zero queries for tree data
|
||||
const [zeroFolders, zeroFoldersResult] = useQuery(
|
||||
queries.folders.bySpace({ searchSpaceId: workspaceId })
|
||||
queries.folders.bySpace({ workspaceId: workspaceId })
|
||||
);
|
||||
const [zeroAllDocs, zeroAllDocsResult] = useQuery(
|
||||
queries.documents.bySpace({ searchSpaceId: workspaceId })
|
||||
queries.documents.bySpace({ workspaceId: workspaceId })
|
||||
);
|
||||
const [agentCreatedDocs, setAgentCreatedDocs] = useAtom(agentCreatedDocumentsAtom);
|
||||
|
||||
|
|
@ -336,7 +336,7 @@ function AuthenticatedDocumentsSidebarBase({
|
|||
name: f.name,
|
||||
position: f.position,
|
||||
parentId: f.parentId ?? null,
|
||||
searchSpaceId: f.searchSpaceId,
|
||||
workspaceId: f.workspaceId,
|
||||
metadata: f.metadata as Record<string, unknown> | null | undefined,
|
||||
})),
|
||||
[zeroFolders]
|
||||
|
|
@ -361,7 +361,7 @@ function AuthenticatedDocumentsSidebarBase({
|
|||
const zeroIds = new Set(zeroDocs.map((d) => d.id));
|
||||
|
||||
const pendingAgentDocs = agentCreatedDocs
|
||||
.filter((d) => d.searchSpaceId === workspaceId && !zeroIds.has(d.id))
|
||||
.filter((d) => d.workspaceId === workspaceId && !zeroIds.has(d.id))
|
||||
.map((d) => ({
|
||||
id: d.id,
|
||||
title: d.title,
|
||||
|
|
@ -456,7 +456,7 @@ function AuthenticatedDocumentsSidebarBase({
|
|||
await uploadFolderScan({
|
||||
folderPath: matched.path,
|
||||
folderName: matched.name,
|
||||
searchSpaceId: workspaceId,
|
||||
workspaceId: workspaceId,
|
||||
excludePatterns: matched.excludePatterns ?? DEFAULT_EXCLUDE_PATTERNS,
|
||||
fileExtensions:
|
||||
matched.fileExtensions ?? Array.from(getSupportedExtensionsSet(undefined, etlService)),
|
||||
|
|
@ -842,7 +842,7 @@ function AuthenticatedDocumentsSidebarBase({
|
|||
openEditorPanel({
|
||||
kind: "memory",
|
||||
memoryScope: "user",
|
||||
searchSpaceId: workspaceId,
|
||||
workspaceId: workspaceId,
|
||||
title: doc.title,
|
||||
});
|
||||
return true;
|
||||
|
|
@ -851,7 +851,7 @@ function AuthenticatedDocumentsSidebarBase({
|
|||
openEditorPanel({
|
||||
kind: "memory",
|
||||
memoryScope: "team",
|
||||
searchSpaceId: workspaceId,
|
||||
workspaceId: workspaceId,
|
||||
title: doc.title,
|
||||
});
|
||||
return true;
|
||||
|
|
@ -1020,7 +1020,7 @@ function AuthenticatedDocumentsSidebarBase({
|
|||
if (openMemoryDocument(doc)) return;
|
||||
openEditorPanel({
|
||||
documentId: doc.id,
|
||||
searchSpaceId: workspaceId,
|
||||
workspaceId: workspaceId,
|
||||
title: doc.title,
|
||||
});
|
||||
}}
|
||||
|
|
@ -1028,7 +1028,7 @@ function AuthenticatedDocumentsSidebarBase({
|
|||
if (openMemoryDocument(doc)) return;
|
||||
openEditorPanel({
|
||||
documentId: doc.id,
|
||||
searchSpaceId: workspaceId,
|
||||
workspaceId: workspaceId,
|
||||
title: doc.title,
|
||||
});
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ export function InboxSidebarContent({
|
|||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const isMobile = !useMediaQuery("(min-width: 640px)");
|
||||
const searchSpaceId = getWorkspaceIdNumber(params) ?? null;
|
||||
const workspaceId = getWorkspaceIdNumber(params) ?? null;
|
||||
|
||||
const [, setTargetCommentId] = useAtom(setTargetCommentIdAtom);
|
||||
|
||||
|
|
@ -204,11 +204,11 @@ export function InboxSidebarContent({
|
|||
// Server-side search query
|
||||
const searchTypeFilter = activeTab === "comments" ? ("new_mention" as const) : undefined;
|
||||
const { data: searchResponse, isLoading: isSearchLoading } = useQuery({
|
||||
queryKey: cacheKeys.notifications.search(searchSpaceId, debouncedSearch.trim(), activeTab),
|
||||
queryKey: cacheKeys.notifications.search(workspaceId, debouncedSearch.trim(), activeTab),
|
||||
queryFn: () =>
|
||||
notificationsApiService.getNotifications({
|
||||
queryParams: {
|
||||
workspace_id: searchSpaceId ?? undefined,
|
||||
workspace_id: workspaceId ?? undefined,
|
||||
type: searchTypeFilter,
|
||||
search: debouncedSearch.trim(),
|
||||
limit: 50,
|
||||
|
|
@ -242,8 +242,8 @@ export function InboxSidebarContent({
|
|||
|
||||
// Fetch source types for the status tab filter
|
||||
const { data: sourceTypesData } = useQuery({
|
||||
queryKey: cacheKeys.notifications.sourceTypes(searchSpaceId),
|
||||
queryFn: () => notificationsApiService.getSourceTypes(searchSpaceId ?? undefined),
|
||||
queryKey: cacheKeys.notifications.sourceTypes(workspaceId),
|
||||
queryFn: () => notificationsApiService.getSourceTypes(workspaceId ?? undefined),
|
||||
staleTime: 60 * 1000,
|
||||
enabled: activeTab === "status",
|
||||
});
|
||||
|
|
@ -364,17 +364,17 @@ export function InboxSidebarContent({
|
|||
|
||||
if (item.type === "new_mention") {
|
||||
if (isNewMentionMetadata(item.metadata)) {
|
||||
const searchSpaceId = item.workspace_id;
|
||||
const workspaceId = item.workspace_id;
|
||||
const threadId = item.metadata.thread_id;
|
||||
const commentId = item.metadata.comment_id;
|
||||
|
||||
if (searchSpaceId && threadId) {
|
||||
if (workspaceId && threadId) {
|
||||
if (commentId) {
|
||||
setTargetCommentId(commentId);
|
||||
}
|
||||
const url = commentId
|
||||
? `/dashboard/${searchSpaceId}/new-chat/${threadId}?commentId=${commentId}`
|
||||
: `/dashboard/${searchSpaceId}/new-chat/${threadId}`;
|
||||
? `/dashboard/${workspaceId}/new-chat/${threadId}?commentId=${commentId}`
|
||||
: `/dashboard/${workspaceId}/new-chat/${threadId}`;
|
||||
onOpenChange(false);
|
||||
onCloseMobileSidebar?.();
|
||||
router.push(url);
|
||||
|
|
@ -382,17 +382,17 @@ export function InboxSidebarContent({
|
|||
}
|
||||
} else if (item.type === "comment_reply") {
|
||||
if (isCommentReplyMetadata(item.metadata)) {
|
||||
const searchSpaceId = item.workspace_id;
|
||||
const workspaceId = item.workspace_id;
|
||||
const threadId = item.metadata.thread_id;
|
||||
const replyId = item.metadata.reply_id;
|
||||
|
||||
if (searchSpaceId && threadId) {
|
||||
if (workspaceId && threadId) {
|
||||
if (replyId) {
|
||||
setTargetCommentId(replyId);
|
||||
}
|
||||
const url = replyId
|
||||
? `/dashboard/${searchSpaceId}/new-chat/${threadId}?commentId=${replyId}`
|
||||
: `/dashboard/${searchSpaceId}/new-chat/${threadId}`;
|
||||
? `/dashboard/${workspaceId}/new-chat/${threadId}?commentId=${replyId}`
|
||||
: `/dashboard/${workspaceId}/new-chat/${threadId}`;
|
||||
onOpenChange(false);
|
||||
onCloseMobileSidebar?.();
|
||||
router.push(url);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { cn } from "@/lib/utils";
|
|||
|
||||
interface LocalFilesystemBrowserProps {
|
||||
rootPaths: string[];
|
||||
searchSpaceId: number;
|
||||
workspaceId: number;
|
||||
active?: boolean;
|
||||
searchQuery?: string;
|
||||
onOpenFile: (fullPath: string) => void;
|
||||
|
|
@ -134,7 +134,7 @@ function getNormalizedExtension(pathValue: string): string {
|
|||
|
||||
export function LocalFilesystemBrowser({
|
||||
rootPaths,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
active = true,
|
||||
searchQuery,
|
||||
onOpenFile,
|
||||
|
|
@ -184,7 +184,7 @@ export function LocalFilesystemBrowser({
|
|||
}
|
||||
const rootsToReload = rootEntries.filter(({ rootKey }) => {
|
||||
const nonce = reloadNonceByRoot[rootKey] ?? 0;
|
||||
const signature = `${searchSpaceId}:${rootKey}:${nonce}`;
|
||||
const signature = `${workspaceId}:${rootKey}:${nonce}`;
|
||||
return lastLoadedSignatureByRootRef.current.get(rootKey) !== signature;
|
||||
});
|
||||
if (rootsToReload.length === 0) {
|
||||
|
|
@ -192,7 +192,7 @@ export function LocalFilesystemBrowser({
|
|||
}
|
||||
for (const { rootKey } of rootsToReload) {
|
||||
const nonce = reloadNonceByRoot[rootKey] ?? 0;
|
||||
lastLoadedSignatureByRootRef.current.set(rootKey, `${searchSpaceId}:${rootKey}:${nonce}`);
|
||||
lastLoadedSignatureByRootRef.current.set(rootKey, `${workspaceId}:${rootKey}:${nonce}`);
|
||||
}
|
||||
let cancelled = false;
|
||||
|
||||
|
|
@ -212,7 +212,7 @@ export function LocalFilesystemBrowser({
|
|||
try {
|
||||
const files = (await electronAPI.listAgentFilesystemFiles({
|
||||
rootPath,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
excludePatterns: DEFAULT_EXCLUDE_PATTERNS,
|
||||
})) as LocalFolderFileEntry[];
|
||||
if (cancelled) return;
|
||||
|
|
@ -241,7 +241,7 @@ export function LocalFilesystemBrowser({
|
|||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [active, electronAPI, isWindowsPlatform, reloadNonceByRoot, rootPaths, searchSpaceId]);
|
||||
}, [active, electronAPI, isWindowsPlatform, reloadNonceByRoot, rootPaths, workspaceId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (active) return;
|
||||
|
|
@ -254,19 +254,19 @@ export function LocalFilesystemBrowser({
|
|||
if (!electronAPI?.onAgentFilesystemTreeDirty) return;
|
||||
if (!active) return;
|
||||
if (rootPaths.length === 0) {
|
||||
void electronAPI.stopAgentFilesystemTreeWatch(searchSpaceId);
|
||||
void electronAPI.stopAgentFilesystemTreeWatch(workspaceId);
|
||||
return;
|
||||
}
|
||||
|
||||
const unsubscribe = electronAPI.onAgentFilesystemTreeDirty(
|
||||
(event: {
|
||||
searchSpaceId: number | null;
|
||||
workspaceId: number | null;
|
||||
reason: "watcher_event" | "safety_poll";
|
||||
rootPath: string;
|
||||
changedPath: string | null;
|
||||
timestamp: number;
|
||||
}) => {
|
||||
if ((event.searchSpaceId ?? null) !== (searchSpaceId ?? null)) {
|
||||
if ((event.workspaceId ?? null) !== (workspaceId ?? null)) {
|
||||
return;
|
||||
}
|
||||
const eventRootKey = normalizeRootPathForLookup(event.rootPath, isWindowsPlatform);
|
||||
|
|
@ -290,16 +290,16 @@ export function LocalFilesystemBrowser({
|
|||
}
|
||||
);
|
||||
void electronAPI.startAgentFilesystemTreeWatch({
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
rootPaths,
|
||||
excludePatterns: DEFAULT_EXCLUDE_PATTERNS,
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
void electronAPI.stopAgentFilesystemTreeWatch(searchSpaceId);
|
||||
void electronAPI.stopAgentFilesystemTreeWatch(workspaceId);
|
||||
};
|
||||
}, [active, electronAPI, isWindowsPlatform, rootPaths, searchSpaceId]);
|
||||
}, [active, electronAPI, isWindowsPlatform, rootPaths, workspaceId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!electronAPI?.getAgentFilesystemMounts) {
|
||||
|
|
@ -322,7 +322,7 @@ export function LocalFilesystemBrowser({
|
|||
setMountRefreshInFlight(true);
|
||||
}
|
||||
void electronAPI
|
||||
.getAgentFilesystemMounts(searchSpaceId)
|
||||
.getAgentFilesystemMounts(workspaceId)
|
||||
.then((mounts: LocalRootMount[]) => {
|
||||
if (cancelled) return;
|
||||
const next = new Map<string, string>();
|
||||
|
|
@ -348,7 +348,7 @@ export function LocalFilesystemBrowser({
|
|||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [electronAPI, isWindowsPlatform, rootPaths, searchSpaceId]);
|
||||
}, [electronAPI, isWindowsPlatform, rootPaths, workspaceId]);
|
||||
|
||||
const treeByRoot = useMemo(() => {
|
||||
const query = searchQuery?.trim().toLowerCase() ?? "";
|
||||
|
|
|
|||
|
|
@ -4,18 +4,18 @@ import { PanelLeft, Plus } from "lucide-react";
|
|||
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/WorkspaceAvatar";
|
||||
import type { ChatItem, NavItem, PageUsage, User, Workspace } from "../../types/layout.types";
|
||||
import { WorkspaceAvatar } from "../icon-rail/WorkspaceAvatar";
|
||||
import { Sidebar } from "./Sidebar";
|
||||
|
||||
interface MobileSidebarProps {
|
||||
isOpen: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
searchSpaces: SearchSpace[];
|
||||
activeSearchSpaceId: number | null;
|
||||
onSearchSpaceSelect: (id: number) => void;
|
||||
onAddSearchSpace: () => void;
|
||||
searchSpace: SearchSpace | null;
|
||||
workspaces: Workspace[];
|
||||
activeWorkspaceId: number | null;
|
||||
onWorkspaceSelect: (id: number) => void;
|
||||
onAddWorkspace: () => void;
|
||||
workspace: Workspace | null;
|
||||
navItems: NavItem[];
|
||||
onNavItemClick?: (item: NavItem) => void;
|
||||
chats: ChatItem[];
|
||||
|
|
@ -62,12 +62,12 @@ export function MobileSidebarTrigger({ onClick }: { onClick: () => void }) {
|
|||
export function MobileSidebar({
|
||||
isOpen,
|
||||
onOpenChange,
|
||||
searchSpaces,
|
||||
activeSearchSpaceId,
|
||||
onSearchSpaceSelect,
|
||||
workspaces,
|
||||
activeWorkspaceId,
|
||||
onWorkspaceSelect,
|
||||
|
||||
onAddSearchSpace,
|
||||
searchSpace,
|
||||
onAddWorkspace,
|
||||
workspace,
|
||||
navItems,
|
||||
onNavItemClick,
|
||||
chats,
|
||||
|
|
@ -93,8 +93,8 @@ export function MobileSidebar({
|
|||
setTheme,
|
||||
isLoadingChats = false,
|
||||
}: MobileSidebarProps) {
|
||||
const handleSearchSpaceSelect = (id: number) => {
|
||||
onSearchSpaceSelect(id);
|
||||
const handleWorkspaceSelect = (id: number) => {
|
||||
onWorkspaceSelect(id);
|
||||
};
|
||||
|
||||
const handleNavItemClick = (item: NavItem) => {
|
||||
|
|
@ -118,18 +118,18 @@ export function MobileSidebar({
|
|||
>
|
||||
<SheetTitle className="sr-only">Navigation</SheetTitle>
|
||||
|
||||
{/* Vertical Search Spaces Rail - left side */}
|
||||
{/* Vertical Workspaces Rail - left side */}
|
||||
<div className="flex h-full w-14 shrink-0 flex-col items-center border-r bg-rail">
|
||||
<ScrollArea className="w-full flex-1">
|
||||
<div className="flex flex-col items-center gap-2 px-1.5 py-3">
|
||||
{searchSpaces.map((space) => (
|
||||
<SearchSpaceAvatar
|
||||
{workspaces.map((space) => (
|
||||
<WorkspaceAvatar
|
||||
key={space.id}
|
||||
name={space.name}
|
||||
isActive={space.id === activeSearchSpaceId}
|
||||
isActive={space.id === activeWorkspaceId}
|
||||
isShared={space.memberCount > 1}
|
||||
isOwner={space.isOwner}
|
||||
onClick={() => handleSearchSpaceSelect(space.id)}
|
||||
onClick={() => handleWorkspaceSelect(space.id)}
|
||||
size="md"
|
||||
disableTooltip
|
||||
/>
|
||||
|
|
@ -137,7 +137,7 @@ export function MobileSidebar({
|
|||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={onAddSearchSpace}
|
||||
onClick={onAddWorkspace}
|
||||
className="h-10 w-10 shrink-0 rounded-lg border-2 border-dashed border-muted-foreground/30 hover:border-muted-foreground/50"
|
||||
>
|
||||
<Plus className="h-5 w-5 text-muted-foreground" />
|
||||
|
|
@ -150,7 +150,7 @@ export function MobileSidebar({
|
|||
{/* Sidebar Content - right side */}
|
||||
<div className="flex-1 overflow-hidden flex flex-col [&>*]:!w-full">
|
||||
<Sidebar
|
||||
searchSpace={searchSpace}
|
||||
workspace={workspace}
|
||||
isCollapsed={false}
|
||||
onToggleCollapse={() => onOpenChange(false)}
|
||||
navItems={navItems}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ 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";
|
||||
import type { ChatItem, NavItem, PageUsage, User, Workspace } from "../../types/layout.types";
|
||||
import { ChatListItem } from "./ChatListItem";
|
||||
import { CreditBalanceDisplay } from "./CreditBalanceDisplay";
|
||||
import { DocumentsSidebar } from "./DocumentsSidebar";
|
||||
|
|
@ -62,7 +62,7 @@ function CollapsedInboxIcon({ item }: { item: NavItem }) {
|
|||
}
|
||||
|
||||
interface SidebarProps {
|
||||
searchSpace: SearchSpace | null;
|
||||
workspace: Workspace | null;
|
||||
isCollapsed?: boolean;
|
||||
onToggleCollapse?: () => void;
|
||||
navItems: NavItem[];
|
||||
|
|
@ -103,7 +103,7 @@ interface SidebarProps {
|
|||
}
|
||||
|
||||
export function Sidebar({
|
||||
searchSpace,
|
||||
workspace,
|
||||
isCollapsed = false,
|
||||
onToggleCollapse,
|
||||
navItems,
|
||||
|
|
@ -192,7 +192,7 @@ export function Sidebar({
|
|||
aria-hidden={isCollapsed}
|
||||
>
|
||||
<SidebarHeader
|
||||
searchSpace={searchSpace}
|
||||
workspace={workspace}
|
||||
isCollapsed={false}
|
||||
onSettings={onSettings}
|
||||
onManageMembers={onManageMembers}
|
||||
|
|
@ -400,7 +400,7 @@ function SidebarUsageFooter({
|
|||
onNavigate?: () => void;
|
||||
}) {
|
||||
const params = useParams();
|
||||
const searchSpaceId = getWorkspaceIdParam(params) ?? "";
|
||||
const workspaceId = getWorkspaceIdParam(params) ?? "";
|
||||
const isAnonymous = useIsAnonymous();
|
||||
|
||||
if (isCollapsed) return null;
|
||||
|
|
@ -446,7 +446,7 @@ function SidebarUsageFooter({
|
|||
<CreditBalanceDisplay />
|
||||
<div className="space-y-0.5">
|
||||
<Link
|
||||
href={`/dashboard/${searchSpaceId}/earn-credits`}
|
||||
href={`/dashboard/${workspaceId}/earn-credits`}
|
||||
onClick={onNavigate}
|
||||
className="group flex w-full items-center justify-between rounded-md px-1.5 py-1 transition-colors hover:bg-accent"
|
||||
>
|
||||
|
|
@ -459,7 +459,7 @@ function SidebarUsageFooter({
|
|||
</Badge>
|
||||
</Link>
|
||||
<Link
|
||||
href={`/dashboard/${searchSpaceId}/buy-more`}
|
||||
href={`/dashboard/${workspaceId}/buy-more`}
|
||||
onClick={onNavigate}
|
||||
className="group flex w-full items-center justify-between rounded-md px-1.5 py-1 transition-colors hover:bg-accent"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ import {
|
|||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { SearchSpace } from "../../types/layout.types";
|
||||
import type { Workspace } from "../../types/layout.types";
|
||||
|
||||
interface SidebarHeaderProps {
|
||||
searchSpace: SearchSpace | null;
|
||||
workspace: Workspace | null;
|
||||
isCollapsed?: boolean;
|
||||
onSettings?: () => void;
|
||||
onManageMembers?: () => void;
|
||||
|
|
@ -21,7 +21,7 @@ interface SidebarHeaderProps {
|
|||
}
|
||||
|
||||
export function SidebarHeader({
|
||||
searchSpace,
|
||||
workspace,
|
||||
isCollapsed,
|
||||
onSettings,
|
||||
onManageMembers,
|
||||
|
|
@ -40,9 +40,7 @@ export function SidebarHeader({
|
|||
isCollapsed && "w-10"
|
||||
)}
|
||||
>
|
||||
<span className="truncate text-sm">
|
||||
{searchSpace?.name ?? t("select_search_space")}
|
||||
</span>
|
||||
<span className="truncate text-sm">{workspace?.name ?? t("select_workspace")}</span>
|
||||
<ChevronsUpDown className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
|
@ -53,7 +51,7 @@ export function SidebarHeader({
|
|||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={onSettings}>
|
||||
<Settings className="h-4 w-4" />
|
||||
{t("search_space_settings")}
|
||||
{t("workspace_settings")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ function DocumentSkeleton() {
|
|||
|
||||
interface DocumentTabContentProps {
|
||||
documentId: number;
|
||||
searchSpaceId: number;
|
||||
workspaceId: number;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ function formatBytes(bytes: number): string {
|
|||
return `${bytes}B`;
|
||||
}
|
||||
|
||||
export function DocumentTabContent({ documentId, searchSpaceId, title }: DocumentTabContentProps) {
|
||||
export function DocumentTabContent({ documentId, workspaceId, title }: DocumentTabContentProps) {
|
||||
const [doc, setDoc] = useState<DocumentContent | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
|
@ -104,7 +104,7 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen
|
|||
try {
|
||||
const response = await authenticatedFetch(
|
||||
buildBackendUrl(
|
||||
`/api/v1/workspaces/${searchSpaceId}/documents/${documentId}/editor-content`
|
||||
`/api/v1/workspaces/${workspaceId}/documents/${documentId}/editor-content`
|
||||
),
|
||||
{ method: "GET" }
|
||||
);
|
||||
|
|
@ -140,7 +140,7 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen
|
|||
|
||||
doFetch().catch(() => {});
|
||||
return () => controller.abort();
|
||||
}, [documentId, searchSpaceId]);
|
||||
}, [documentId, workspaceId]);
|
||||
|
||||
const handleMarkdownChange = useCallback((md: string) => {
|
||||
markdownRef.current = md;
|
||||
|
|
@ -154,7 +154,7 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen
|
|||
setSaving(true);
|
||||
try {
|
||||
const response = await authenticatedFetch(
|
||||
buildBackendUrl(`/api/v1/workspaces/${searchSpaceId}/documents/${documentId}/save`),
|
||||
buildBackendUrl(`/api/v1/workspaces/${workspaceId}/documents/${documentId}/save`),
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
|
|
@ -183,7 +183,7 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen
|
|||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}, [documentId, plateMaxBytes, searchSpaceId]);
|
||||
}, [documentId, plateMaxBytes, workspaceId]);
|
||||
|
||||
if (isLoading) return <DocumentSkeleton />;
|
||||
|
||||
|
|
@ -313,7 +313,7 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen
|
|||
try {
|
||||
const response = await authenticatedFetch(
|
||||
buildBackendUrl(
|
||||
`/api/v1/workspaces/${searchSpaceId}/documents/${documentId}/download-markdown`
|
||||
`/api/v1/workspaces/${workspaceId}/documents/${documentId}/download-markdown`
|
||||
),
|
||||
{ method: "GET" }
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,20 +4,20 @@ import { ImageModelSelector } from "./image-model-selector";
|
|||
import { ModelSelector } from "./model-selector";
|
||||
|
||||
interface ChatHeaderProps {
|
||||
searchSpaceId: number;
|
||||
workspaceId: number;
|
||||
className?: string;
|
||||
onChatModelSelected?: () => void;
|
||||
}
|
||||
|
||||
export function ChatHeader({ searchSpaceId, className, onChatModelSelected }: ChatHeaderProps) {
|
||||
export function ChatHeader({ workspaceId, className, onChatModelSelected }: ChatHeaderProps) {
|
||||
return (
|
||||
<div className="flex min-w-0 shrink-0 items-center gap-2">
|
||||
<ModelSelector
|
||||
searchSpaceId={searchSpaceId}
|
||||
workspaceId={workspaceId}
|
||||
className={className}
|
||||
onChatModelSelected={onChatModelSelected}
|
||||
/>
|
||||
<ImageModelSelector searchSpaceId={searchSpaceId} className={className} mobileIconOnly />
|
||||
<ImageModelSelector workspaceId={workspaceId} className={className} mobileIconOnly />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ const visibilityOptions: {
|
|||
},
|
||||
{
|
||||
value: "SEARCH_SPACE",
|
||||
label: "Search Space",
|
||||
description: "All members of this search space can access",
|
||||
label: "Workspace",
|
||||
description: "All members of this workspace can access",
|
||||
icon: Users,
|
||||
},
|
||||
];
|
||||
|
|
@ -96,7 +96,7 @@ export function ChatShareButton({ thread, onVisibilityChange, className }: ChatS
|
|||
|
||||
onVisibilityChange?.(updatedThread.visibility);
|
||||
toast.success(
|
||||
newVisibility === "SEARCH_SPACE" ? "Chat shared with search space" : "Chat is now private"
|
||||
newVisibility === "SEARCH_SPACE" ? "Chat shared with workspace" : "Chat is now private"
|
||||
);
|
||||
setOpen(false);
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ import { queries } from "@/zero/queries";
|
|||
export type DocumentMentionPickerRef = ComposerSuggestionNavigatorRef;
|
||||
|
||||
interface DocumentMentionPickerProps {
|
||||
searchSpaceId: number;
|
||||
workspaceId: number;
|
||||
onSelectionChange: (mentions: MentionedDocumentInfo[]) => void;
|
||||
onDone: () => void;
|
||||
initialSelectedDocuments?: MentionedDocumentInfo[];
|
||||
|
|
@ -105,14 +105,14 @@ function isMentionedContextItem(value: unknown): value is MentionedDocumentInfo
|
|||
return false;
|
||||
}
|
||||
|
||||
function getRecentsStorageKey(searchSpaceId: number) {
|
||||
return `${RECENTS_STORAGE_PREFIX}${searchSpaceId}`;
|
||||
function getRecentsStorageKey(workspaceId: number) {
|
||||
return `${RECENTS_STORAGE_PREFIX}${workspaceId}`;
|
||||
}
|
||||
|
||||
function readRecentMentions(searchSpaceId: number): MentionedDocumentInfo[] {
|
||||
function readRecentMentions(workspaceId: number): MentionedDocumentInfo[] {
|
||||
if (typeof window === "undefined") return [];
|
||||
try {
|
||||
const raw = window.localStorage.getItem(getRecentsStorageKey(searchSpaceId));
|
||||
const raw = window.localStorage.getItem(getRecentsStorageKey(workspaceId));
|
||||
if (!raw) return [];
|
||||
const parsed: unknown = JSON.parse(raw);
|
||||
if (!Array.isArray(parsed)) return [];
|
||||
|
|
@ -122,11 +122,11 @@ function readRecentMentions(searchSpaceId: number): MentionedDocumentInfo[] {
|
|||
}
|
||||
}
|
||||
|
||||
function writeRecentMentions(searchSpaceId: number, mentions: MentionedDocumentInfo[]) {
|
||||
function writeRecentMentions(workspaceId: number, mentions: MentionedDocumentInfo[]) {
|
||||
if (typeof window === "undefined") return;
|
||||
try {
|
||||
window.localStorage.setItem(
|
||||
getRecentsStorageKey(searchSpaceId),
|
||||
getRecentsStorageKey(workspaceId),
|
||||
JSON.stringify(mentions.slice(0, RECENTS_LIMIT))
|
||||
);
|
||||
} catch {
|
||||
|
|
@ -134,13 +134,13 @@ function writeRecentMentions(searchSpaceId: number, mentions: MentionedDocumentI
|
|||
}
|
||||
}
|
||||
|
||||
export function promoteRecentMention(searchSpaceId: number, mention: MentionedDocumentInfo) {
|
||||
export function promoteRecentMention(workspaceId: number, mention: MentionedDocumentInfo) {
|
||||
const mentionKey = getMentionDocKey(mention);
|
||||
const next = [
|
||||
mention,
|
||||
...readRecentMentions(searchSpaceId).filter((item) => getMentionDocKey(item) !== mentionKey),
|
||||
...readRecentMentions(workspaceId).filter((item) => getMentionDocKey(item) !== mentionKey),
|
||||
].slice(0, RECENTS_LIMIT);
|
||||
writeRecentMentions(searchSpaceId, next);
|
||||
writeRecentMentions(workspaceId, next);
|
||||
return next;
|
||||
}
|
||||
|
||||
|
|
@ -261,7 +261,7 @@ export const DocumentMentionPicker = forwardRef<
|
|||
DocumentMentionPickerProps
|
||||
>(function DocumentMentionPicker(
|
||||
{
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
onSelectionChange,
|
||||
onDone,
|
||||
initialSelectedDocuments = [],
|
||||
|
|
@ -286,15 +286,15 @@ export const DocumentMentionPicker = forwardRef<
|
|||
const [hasMore, setHasMore] = useState(false);
|
||||
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
||||
const [recentMentions, setRecentMentions] = useState<MentionedDocumentInfo[]>(() =>
|
||||
readRecentMentions(searchSpaceId)
|
||||
readRecentMentions(workspaceId)
|
||||
);
|
||||
|
||||
const [zeroFolders] = useZeroQuery(queries.folders.bySpace({ searchSpaceId }));
|
||||
const [zeroFolders] = useZeroQuery(queries.folders.bySpace({ workspaceId }));
|
||||
const { data: connectors = [], isLoading: isConnectorsLoading } = useAtomValue(connectorsAtom);
|
||||
const activeConnectors = useMemo(() => connectors.filter(isConnectorActive), [connectors]);
|
||||
const paginationScopeKey = useMemo(
|
||||
() => `${searchSpaceId}:${debouncedSearch}`,
|
||||
[searchSpaceId, debouncedSearch]
|
||||
() => `${workspaceId}:${debouncedSearch}`,
|
||||
[workspaceId, debouncedSearch]
|
||||
);
|
||||
const previousPaginationScopeKeyRef = useRef<string | null>(null);
|
||||
|
||||
|
|
@ -311,17 +311,17 @@ export const DocumentMentionPicker = forwardRef<
|
|||
}, [hasSearch]);
|
||||
|
||||
useEffect(() => {
|
||||
setRecentMentions(readRecentMentions(searchSpaceId));
|
||||
}, [searchSpaceId]);
|
||||
setRecentMentions(readRecentMentions(workspaceId));
|
||||
}, [workspaceId]);
|
||||
|
||||
const titleSearchParams = useMemo(
|
||||
() => ({
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
page: 0,
|
||||
page_size: PAGE_SIZE,
|
||||
...(isSearchValid ? { title: debouncedSearch.trim() } : {}),
|
||||
}),
|
||||
[searchSpaceId, debouncedSearch, isSearchValid]
|
||||
[workspaceId, debouncedSearch, isSearchValid]
|
||||
);
|
||||
|
||||
const { data: titleSearchResults, isLoading: isTitleSearchLoading } = useQuery({
|
||||
|
|
@ -329,7 +329,7 @@ export const DocumentMentionPicker = forwardRef<
|
|||
queryFn: ({ signal }) =>
|
||||
documentsApiService.searchDocumentTitles({ queryParams: titleSearchParams }, signal),
|
||||
staleTime: 60 * 1000,
|
||||
enabled: !!searchSpaceId && currentPage === 0 && (!hasSearch || isSearchValid),
|
||||
enabled: !!workspaceId && currentPage === 0 && (!hasSearch || isSearchValid),
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
|
||||
|
|
@ -362,7 +362,7 @@ export const DocumentMentionPicker = forwardRef<
|
|||
|
||||
try {
|
||||
const queryParams = {
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
page: nextPage,
|
||||
page_size: PAGE_SIZE,
|
||||
...(isSearchValid ? { title: debouncedSearch.trim() } : {}),
|
||||
|
|
@ -381,7 +381,7 @@ export const DocumentMentionPicker = forwardRef<
|
|||
} finally {
|
||||
setIsLoadingMore(false);
|
||||
}
|
||||
}, [currentPage, hasMore, isLoadingMore, debouncedSearch, searchSpaceId, isSearchValid]);
|
||||
}, [currentPage, hasMore, isLoadingMore, debouncedSearch, workspaceId, isSearchValid]);
|
||||
|
||||
const actualDocuments = useMemo(() => {
|
||||
if (!isSingleCharSearch) return accumulatedDocuments;
|
||||
|
|
@ -406,10 +406,10 @@ export const DocumentMentionPicker = forwardRef<
|
|||
// or types a search. An empty title returns recent threads (the
|
||||
// backend ``ilike '%%'`` matches all, newest first).
|
||||
const { data: threadResults = [], isLoading: isThreadsLoading } = useQuery({
|
||||
queryKey: ["composer-mention-threads", searchSpaceId, debouncedSearch],
|
||||
queryFn: () => searchThreads(searchSpaceId, debouncedSearch.trim()),
|
||||
queryKey: ["composer-mention-threads", workspaceId, debouncedSearch],
|
||||
queryFn: () => searchThreads(workspaceId, debouncedSearch.trim()),
|
||||
staleTime: 60 * 1000,
|
||||
enabled: enableChatMentions && !!searchSpaceId && (view.kind === "chats" || hasSearch),
|
||||
enabled: enableChatMentions && !!workspaceId && (view.kind === "chats" || hasSearch),
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
const threadMentions = useMemo(
|
||||
|
|
@ -425,7 +425,7 @@ export const DocumentMentionPicker = forwardRef<
|
|||
[recentDocMentions]
|
||||
);
|
||||
const { data: hydratedRecentDocs = [], isFetched: hasHydratedRecentDocs } = useQuery({
|
||||
queryKey: ["composer-mention-recent-docs", searchSpaceId, recentDocIdsKey],
|
||||
queryKey: ["composer-mention-recent-docs", workspaceId, recentDocIdsKey],
|
||||
queryFn: async () => {
|
||||
const results = await Promise.allSettled(
|
||||
recentDocMentions.map((mention) => documentsApiService.getDocument({ id: mention.id }))
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import { cn } from "@/lib/utils";
|
|||
import { providerDisplay } from "../settings/model-connections/provider-metadata";
|
||||
|
||||
interface ImageModelSelectorProps {
|
||||
searchSpaceId: number;
|
||||
workspaceId: number;
|
||||
className?: string;
|
||||
mobileIconOnly?: boolean;
|
||||
}
|
||||
|
|
@ -97,7 +97,7 @@ function groupedModels(models: ImageModel[]) {
|
|||
}
|
||||
|
||||
export function ImageModelSelector({
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
className,
|
||||
mobileIconOnly = false,
|
||||
}: ImageModelSelectorProps) {
|
||||
|
|
@ -146,7 +146,7 @@ export function ImageModelSelector({
|
|||
|
||||
function manageModelConnections() {
|
||||
setOpen(false);
|
||||
router.push(`/dashboard/${searchSpaceId}/workspace-settings/models`);
|
||||
router.push(`/dashboard/${workspaceId}/workspace-settings/models`);
|
||||
}
|
||||
|
||||
const handleScroll = useCallback((event: UIEvent<HTMLDivElement>) => {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import { cn } from "@/lib/utils";
|
|||
import { providerDisplay } from "../settings/model-connections/provider-metadata";
|
||||
|
||||
interface ModelSelectorProps {
|
||||
searchSpaceId: number;
|
||||
workspaceId: number;
|
||||
className?: string;
|
||||
onChatModelSelected?: () => void;
|
||||
}
|
||||
|
|
@ -96,11 +96,7 @@ function groupedModels(models: ChatModel[]) {
|
|||
}, {});
|
||||
}
|
||||
|
||||
export function ModelSelector({
|
||||
searchSpaceId,
|
||||
className,
|
||||
onChatModelSelected,
|
||||
}: ModelSelectorProps) {
|
||||
export function ModelSelector({ workspaceId, className, onChatModelSelected }: ModelSelectorProps) {
|
||||
const router = useRouter();
|
||||
const isMobile = useIsMobile();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
|
@ -149,7 +145,7 @@ export function ModelSelector({
|
|||
|
||||
function manageModelConnections() {
|
||||
setOpen(false);
|
||||
router.push(`/dashboard/${searchSpaceId}/workspace-settings/models`);
|
||||
router.push(`/dashboard/${workspaceId}/workspace-settings/models`);
|
||||
}
|
||||
|
||||
const handleScroll = useCallback((event: UIEvent<HTMLDivElement>) => {
|
||||
|
|
|
|||
|
|
@ -70,14 +70,14 @@ export const PromptPicker = forwardRef<PromptPickerRef, PromptPickerProps>(funct
|
|||
|
||||
const createPromptIndex = filtered.length;
|
||||
const totalItems = filtered.length + 1;
|
||||
const searchSpaceId = getWorkspaceIdParam(params);
|
||||
const workspaceId = getWorkspaceIdParam(params);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(index: number) => {
|
||||
if (index === createPromptIndex) {
|
||||
onDone();
|
||||
if (searchSpaceId) {
|
||||
router.push(`/dashboard/${searchSpaceId}/user-settings/prompts`);
|
||||
if (workspaceId) {
|
||||
router.push(`/dashboard/${workspaceId}/user-settings/prompts`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -85,7 +85,7 @@ export const PromptPicker = forwardRef<PromptPickerRef, PromptPickerProps>(funct
|
|||
if (!action) return;
|
||||
onSelect({ name: action.name, prompt: action.prompt, mode: action.mode });
|
||||
},
|
||||
[filtered, onSelect, createPromptIndex, onDone, router, searchSpaceId]
|
||||
[filtered, onSelect, createPromptIndex, onDone, router, workspaceId]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ const TOUR_STEPS: TourStep[] = [
|
|||
{
|
||||
target: '[data-joyride="upload-button"]',
|
||||
title: "Upload documents",
|
||||
content: "Upload files to your search space.",
|
||||
content: "Upload files to your workspace.",
|
||||
placement: "left",
|
||||
},
|
||||
{
|
||||
|
|
@ -391,17 +391,17 @@ export function OnboardingTour() {
|
|||
|
||||
// Get user data
|
||||
const { data: user } = useAtomValue(currentUserAtom);
|
||||
const searchSpaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
const workspaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
|
||||
// Fetch threads data
|
||||
const { data: threadsData } = useQuery({
|
||||
queryKey: ["threads", searchSpaceId, { limit: 40 }], // Same key as layout
|
||||
queryFn: () => fetchThreads(Number(searchSpaceId), 40),
|
||||
enabled: !!searchSpaceId,
|
||||
queryKey: ["threads", workspaceId, { limit: 40 }], // Same key as layout
|
||||
queryFn: () => fetchThreads(Number(workspaceId), 40),
|
||||
enabled: !!workspaceId,
|
||||
});
|
||||
|
||||
// Real-time document type counts via Zero
|
||||
const documentTypeCounts = useZeroDocumentTypeCounts(searchSpaceId);
|
||||
const documentTypeCounts = useZeroDocumentTypeCounts(workspaceId);
|
||||
|
||||
// Get connectors
|
||||
const { data: connectors = [] } = useAtomValue(connectorsAtom);
|
||||
|
|
@ -448,7 +448,7 @@ export function OnboardingTour() {
|
|||
// Check if tour should run: localStorage + data validation with user ID tracking
|
||||
useEffect(() => {
|
||||
// Don't check if not mounted or no user
|
||||
if (!mounted || !user?.id || !searchSpaceId) return;
|
||||
if (!mounted || !user?.id || !workspaceId) return;
|
||||
|
||||
// Check if on new-chat page
|
||||
const isNewChatPage = pathname?.includes("/new-chat");
|
||||
|
|
@ -459,7 +459,7 @@ export function OnboardingTour() {
|
|||
// - threadsData is defined (query completed, even if empty)
|
||||
// - documentTypeCounts is defined (query completed, even if empty object)
|
||||
// - connectors is an array (always defined with default [])
|
||||
// If searchSpaceId is not set, connectors query won't run, but that's okay
|
||||
// If workspaceId is not set, connectors query won't run, but that's okay
|
||||
const dataLoaded = threadsData !== undefined && documentTypeCounts !== undefined;
|
||||
if (!dataLoaded) return;
|
||||
|
||||
|
|
@ -542,7 +542,7 @@ export function OnboardingTour() {
|
|||
cancelled = true;
|
||||
if (startCheckTimerRef.current) clearTimeout(startCheckTimerRef.current);
|
||||
};
|
||||
}, [mounted, user?.id, searchSpaceId, pathname, threadsData, documentTypeCounts, connectors]);
|
||||
}, [mounted, user?.id, workspaceId, pathname, threadsData, documentTypeCounts, connectors]);
|
||||
|
||||
// Update position on resize/scroll
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ export function PublicChatSnapshotsManager({
|
|||
<Alert>
|
||||
<Info />
|
||||
<AlertDescription>
|
||||
You don't have permission to view public chats in this search space.
|
||||
You don't have permission to view public chats in this workspace.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export function AutoReloadSettings() {
|
|||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const queryClient = useQueryClient();
|
||||
const searchSpaceId = getWorkspaceIdNumber(params) ?? 0;
|
||||
const workspaceId = getWorkspaceIdNumber(params) ?? 0;
|
||||
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const [thresholdInput, setThresholdInput] = useState("");
|
||||
|
|
@ -79,8 +79,7 @@ export function AutoReloadSettings() {
|
|||
}, [searchParams, router, pathname, queryClient]);
|
||||
|
||||
const setupMutation = useMutation({
|
||||
mutationFn: () =>
|
||||
stripeApiService.createAutoReloadSetupSession({ workspace_id: searchSpaceId }),
|
||||
mutationFn: () => stripeApiService.createAutoReloadSetupSession({ workspace_id: workspaceId }),
|
||||
onSuccess: (response) => {
|
||||
window.location.assign(response.checkout_url);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ const formatUsd = (micros: number) => {
|
|||
|
||||
export function BuyCreditsContent() {
|
||||
const params = useParams();
|
||||
const searchSpaceId = getWorkspaceIdNumber(params) ?? 0;
|
||||
const workspaceId = 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.
|
||||
|
|
@ -177,7 +177,7 @@ export function BuyCreditsContent() {
|
|||
<Button
|
||||
className="w-full"
|
||||
disabled={purchaseMutation.isPending}
|
||||
onClick={() => purchaseMutation.mutate({ quantity, workspace_id: searchSpaceId })}
|
||||
onClick={() => purchaseMutation.mutate({ quantity, workspace_id: workspaceId })}
|
||||
>
|
||||
{purchaseMutation.isPending ? (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ const formatRewardUsd = (micros: number) => {
|
|||
export function EarnCreditsContent() {
|
||||
const params = useParams();
|
||||
const queryClient = useQueryClient();
|
||||
const searchSpaceId = getWorkspaceIdParam(params) ?? "";
|
||||
const workspaceId = getWorkspaceIdParam(params) ?? "";
|
||||
|
||||
useEffect(() => {
|
||||
trackIncentivePageViewed();
|
||||
|
|
@ -163,7 +163,7 @@ export function EarnCreditsContent() {
|
|||
<p className="text-sm text-muted-foreground">Need more?</p>
|
||||
{creditBuyingEnabled ? (
|
||||
<Button asChild variant="link" className="text-emerald-600 dark:text-emerald-400">
|
||||
<Link href={`/dashboard/${searchSpaceId}/buy-more`}>Buy credits at $1 per $1</Link>
|
||||
<Link href={`/dashboard/${workspaceId}/buy-more`}>Buy credits at $1 per $1</Link>
|
||||
</Button>
|
||||
) : (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
|
|
|
|||
|
|
@ -6,15 +6,15 @@ import { useTranslations } from "next-intl";
|
|||
import { useCallback, useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
updateSearchSpaceApiAccessMutationAtom,
|
||||
updateSearchSpaceMutationAtom,
|
||||
updateWorkspaceApiAccessMutationAtom,
|
||||
updateWorkspaceMutationAtom,
|
||||
} 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/workspaces-api.service";
|
||||
import { workspacesApiService } 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";
|
||||
|
|
@ -25,23 +25,22 @@ interface GeneralSettingsManagerProps {
|
|||
}
|
||||
|
||||
export function GeneralSettingsManager({ workspaceId }: GeneralSettingsManagerProps) {
|
||||
const searchSpaceId = workspaceId;
|
||||
const t = useTranslations("searchSpaceSettings");
|
||||
const t = useTranslations("workspaceSettings");
|
||||
const tCommon = useTranslations("common");
|
||||
const {
|
||||
data: searchSpace,
|
||||
data: workspace,
|
||||
isLoading: loading,
|
||||
isError,
|
||||
refetch: fetchSearchSpace,
|
||||
refetch: fetchWorkspace,
|
||||
} = useQuery({
|
||||
queryKey: cacheKeys.searchSpaces.detail(searchSpaceId.toString()),
|
||||
queryFn: () => searchSpacesApiService.getSearchSpace({ id: searchSpaceId }),
|
||||
enabled: !!searchSpaceId,
|
||||
queryKey: cacheKeys.workspaces.detail(workspaceId.toString()),
|
||||
queryFn: () => workspacesApiService.getWorkspace({ id: workspaceId }),
|
||||
enabled: !!workspaceId,
|
||||
});
|
||||
|
||||
const { mutateAsync: updateSearchSpace } = useAtomValue(updateSearchSpaceMutationAtom);
|
||||
const { mutateAsync: updateSearchSpaceApiAccess } = useAtomValue(
|
||||
updateSearchSpaceApiAccessMutationAtom
|
||||
const { mutateAsync: updateWorkspace } = useAtomValue(updateWorkspaceMutationAtom);
|
||||
const { mutateAsync: updateWorkspaceApiAccess } = useAtomValue(
|
||||
updateWorkspaceApiAccessMutationAtom
|
||||
);
|
||||
|
||||
const [name, setName] = useState("");
|
||||
|
|
@ -49,16 +48,16 @@ export function GeneralSettingsManager({ workspaceId }: GeneralSettingsManagerPr
|
|||
const [saving, setSaving] = useState(false);
|
||||
const [savingApiAccess, setSavingApiAccess] = useState(false);
|
||||
const [isExporting, setIsExporting] = useState(false);
|
||||
const hasSearchSpace = !!searchSpace;
|
||||
const searchSpaceName = searchSpace?.name;
|
||||
const searchSpaceDescription = searchSpace?.description;
|
||||
const hasWorkspace = !!workspace;
|
||||
const workspaceName = workspace?.name;
|
||||
const workspaceDescription = workspace?.description;
|
||||
|
||||
const handleExportKB = useCallback(async () => {
|
||||
if (isExporting) return;
|
||||
setIsExporting(true);
|
||||
try {
|
||||
const response = await authenticatedFetch(
|
||||
buildBackendUrl(`/api/v1/workspaces/${searchSpaceId}/export`),
|
||||
buildBackendUrl(`/api/v1/workspaces/${workspaceId}/export`),
|
||||
{ method: "GET" }
|
||||
);
|
||||
if (!response.ok) {
|
||||
|
|
@ -81,37 +80,37 @@ export function GeneralSettingsManager({ workspaceId }: GeneralSettingsManagerPr
|
|||
} finally {
|
||||
setIsExporting(false);
|
||||
}
|
||||
}, [searchSpaceId, isExporting]);
|
||||
}, [workspaceId, isExporting]);
|
||||
|
||||
// Initialize state from fetched search space
|
||||
// Initialize state from fetched workspace
|
||||
useEffect(() => {
|
||||
if (hasSearchSpace) {
|
||||
setName(searchSpaceName || "");
|
||||
setDescription(searchSpaceDescription || "");
|
||||
if (hasWorkspace) {
|
||||
setName(workspaceName || "");
|
||||
setDescription(workspaceDescription || "");
|
||||
}
|
||||
}, [hasSearchSpace, searchSpaceName, searchSpaceDescription]);
|
||||
}, [hasWorkspace, workspaceName, workspaceDescription]);
|
||||
|
||||
// Derive hasChanges during render
|
||||
const hasChanges =
|
||||
!!searchSpace &&
|
||||
((searchSpace.name || "") !== name || (searchSpace.description || "") !== description);
|
||||
!!workspace &&
|
||||
((workspace.name || "") !== name || (workspace.description || "") !== description);
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
setSaving(true);
|
||||
|
||||
await updateSearchSpace({
|
||||
id: searchSpaceId,
|
||||
await updateWorkspace({
|
||||
id: workspaceId,
|
||||
data: {
|
||||
name: name.trim(),
|
||||
description: description.trim() || undefined,
|
||||
},
|
||||
});
|
||||
|
||||
await fetchSearchSpace();
|
||||
await fetchWorkspace();
|
||||
} catch (error: unknown) {
|
||||
console.error("Error saving search space details:", error);
|
||||
toast.error(error instanceof Error ? error.message : "Failed to save search space details");
|
||||
console.error("Error saving workspace details:", error);
|
||||
toast.error(error instanceof Error ? error.message : "Failed to save workspace details");
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
|
|
@ -126,11 +125,11 @@ export function GeneralSettingsManager({ workspaceId }: GeneralSettingsManagerPr
|
|||
async (enabled: boolean) => {
|
||||
try {
|
||||
setSavingApiAccess(true);
|
||||
await updateSearchSpaceApiAccess({
|
||||
id: searchSpaceId,
|
||||
await updateWorkspaceApiAccess({
|
||||
id: workspaceId,
|
||||
api_access_enabled: enabled,
|
||||
});
|
||||
await fetchSearchSpace();
|
||||
await fetchWorkspace();
|
||||
} catch (error) {
|
||||
console.error("Error updating API access:", error);
|
||||
toast.error(error instanceof Error ? error.message : "Failed to update API access");
|
||||
|
|
@ -138,7 +137,7 @@ export function GeneralSettingsManager({ workspaceId }: GeneralSettingsManagerPr
|
|||
setSavingApiAccess(false);
|
||||
}
|
||||
},
|
||||
[fetchSearchSpace, searchSpaceId, updateSearchSpaceApiAccess]
|
||||
[fetchWorkspace, workspaceId, updateWorkspaceApiAccess]
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
|
|
@ -156,7 +155,7 @@ export function GeneralSettingsManager({ workspaceId }: GeneralSettingsManagerPr
|
|||
return (
|
||||
<div className="flex flex-col items-center justify-center gap-3 py-8 text-center">
|
||||
<p className="text-sm text-destructive">Failed to load settings.</p>
|
||||
<Button variant="outline" size="sm" onClick={() => fetchSearchSpace()}>
|
||||
<Button variant="outline" size="sm" onClick={() => fetchWorkspace()}>
|
||||
Retry
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -168,9 +167,9 @@ export function GeneralSettingsManager({ workspaceId }: GeneralSettingsManagerPr
|
|||
<form onSubmit={onSubmit} className="space-y-6">
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="search-space-name">{t("general_name_label")}</Label>
|
||||
<Label htmlFor="workspace-name">{t("general_name_label")}</Label>
|
||||
<Input
|
||||
id="search-space-name"
|
||||
id="workspace-name"
|
||||
maxLength={100}
|
||||
placeholder={t("general_name_placeholder")}
|
||||
value={name}
|
||||
|
|
@ -180,12 +179,12 @@ export function GeneralSettingsManager({ workspaceId }: GeneralSettingsManagerPr
|
|||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="search-space-description">
|
||||
<Label htmlFor="workspace-description">
|
||||
{t("general_description_label")}{" "}
|
||||
<span className="text-muted-foreground font-normal">({tCommon("optional")})</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="search-space-description"
|
||||
id="workspace-description"
|
||||
placeholder={t("general_description_placeholder")}
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
|
|
@ -210,13 +209,11 @@ export function GeneralSettingsManager({ workspaceId }: GeneralSettingsManagerPr
|
|||
<div className="border-t pt-6 flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="api-access-enabled">API key access</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Allow API keys to access this search space.
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">Allow API keys to access this workspace.</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="api-access-enabled"
|
||||
checked={!!searchSpace?.api_access_enabled}
|
||||
checked={!!workspace?.api_access_enabled}
|
||||
disabled={savingApiAccess}
|
||||
onCheckedChange={handleApiAccessToggle}
|
||||
/>
|
||||
|
|
@ -226,7 +223,7 @@ export function GeneralSettingsManager({ workspaceId }: GeneralSettingsManagerPr
|
|||
<div className="space-y-1">
|
||||
<Label>Export knowledge base</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Download all documents in this search space as a ZIP of markdown files.
|
||||
Download all documents in this workspace as a ZIP of markdown files.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ function renderAutoModeOption() {
|
|||
}
|
||||
|
||||
export function ModelConnectionsSettings({ workspaceId }: { workspaceId: number }) {
|
||||
const searchSpaceId = workspaceId;
|
||||
const [{ data: globalConnections = [] }] = useAtom(globalModelConnectionsAtom);
|
||||
const [{ data: connections = [] }] = useAtom(modelConnectionsAtom);
|
||||
const [{ data: roles }] = useAtom(modelRolesAtom);
|
||||
|
|
@ -148,7 +147,7 @@ export function ModelConnectionsSettings({ workspaceId }: { workspaceId: number
|
|||
|
||||
<Separator />
|
||||
|
||||
<ModelProviderConnectionsPanel searchSpaceId={searchSpaceId} connections={connections} />
|
||||
<ModelProviderConnectionsPanel workspaceId={workspaceId} connections={connections} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ export function ConnectionCard({ connection }: { connection: ConnectionRead }) {
|
|||
<AlertDialogTitle>Delete this provider?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
<span className="font-medium text-foreground">{providerLabel}</span> and all of
|
||||
its models will be removed from this search space. This cannot be undone.
|
||||
its models will be removed from this workspace. This cannot be undone.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import {
|
|||
} from "./provider-metadata";
|
||||
|
||||
interface ModelProviderConnectionsPanelProps {
|
||||
searchSpaceId: number;
|
||||
workspaceId: number;
|
||||
connections: ConnectionRead[];
|
||||
className?: string;
|
||||
addProviderTitle?: string;
|
||||
|
|
@ -49,7 +49,7 @@ function toModelSelection(model: SelectableModel): ModelSelection {
|
|||
}
|
||||
|
||||
export function ModelProviderConnectionsPanel({
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
connections,
|
||||
className,
|
||||
addProviderTitle = "Add Provider",
|
||||
|
|
@ -126,7 +126,7 @@ 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) {
|
||||
if (!Number.isFinite(workspaceId) || workspaceId <= 0) {
|
||||
toast.error("Workspace is still loading. Please try again.");
|
||||
return;
|
||||
}
|
||||
|
|
@ -143,7 +143,7 @@ export function ModelProviderConnectionsPanel({
|
|||
base_url: draft.base_url,
|
||||
api_key: draft.api_key,
|
||||
scope: "SEARCH_SPACE" as const,
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
extra: draft.extra,
|
||||
enabled: true,
|
||||
models,
|
||||
|
|
@ -170,7 +170,7 @@ export function ModelProviderConnectionsPanel({
|
|||
setProvider(providerId);
|
||||
setIsAddProviderOpen(true);
|
||||
if (providerId === "vertex_ai") {
|
||||
if (!Number.isFinite(searchSpaceId) || searchSpaceId <= 0) {
|
||||
if (!Number.isFinite(workspaceId) || workspaceId <= 0) {
|
||||
toast.error("Workspace is still loading. Please try again.");
|
||||
return;
|
||||
}
|
||||
|
|
@ -181,7 +181,7 @@ export function ModelProviderConnectionsPanel({
|
|||
base_url: null,
|
||||
api_key: null,
|
||||
scope: "SEARCH_SPACE",
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
extra: {},
|
||||
enabled: true,
|
||||
models: [],
|
||||
|
|
@ -194,7 +194,7 @@ export function ModelProviderConnectionsPanel({
|
|||
}
|
||||
|
||||
function refreshConnectModels(draft: ConnectionDraft) {
|
||||
if (!Number.isFinite(searchSpaceId) || searchSpaceId <= 0) {
|
||||
if (!Number.isFinite(workspaceId) || workspaceId <= 0) {
|
||||
toast.error("Workspace is still loading. Please try again.");
|
||||
return;
|
||||
}
|
||||
|
|
@ -205,7 +205,7 @@ export function ModelProviderConnectionsPanel({
|
|||
base_url: draft.base_url,
|
||||
api_key: draft.api_key,
|
||||
scope: "SEARCH_SPACE",
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
extra: draft.extra,
|
||||
enabled: true,
|
||||
models: [],
|
||||
|
|
|
|||
|
|
@ -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/workspaces/workspace-mutation.atoms";
|
||||
import { updateWorkspaceMutationAtom } 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/workspaces-api.service";
|
||||
import { workspacesApiService } from "@/lib/apis/workspaces-api.service";
|
||||
import { cacheKeys } from "@/lib/query-client/cache-keys";
|
||||
import { Spinner } from "../ui/spinner";
|
||||
|
||||
|
|
@ -20,36 +20,35 @@ interface PromptConfigManagerProps {
|
|||
}
|
||||
|
||||
export function PromptConfigManager({ workspaceId }: PromptConfigManagerProps) {
|
||||
const searchSpaceId = workspaceId;
|
||||
const { data: searchSpace, isLoading: loading } = useQuery({
|
||||
queryKey: cacheKeys.searchSpaces.detail(searchSpaceId.toString()),
|
||||
queryFn: () => searchSpacesApiService.getSearchSpace({ id: searchSpaceId }),
|
||||
enabled: !!searchSpaceId,
|
||||
const { data: workspace, isLoading: loading } = useQuery({
|
||||
queryKey: cacheKeys.workspaces.detail(workspaceId.toString()),
|
||||
queryFn: () => workspacesApiService.getWorkspace({ id: workspaceId }),
|
||||
enabled: !!workspaceId,
|
||||
});
|
||||
|
||||
const { mutateAsync: updateSearchSpace, isPending: isSaving } = useAtomValue(
|
||||
updateSearchSpaceMutationAtom
|
||||
const { mutateAsync: updateWorkspace, isPending: isSaving } = useAtomValue(
|
||||
updateWorkspaceMutationAtom
|
||||
);
|
||||
|
||||
const [customInstructions, setCustomInstructions] = useState("");
|
||||
const hasSearchSpace = !!searchSpace;
|
||||
const searchSpaceInstructions = searchSpace?.qna_custom_instructions;
|
||||
const hasWorkspace = !!workspace;
|
||||
const workspaceInstructions = workspace?.qna_custom_instructions;
|
||||
|
||||
// Initialize state from fetched search space
|
||||
// Initialize state from fetched workspace
|
||||
useEffect(() => {
|
||||
if (hasSearchSpace) {
|
||||
setCustomInstructions(searchSpaceInstructions || "");
|
||||
if (hasWorkspace) {
|
||||
setCustomInstructions(workspaceInstructions || "");
|
||||
}
|
||||
}, [hasSearchSpace, searchSpaceInstructions]);
|
||||
}, [hasWorkspace, workspaceInstructions]);
|
||||
|
||||
// Derive hasChanges during render
|
||||
const hasChanges =
|
||||
!!searchSpace && (searchSpace.qna_custom_instructions || "") !== customInstructions;
|
||||
!!workspace && (workspace.qna_custom_instructions || "") !== customInstructions;
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
await updateSearchSpace({
|
||||
id: searchSpaceId,
|
||||
await updateWorkspace({
|
||||
id: workspaceId,
|
||||
data: { qna_custom_instructions: customInstructions.trim() || "" },
|
||||
});
|
||||
toast.success("System instructions saved successfully");
|
||||
|
|
@ -98,8 +97,8 @@ export function PromptConfigManager({ workspaceId }: PromptConfigManagerProps) {
|
|||
<Alert>
|
||||
<Info />
|
||||
<AlertDescription>
|
||||
System instructions apply to all AI interactions in this search space. They guide how the
|
||||
AI responds, its tone, focus areas, and behavior patterns.
|
||||
System instructions apply to all AI interactions in this workspace. They guide how the AI
|
||||
responds, its tone, focus areas, and behavior patterns.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
|
|
@ -112,7 +111,7 @@ export function PromptConfigManager({ workspaceId }: PromptConfigManagerProps) {
|
|||
</h3>
|
||||
<p className="text-xs md:text-sm text-muted-foreground">
|
||||
Provide specific guidelines for how you want the AI to respond. These instructions
|
||||
will be applied to all answers in this search space.
|
||||
will be applied to all answers in this workspace.
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-3 md:space-y-4">
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ const CATEGORY_CONFIG: Record<
|
|||
settings: {
|
||||
label: "Settings",
|
||||
icon: Settings,
|
||||
description: "Manage search space settings",
|
||||
description: "Manage workspace settings",
|
||||
order: 10,
|
||||
},
|
||||
public_sharing: {
|
||||
|
|
@ -172,7 +172,7 @@ const CATEGORY_CONFIG: Record<
|
|||
general: {
|
||||
label: "General",
|
||||
icon: SlidersHorizontal,
|
||||
description: "General search space permissions",
|
||||
description: "General workspace permissions",
|
||||
order: 12,
|
||||
},
|
||||
};
|
||||
|
|
@ -270,7 +270,6 @@ type PermissionWithDescription = PermissionInfo;
|
|||
// ============ Roles Manager (for Settings page) ============
|
||||
|
||||
export function RolesManager({ workspaceId }: { workspaceId: number }) {
|
||||
const searchSpaceId = workspaceId;
|
||||
const { data: access = null } = useAtomValue(myAccessAtom);
|
||||
|
||||
const hasPermission = useCallback(
|
||||
|
|
@ -279,9 +278,9 @@ export function RolesManager({ workspaceId }: { workspaceId: number }) {
|
|||
);
|
||||
|
||||
const { data: roles = [], isLoading: rolesLoading } = useQuery({
|
||||
queryKey: cacheKeys.roles.all(searchSpaceId.toString()),
|
||||
queryFn: () => rolesApiService.getRoles({ workspace_id: searchSpaceId }),
|
||||
enabled: !!searchSpaceId,
|
||||
queryKey: cacheKeys.roles.all(workspaceId.toString()),
|
||||
queryFn: () => rolesApiService.getRoles({ workspace_id: workspaceId }),
|
||||
enabled: !!workspaceId,
|
||||
});
|
||||
|
||||
const { data: permissionsData } = useAtomValue(permissionsAtom);
|
||||
|
|
@ -312,36 +311,36 @@ export function RolesManager({ workspaceId }: { workspaceId: number }) {
|
|||
}
|
||||
): Promise<Role> => {
|
||||
const request: UpdateRoleRequest = {
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
role_id: roleId,
|
||||
data: data,
|
||||
};
|
||||
return await updateRole(request);
|
||||
},
|
||||
[updateRole, searchSpaceId]
|
||||
[updateRole, workspaceId]
|
||||
);
|
||||
|
||||
const handleDeleteRole = useCallback(
|
||||
async (roleId: number): Promise<boolean> => {
|
||||
const request: DeleteRoleRequest = {
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
role_id: roleId,
|
||||
};
|
||||
await deleteRole(request);
|
||||
return true;
|
||||
},
|
||||
[deleteRole, searchSpaceId]
|
||||
[deleteRole, workspaceId]
|
||||
);
|
||||
|
||||
const handleCreateRole = useCallback(
|
||||
async (roleData: CreateRoleRequest["data"]): Promise<Role> => {
|
||||
const request: CreateRoleRequest = {
|
||||
workspace_id: searchSpaceId,
|
||||
workspace_id: workspaceId,
|
||||
data: roleData,
|
||||
};
|
||||
return await createRole(request);
|
||||
},
|
||||
[createRole, searchSpaceId]
|
||||
[createRole, workspaceId]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
@ -885,7 +884,7 @@ function CreateRoleDialog({
|
|||
<DialogHeader className="px-5 pt-5 pb-4 shrink-0">
|
||||
<DialogTitle className="text-lg">Create Custom Role</DialogTitle>
|
||||
<DialogDescription className="text-sm text-muted-foreground">
|
||||
Define permissions for a new role in this search space
|
||||
Define permissions for a new role in this workspace
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex-1 min-h-0 overflow-y-auto">
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ import {
|
|||
} from "@/lib/supported-extensions";
|
||||
|
||||
interface DocumentUploadTabProps {
|
||||
searchSpaceId: string;
|
||||
workspaceId: string;
|
||||
onSuccess?: () => void;
|
||||
onAccordionStateChange?: (isExpanded: boolean) => void;
|
||||
}
|
||||
|
|
@ -132,7 +132,7 @@ const toggleRowClass =
|
|||
"flex items-center justify-between rounded-lg bg-slate-400/5 dark:bg-white/5 p-3";
|
||||
|
||||
export function DocumentUploadTab({
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
onSuccess,
|
||||
onAccordionStateChange,
|
||||
}: DocumentUploadTabProps) {
|
||||
|
|
@ -319,7 +319,7 @@ export function DocumentUploadTab({
|
|||
setUploadProgress(0);
|
||||
setIsFolderUploading(true);
|
||||
const total = folderUpload.entries.length;
|
||||
trackDocumentUploadStarted(Number(searchSpaceId), total, totalFileSize);
|
||||
trackDocumentUploadStarted(Number(workspaceId), total, totalFileSize);
|
||||
|
||||
try {
|
||||
const batches: FolderEntry[][] = [];
|
||||
|
|
@ -364,7 +364,7 @@ export function DocumentUploadTab({
|
|||
batch.map((e) => e.file),
|
||||
{
|
||||
folder_name: folderUpload.folderName,
|
||||
workspace_id: Number(searchSpaceId),
|
||||
workspace_id: Number(workspaceId),
|
||||
relative_paths: batch.map((e) => e.relativePath),
|
||||
root_folder_id: rootFolderId,
|
||||
use_vision_llm: useVisionLlm,
|
||||
|
|
@ -380,13 +380,13 @@ export function DocumentUploadTab({
|
|||
setUploadProgress(Math.round((uploaded / total) * 100));
|
||||
}
|
||||
|
||||
trackDocumentUploadSuccess(Number(searchSpaceId), total);
|
||||
trackDocumentUploadSuccess(Number(workspaceId), total);
|
||||
toast(t("upload_initiated"), { description: t("upload_initiated_desc") });
|
||||
setFolderUpload(null);
|
||||
onSuccess?.();
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Upload failed";
|
||||
trackDocumentUploadFailure(Number(searchSpaceId), message);
|
||||
trackDocumentUploadFailure(Number(workspaceId), message);
|
||||
toast(t("upload_error"), {
|
||||
description: `${t("upload_error_desc")}: ${message}`,
|
||||
});
|
||||
|
|
@ -403,7 +403,7 @@ export function DocumentUploadTab({
|
|||
}
|
||||
|
||||
setUploadProgress(0);
|
||||
trackDocumentUploadStarted(Number(searchSpaceId), files.length, totalFileSize);
|
||||
trackDocumentUploadStarted(Number(workspaceId), files.length, totalFileSize);
|
||||
|
||||
progressIntervalRef.current = setInterval(() => {
|
||||
setUploadProgress((prev) => (prev >= 90 ? prev : prev + Math.random() * 10));
|
||||
|
|
@ -413,7 +413,7 @@ export function DocumentUploadTab({
|
|||
uploadDocuments(
|
||||
{
|
||||
files: rawFiles,
|
||||
workspace_id: Number(searchSpaceId),
|
||||
workspace_id: Number(workspaceId),
|
||||
use_vision_llm: useVisionLlm,
|
||||
processing_mode: processingMode,
|
||||
},
|
||||
|
|
@ -421,7 +421,7 @@ export function DocumentUploadTab({
|
|||
onSuccess: () => {
|
||||
if (progressIntervalRef.current) clearInterval(progressIntervalRef.current);
|
||||
setUploadProgress(100);
|
||||
trackDocumentUploadSuccess(Number(searchSpaceId), files.length);
|
||||
trackDocumentUploadSuccess(Number(workspaceId), files.length);
|
||||
toast(t("upload_initiated"), { description: t("upload_initiated_desc") });
|
||||
onSuccess?.();
|
||||
},
|
||||
|
|
@ -429,7 +429,7 @@ export function DocumentUploadTab({
|
|||
if (progressIntervalRef.current) clearInterval(progressIntervalRef.current);
|
||||
setUploadProgress(0);
|
||||
const message = error instanceof Error ? error.message : "Upload failed";
|
||||
trackDocumentUploadFailure(Number(searchSpaceId), message);
|
||||
trackDocumentUploadFailure(Number(workspaceId), message);
|
||||
toast(t("upload_error"), {
|
||||
description: `${t("upload_error_desc")}: ${message}`,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export interface SelectedFolder {
|
|||
interface FolderWatchDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
searchSpaceId: number;
|
||||
workspaceId: number;
|
||||
onSuccess?: () => void;
|
||||
initialFolder?: SelectedFolder | null;
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ export const DEFAULT_EXCLUDE_PATTERNS = [
|
|||
export function FolderWatchDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
onSuccess,
|
||||
initialFolder,
|
||||
}: FolderWatchDialogProps) {
|
||||
|
|
@ -91,7 +91,7 @@ export function FolderWatchDialog({
|
|||
const rootFolderId = await uploadFolderScan({
|
||||
folderPath: selectedFolder.path,
|
||||
folderName: selectedFolder.name,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
excludePatterns: DEFAULT_EXCLUDE_PATTERNS,
|
||||
fileExtensions: supportedExtensions,
|
||||
onProgress: setProgress,
|
||||
|
|
@ -104,7 +104,7 @@ export function FolderWatchDialog({
|
|||
excludePatterns: DEFAULT_EXCLUDE_PATTERNS,
|
||||
fileExtensions: supportedExtensions,
|
||||
rootFolderId: rootFolderId ?? null,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
active: true,
|
||||
});
|
||||
|
||||
|
|
@ -124,7 +124,7 @@ export function FolderWatchDialog({
|
|||
setSubmitting(false);
|
||||
setProgress(null);
|
||||
}
|
||||
}, [selectedFolder, searchSpaceId, supportedExtensions, onOpenChange, onSuccess]);
|
||||
}, [selectedFolder, workspaceId, supportedExtensions, onOpenChange, onSuccess]);
|
||||
|
||||
const handleOpenChange = useCallback(
|
||||
(nextOpen: boolean) => {
|
||||
|
|
|
|||
|
|
@ -108,9 +108,9 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) {
|
|||
const draft = useMemo(() => extractDraft(effectiveArgs), [effectiveArgs]);
|
||||
|
||||
// Per-automation model selection. The card always supplies models (chosen
|
||||
// here, not snapshotted from the search space), so Approve dispatches an
|
||||
// here, not snapshotted from the workspace), so Approve dispatches an
|
||||
// `edit` decision carrying `definition.models`.
|
||||
const searchSpaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
const workspaceId = 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({
|
||||
workspace_id: searchSpaceId ? Number(searchSpaceId) : undefined,
|
||||
workspace_id: workspaceId ? Number(workspaceId) : undefined,
|
||||
edited: pendingEdits !== null,
|
||||
task_count: plan.length,
|
||||
trigger_type:
|
||||
|
|
@ -184,17 +184,17 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) {
|
|||
args,
|
||||
pendingEdits,
|
||||
resolvedModels,
|
||||
searchSpaceId,
|
||||
workspaceId,
|
||||
]);
|
||||
|
||||
const handleReject = useCallback(() => {
|
||||
if (phase !== "pending" || !canReject || isEditing) return;
|
||||
setRejected();
|
||||
trackAutomationChatRejected({
|
||||
workspace_id: searchSpaceId ? Number(searchSpaceId) : undefined,
|
||||
workspace_id: workspaceId ? Number(workspaceId) : undefined,
|
||||
});
|
||||
onDecision({ type: "reject", message: "User rejected the automation draft." });
|
||||
}, [phase, canReject, isEditing, setRejected, onDecision, searchSpaceId]);
|
||||
}, [phase, canReject, isEditing, setRejected, onDecision, workspaceId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditing) return;
|
||||
|
|
@ -268,7 +268,7 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) {
|
|||
setPendingEdits(parsed);
|
||||
setIsEditing(false);
|
||||
trackAutomationChatDraftEdited({
|
||||
workspace_id: searchSpaceId ? Number(searchSpaceId) : undefined,
|
||||
workspace_id: workspaceId ? Number(workspaceId) : undefined,
|
||||
});
|
||||
}}
|
||||
onCancel={() => setIsEditing(false)}
|
||||
|
|
@ -284,7 +284,7 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) {
|
|||
<div className="px-5 py-4">
|
||||
<p className="mb-3 text-xs font-medium text-foreground">Models</p>
|
||||
<AutomationModelFields
|
||||
workspaceId={Number(searchSpaceId)}
|
||||
workspaceId={Number(workspaceId)}
|
||||
value={resolvedModels}
|
||||
onChange={(patch) => setModelSelection((prev) => ({ ...prev, ...patch }))}
|
||||
/>
|
||||
|
|
@ -385,7 +385,7 @@ function JsonEditor({ initialValue, onSave, onCancel }: JsonEditorProps) {
|
|||
// ----------------------------------------------------------------------------
|
||||
|
||||
function SavedCard({ result }: { result: SavedResult }) {
|
||||
const searchSpaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
const workspaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
const tracked = useRef(false);
|
||||
useEffect(() => {
|
||||
if (tracked.current) return;
|
||||
|
|
@ -393,12 +393,12 @@ function SavedCard({ result }: { result: SavedResult }) {
|
|||
trackAutomationChatCreateSucceeded({
|
||||
automation_id: result.automation_id,
|
||||
name: result.name,
|
||||
workspace_id: searchSpaceId ? Number(searchSpaceId) : undefined,
|
||||
workspace_id: workspaceId ? Number(workspaceId) : undefined,
|
||||
});
|
||||
}, [result.automation_id, result.name, searchSpaceId]);
|
||||
}, [result.automation_id, result.name, workspaceId]);
|
||||
|
||||
const detailHref = searchSpaceId
|
||||
? `/dashboard/${searchSpaceId}/automations/${result.automation_id}`
|
||||
const detailHref = workspaceId
|
||||
? `/dashboard/${workspaceId}/automations/${result.automation_id}`
|
||||
: null;
|
||||
|
||||
return (
|
||||
|
|
@ -429,7 +429,7 @@ function SavedCard({ result }: { result: SavedResult }) {
|
|||
}
|
||||
|
||||
function InvalidCard({ result }: { result: InvalidResult }) {
|
||||
const searchSpaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
const workspaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
const tracked = useRef(false);
|
||||
useEffect(() => {
|
||||
if (tracked.current) return;
|
||||
|
|
@ -437,9 +437,9 @@ function InvalidCard({ result }: { result: InvalidResult }) {
|
|||
trackAutomationChatCreateFailed({
|
||||
reason: "invalid",
|
||||
issue_count: result.issues.length,
|
||||
workspace_id: searchSpaceId ? Number(searchSpaceId) : undefined,
|
||||
workspace_id: workspaceId ? Number(workspaceId) : undefined,
|
||||
});
|
||||
}, [result.issues.length, searchSpaceId]);
|
||||
}, [result.issues.length, workspaceId]);
|
||||
|
||||
return (
|
||||
<div className="my-4 max-w-lg overflow-hidden rounded-2xl border bg-muted/30 select-none">
|
||||
|
|
@ -464,7 +464,7 @@ function InvalidCard({ result }: { result: InvalidResult }) {
|
|||
}
|
||||
|
||||
function ErrorCard({ result }: { result: ErrorResult }) {
|
||||
const searchSpaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
const workspaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
const tracked = useRef(false);
|
||||
useEffect(() => {
|
||||
if (tracked.current) return;
|
||||
|
|
@ -472,9 +472,9 @@ function ErrorCard({ result }: { result: ErrorResult }) {
|
|||
trackAutomationChatCreateFailed({
|
||||
reason: "error",
|
||||
message: result.message,
|
||||
workspace_id: searchSpaceId ? Number(searchSpaceId) : undefined,
|
||||
workspace_id: workspaceId ? Number(workspaceId) : undefined,
|
||||
});
|
||||
}, [result.message, searchSpaceId]);
|
||||
}, [result.message, workspaceId]);
|
||||
|
||||
return (
|
||||
<div className="my-4 max-w-lg overflow-hidden rounded-2xl border bg-muted/30 select-none">
|
||||
|
|
|
|||
|
|
@ -35,15 +35,15 @@ import { Tilt } from "@/components/ui/tilt";
|
|||
import { cn } from "@/lib/utils";
|
||||
|
||||
// Define the form schema with Zod
|
||||
const searchSpaceFormSchema = z.object({
|
||||
const workspaceFormSchema = z.object({
|
||||
name: z.string().min(1, "Name is required"),
|
||||
description: z.string().optional(),
|
||||
});
|
||||
|
||||
// Define the type for the form values
|
||||
type SearchSpaceFormValues = z.infer<typeof searchSpaceFormSchema>;
|
||||
type WorkspaceFormValues = z.infer<typeof workspaceFormSchema>;
|
||||
|
||||
interface SearchSpaceFormProps {
|
||||
interface WorkspaceFormProps {
|
||||
onSubmit?: (data: { name: string; description?: string }) => void;
|
||||
onDelete?: () => void;
|
||||
className?: string;
|
||||
|
|
@ -51,19 +51,19 @@ interface SearchSpaceFormProps {
|
|||
initialData?: { name: string; description?: string };
|
||||
}
|
||||
|
||||
export function SearchSpaceForm({
|
||||
export function WorkspaceForm({
|
||||
onSubmit,
|
||||
onDelete,
|
||||
className,
|
||||
isEditing = false,
|
||||
initialData = { name: "", description: "" },
|
||||
}: SearchSpaceFormProps) {
|
||||
}: WorkspaceFormProps) {
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
// Initialize the form with React Hook Form and Zod validation
|
||||
const form = useForm<SearchSpaceFormValues>({
|
||||
resolver: zodResolver(searchSpaceFormSchema),
|
||||
const form = useForm<WorkspaceFormValues>({
|
||||
resolver: zodResolver(workspaceFormSchema),
|
||||
defaultValues: {
|
||||
name: initialData.name,
|
||||
description: initialData.description,
|
||||
|
|
@ -71,7 +71,7 @@ export function SearchSpaceForm({
|
|||
});
|
||||
|
||||
// Handle form submission
|
||||
const handleFormSubmit = (values: SearchSpaceFormValues) => {
|
||||
const handleFormSubmit = (values: WorkspaceFormValues) => {
|
||||
if (onSubmit) {
|
||||
onSubmit(values);
|
||||
}
|
||||
|
|
@ -119,7 +119,7 @@ export function SearchSpaceForm({
|
|||
<motion.div className="flex items-center justify-between" variants={itemVariants}>
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h2 className="text-2xl md:text-3xl font-bold tracking-tight">
|
||||
{isEditing ? "Edit Search Space" : "Create Search Space"}
|
||||
{isEditing ? "Edit Workspace" : "Create Workspace"}
|
||||
</h2>
|
||||
</div>
|
||||
<Button
|
||||
|
|
@ -163,7 +163,7 @@ export function SearchSpaceForm({
|
|||
<span className="p-3 rounded-full bg-blue-100 dark:bg-blue-950/50">
|
||||
<Search className="size-6 text-blue-500" />
|
||||
</span>
|
||||
<h3 className="text-xl font-semibold">Search Space</h3>
|
||||
<h3 className="text-xl font-semibold">Workspace</h3>
|
||||
</div>
|
||||
{isEditing && onDelete && (
|
||||
<AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
|
||||
|
|
@ -193,7 +193,7 @@ export function SearchSpaceForm({
|
|||
)}
|
||||
</div>
|
||||
<p className="text-muted-foreground">
|
||||
A search space is your personal workspace. Connect external sources, upload documents,
|
||||
A workspace is your personal workspace. Connect external sources, upload documents,
|
||||
take notes, and get work done with AI agents.
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -211,9 +211,9 @@ export function SearchSpaceForm({
|
|||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Enter search space name" {...field} />
|
||||
<Input placeholder="Enter workspace name" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>A unique name for your search space.</FormDescription>
|
||||
<FormDescription>A unique name for your workspace.</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
|
|
@ -228,10 +228,10 @@ export function SearchSpaceForm({
|
|||
Description <span className="text-muted-foreground font-normal">(optional)</span>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Enter search space description" {...field} />
|
||||
<Input placeholder="Enter workspace description" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
A brief description of what this search space will be used for.
|
||||
A brief description of what this workspace will be used for.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
|
@ -250,4 +250,4 @@ export function SearchSpaceForm({
|
|||
);
|
||||
}
|
||||
|
||||
export default SearchSpaceForm;
|
||||
export default WorkspaceForm;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue