Skip to content

Commit 312ce20

Browse files
Hide Agent Builder features when disabled
Conditionally hide Agent Builder UI components and redirect from protected routes when agentBuilder access is disabled for a workspace. Features hidden when agentBuilder is false: - Home link in project sidebar navigation - Integrations section in settings page - Latte AI copilot sidebar - Triggers section in document editor sidebar - SubAgents section in document editor sidebar Tools section behavior when agentBuilder is false: - Shows configured integrations (read-only) - Hides add button - Hides remove option from dropdown menu Routes that redirect to /dashboard when agentBuilder is false: - /projects/[projectId]/versions/[commitUuid]/home - /settings/integrations/new - /settings/integrations/[integrationId]/destroy Implementation uses the productAccess flags from the workspace model, accessed via useProductAccess() hook for client components and computeProductAccess()/getProductAccess() for server components. Also extracts Latte fetching logic into a separate LatteWrapper component for cleaner layout code. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 413ec64 commit 312ce20

File tree

11 files changed

+154
-49
lines changed

11 files changed

+154
-49
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
'use server'
2+
3+
import { ReactNode } from 'react'
4+
import { getLastLatteThreadUuidCached } from '$/app/(private)/_data-access'
5+
import { LatteRealtimeUpdatesProvider } from '../providers/LatteRealtimeUpdatesProvider'
6+
import { LatteLayout } from '$/components/LatteSidebar/LatteLayout'
7+
import { fetchConversationWithMessages } from '@latitude-data/core/data-access/conversations/fetchConversationWithMessages'
8+
import type { Workspace } from '@latitude-data/core/schema/models/types/Workspace'
9+
10+
type Props = {
11+
children: ReactNode
12+
projectId: number
13+
workspace: Workspace
14+
}
15+
16+
export async function LatteWrapper({ children, projectId, workspace }: Props) {
17+
const lastThreadUuid = await getLastLatteThreadUuidCached({ projectId })
18+
19+
const conversationResult = lastThreadUuid
20+
? await fetchConversationWithMessages({
21+
workspace,
22+
documentLogUuid: lastThreadUuid,
23+
})
24+
: undefined
25+
26+
const initialMessages = conversationResult?.value?.messages
27+
28+
return (
29+
<LatteRealtimeUpdatesProvider>
30+
<LatteLayout
31+
initialThreadUuid={lastThreadUuid}
32+
initialMessages={initialMessages}
33+
>
34+
{children}
35+
</LatteLayout>
36+
</LatteRealtimeUpdatesProvider>
37+
)
38+
}

apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/ProjectSection/index.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useMemo } from 'react'
44
import { formatCount } from '@latitude-data/constants/formatCount'
55
import { ROUTES } from '$/services/routes'
6+
import { useProductAccess } from '$/components/Providers/SessionProvider'
67

78
import { RunSourceGroup } from '@latitude-data/constants'
89
import { Commit } from '@latitude-data/core/schema/models/types/Commit'
@@ -85,6 +86,7 @@ export default function ProjectSection({
8586
project: Project
8687
commit: Commit
8788
}) {
89+
const { agentBuilder } = useProductAccess()
8890
const { value: lastRunTab } = useLocalStorage<RunSourceGroup>({
8991
key: AppLocalStorage.lastRunTab,
9092
defaultValue: RunSourceGroup.Playground,
@@ -93,7 +95,7 @@ export default function ProjectSection({
9395
const PROJECT_ROUTES = useMemo(
9496
() =>
9597
[
96-
{
98+
agentBuilder && {
9799
label: 'Home',
98100
route: ROUTES.projects
99101
.detail({ id: project.id })
@@ -123,7 +125,7 @@ export default function ProjectSection({
123125
iconName: 'history',
124126
},
125127
].filter(Boolean) as ProjectRoute[],
126-
[project, commit, lastRunTab],
128+
[project, commit, lastRunTab, agentBuilder],
127129
)
128130

129131
return (

apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/_components/DocumentEditor/Editor/SidebarArea/Tools/ActiveIntegration/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export function ActiveIntegration({
4646
onRemove,
4747
}: {
4848
integration: IActiveIntegration
49-
onRemove: (integrationName: string) => void
49+
onRemove?: (integrationName: string) => void
5050
}) {
5151
const { addIntegrationTool, removeIntegrationTool } = use(ToolsContext)
5252
const toggleIntegration = useSidebarStore((state) => state.toggleIntegration)
@@ -220,7 +220,7 @@ export function ActiveIntegration({
220220

221221
<CustomMcpHeadersButton integration={integration} />
222222

223-
{!isClientTools && (
223+
{!isClientTools && onRemove && (
224224
<DropdownMenu
225225
options={[
226226
{

apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/_components/DocumentEditor/Editor/SidebarArea/Tools/index.tsx

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,40 +7,72 @@ import { usePromptConfigInSidebar } from '../hooks/usePromptConfigInSidebar'
77
import { ActiveIntegration } from './ActiveIntegration'
88
import { ToolsProvider } from './ToolsProvider'
99
import { useSidebarStore } from '../hooks/useSidebarStore'
10+
import { CLIENT_TOOLS_INTEGRATION_NAME } from '../toolsHelpers/collectTools'
11+
import { Text } from '@latitude-data/web-ui/atoms/Text'
12+
import Link from 'next/link'
1013

11-
export function ToolsSidebarSection() {
14+
const TOOLS_DOCS_URL = 'https://docs.latitude.so/guides/prompt-manager/tools'
15+
16+
export function ToolsSidebarSection({
17+
agentBuilder,
18+
}: {
19+
agentBuilder: boolean
20+
}) {
1221
const { commit } = useCurrentCommit()
1322
const isLive = !!commit.mergedAt
1423
const { open, onOpen, onClose } = useToggleModal()
1524
const actions = useMemo(
16-
() => [{ onClick: onOpen, disabled: isLive }],
17-
[onOpen, isLive],
25+
() => (agentBuilder ? [{ onClick: onOpen, disabled: isLive }] : []),
26+
[onOpen, isLive, agentBuilder],
1827
)
1928
const {
2029
addNewIntegration,
2130
addIntegrationTool,
2231
removeIntegrationTool,
2332
removeIntegration,
2433
} = usePromptConfigInSidebar()
25-
const { integrations } = useSidebarStore((state) => ({
34+
const { integrations: allIntegrations } = useSidebarStore((state) => ({
2635
integrations: state.integrations,
2736
}))
2837

38+
const integrations = useMemo(() => {
39+
if (agentBuilder) return allIntegrations
40+
return allIntegrations.filter(
41+
(integration) => integration.name === CLIENT_TOOLS_INTEGRATION_NAME,
42+
)
43+
}, [allIntegrations, agentBuilder])
44+
45+
const showEmptyNote = !agentBuilder && integrations.length === 0
46+
2947
return (
3048
<ToolsProvider
3149
addIntegrationTool={addIntegrationTool}
3250
removeIntegrationTool={removeIntegrationTool}
3351
>
3452
<SidebarSection title='Tools' actions={actions}>
35-
{integrations.map((integration) => (
36-
<ActiveIntegration
37-
key={integration.name}
38-
integration={integration}
39-
onRemove={removeIntegration}
40-
/>
41-
))}
53+
{showEmptyNote ? (
54+
<Text.H6 color='foregroundMuted'>
55+
Tools configured in the prompt will appear here.{' '}
56+
<Link
57+
href={TOOLS_DOCS_URL}
58+
target='_blank'
59+
rel='noopener noreferrer'
60+
className='underline'
61+
>
62+
Learn more
63+
</Link>
64+
</Text.H6>
65+
) : (
66+
integrations.map((integration) => (
67+
<ActiveIntegration
68+
key={integration.name}
69+
integration={integration}
70+
onRemove={agentBuilder ? removeIntegration : undefined}
71+
/>
72+
))
73+
)}
4274
</SidebarSection>
43-
{open ? (
75+
{agentBuilder && open ? (
4476
<ConnectToolsModal
4577
onCloseModal={onClose}
4678
addNewIntegration={addNewIntegration}

apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/_components/DocumentEditor/Editor/SidebarArea/index.tsx

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useParams } from 'next/navigation'
33
import { FreeRunsBanner } from '$/components/FreeRunsBanner'
44
import { useIsLatitudeProvider } from '$/hooks/useIsLatitudeProvider'
55
import { useMetadata } from '$/hooks/useMetadata'
6+
import { useProductAccess } from '$/components/Providers/SessionProvider'
67
import { ResolvedMetadata } from '$/workers/readMetadata'
78
import { SectionLoader } from './Section'
89
import { SidebarHeader } from './SidebarHeader'
@@ -11,6 +12,7 @@ import { ToolsSidebarSection } from './Tools'
1112
import { SubAgentsSidebarSection } from './SubAgents'
1213
import { usePromptConfigData } from './hooks/usePromptConfigData'
1314
import { useSidebarStore } from './hooks/useSidebarStore'
15+
import { cn } from '@latitude-data/web-ui/utils'
1416

1517
function SidebarLoader() {
1618
return (
@@ -48,34 +50,40 @@ export function DocumentEditorSidebarArea({
4850
const reset = useSidebarStore((state) => state.reset)
4951
const { metadata } = useMetadata()
5052
const isLatitudeProvider = useIsLatitudeProvider({ metadata })
53+
const { agentBuilder } = useProductAccess()
5154
const data = useSidebarData({ metadata })
5255

53-
// Reset store when document changes or component unmounts
5456
useEffect(() => {
5557
return () => {
5658
reset()
5759
}
5860
}, [documentUuid, reset])
5961

6062
return (
61-
<div className='w-full relative flex flex-col gap-y-6 min-h-0 '>
63+
<div className='w-full relative flex flex-col gap-y-6'>
6264
<SidebarHeader metadata={metadata} />
6365
<FreeRunsBanner
6466
isLatitudeProvider={isLatitudeProvider}
6567
freeRunsCount={freeRunsCount}
6668
/>
67-
<div className='flex flex-col gap-y-6 min-w-0 custom-scrollbar scrollable-indicator'>
69+
<div
70+
className={cn('flex flex-col gap-y-6 min-w-0', {
71+
'custom-scrollbar scrollable-indicator': agentBuilder,
72+
})}
73+
>
6874
{data.isLoading ? (
6975
<SidebarLoader />
7076
) : (
7177
<>
72-
<TriggersSidebarSection
73-
triggers={data.triggersData.triggers}
74-
integrations={data.triggersData.integrations}
75-
document={data.triggersData.document}
76-
/>
77-
<ToolsSidebarSection />
78-
<SubAgentsSidebarSection />
78+
{agentBuilder && (
79+
<TriggersSidebarSection
80+
triggers={data.triggersData.triggers}
81+
integrations={data.triggersData.integrations}
82+
document={data.triggersData.document}
83+
/>
84+
)}
85+
<ToolsSidebarSection agentBuilder={agentBuilder} />
86+
{agentBuilder && <SubAgentsSidebarSection />}
7987
</>
8088
)}
8189
</div>

apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/home/page.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,21 @@ import {
44
findCommitCached,
55
getDocumentsAtCommitCached,
66
} from '$/app/(private)/_data-access'
7+
import { getProductAccess } from '$/services/productAccess/getProductAccess'
8+
import { ROUTES } from '$/services/routes'
9+
import { redirect } from 'next/navigation'
710
import { AgentPageWrapper } from './_components/AgentPageWrapper'
811

912
export default async function AgentPage({
1013
params,
1114
}: {
1215
params: Promise<{ projectId: string; commitUuid: string }>
1316
}) {
17+
const productAccess = await getProductAccess()
18+
if (!productAccess.agentBuilder) {
19+
redirect(ROUTES.dashboard.root)
20+
}
21+
1422
const { projectId, commitUuid } = await params
1523
const commit = await findCommitCached({
1624
uuid: commitUuid,

apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/layout.tsx

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
findCommitsByProjectCached,
77
findProjectCached,
88
getHeadCommitCached,
9-
getLastLatteThreadUuidCached,
109
} from '$/app/(private)/_data-access'
1110
import { ProjectPageParams } from '$/app/(private)/projects/[projectId]/page'
1211
import {
@@ -15,15 +14,14 @@ import {
1514
} from '$/services/auth/getCurrentUser'
1615
import { ROUTES } from '$/services/routes'
1716
import { notFound, redirect } from 'next/navigation'
18-
import { LatteRealtimeUpdatesProvider } from './providers/LatteRealtimeUpdatesProvider'
1917
import { HEAD_COMMIT } from '@latitude-data/core/constants'
2018

21-
import { LatteLayout } from '$/components/LatteSidebar/LatteLayout'
22-
import { fetchConversationWithMessages } from '@latitude-data/core/data-access/conversations/fetchConversationWithMessages'
2319
import { Commit } from '@latitude-data/core/schema/models/types/Commit'
2420
import { Project } from '@latitude-data/core/schema/models/types/Project'
2521
import { ProjectProvider } from '$/app/providers/ProjectProvider'
2622
import { CommitProvider } from '$/app/providers/CommitProvider'
23+
import { computeProductAccess } from '@latitude-data/core/services/productAccess/computeProductAccess'
24+
import { LatteWrapper } from './_components/LatteWrapper'
2725

2826
export type CommitPageParams = {
2927
children: ReactNode
@@ -73,28 +71,21 @@ export default async function CommitLayout({
7371
throw error
7472
}
7573

76-
const lastThreadUuid = await getLastLatteThreadUuidCached({
77-
projectId: project.id,
78-
})
79-
const conversationResult = lastThreadUuid
80-
? await fetchConversationWithMessages({
81-
workspace: session.workspace,
82-
documentLogUuid: lastThreadUuid,
83-
})
84-
: undefined
85-
const initialMessages = conversationResult?.value?.messages
74+
const productAccess = computeProductAccess(session.workspace)
8675

8776
return (
8877
<ProjectProvider project={project}>
8978
<CommitProvider project={project} commit={commit} isHead={isHead}>
90-
<LatteRealtimeUpdatesProvider>
91-
<LatteLayout
92-
initialThreadUuid={lastThreadUuid}
93-
initialMessages={initialMessages}
79+
{productAccess.agentBuilder ? (
80+
<LatteWrapper
81+
projectId={project.id}
82+
workspace={session.workspace}
9483
>
9584
{children}
96-
</LatteLayout>
97-
</LatteRealtimeUpdatesProvider>
85+
</LatteWrapper>
86+
) : (
87+
children
88+
)}
9889
</CommitProvider>
9990
</ProjectProvider>
10091
)

apps/web/src/app/(private)/settings/integrations/[integrationId]/destroy/page.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { use, useMemo } from 'react'
3+
import { use, useEffect, useMemo } from 'react'
44

55
import DestroyModal from '$/components/modals/DestroyModal'
66
import { useNavigate } from '$/hooks/useNavigate'
@@ -16,6 +16,7 @@ import { Icon } from '@latitude-data/web-ui/atoms/Icons'
1616
import { Skeleton } from '@latitude-data/web-ui/atoms/Skeleton'
1717
import useProjects from '$/stores/projects'
1818
import { useCommitsFromProject } from '$/stores/commitsStore'
19+
import { useProductAccess } from '$/components/Providers/SessionProvider'
1920

2021
type IntegrationReferenceGroup = {
2122
projectId: number
@@ -97,9 +98,16 @@ export default function DestroyIntegration({
9798
}) {
9899
const { integrationId } = use(params)
99100
const navigate = useNavigate()
101+
const { agentBuilder } = useProductAccess()
100102
const { data, destroy, isDestroying } = useIntegrations()
101103
const integration = data.find((p) => p.id === Number(integrationId))
102104

105+
useEffect(() => {
106+
if (!agentBuilder) {
107+
navigate.push(ROUTES.dashboard.root)
108+
}
109+
}, [agentBuilder, navigate])
110+
103111
const { data: references, isLoading } = useIntegrationReferences(integration)
104112

105113
const groupedReferences = useMemo<IntegrationReferenceGroup[]>(() => {
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1+
import { getProductAccess } from '$/services/productAccess/getProductAccess'
2+
import { ROUTES } from '$/services/routes'
3+
import { redirect } from 'next/navigation'
14
import NewIntegrationSetting from '../../_components/Integrations/New'
25

3-
export default function NewIntegration() {
6+
export default async function NewIntegration() {
7+
const productAccess = await getProductAccess()
8+
if (!productAccess.agentBuilder) {
9+
redirect(ROUTES.dashboard.root)
10+
}
11+
412
return <NewIntegrationSetting />
513
}

0 commit comments

Comments
 (0)