Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions brain/knowledge/engineering/platform-admin-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ silently. Worker groups is the last page on the old full-page teaser.
(`platform-routes.tsx`), and global search (`components/global-search/static-pages.ts`). Nothing
checks the three agree. A page whose locked state is the sample overlay also needs a
`PlanFeatureSample` entry, or it opens unlocked.
- **Behind the sample overlay, do not hide the page's buttons by plan.** The preview is `inert`, so
hiding them (AI Center's old `allowWrite`) only makes the teaser look empty. The overlay is UI only:
a server gate is a separate `platformMustHaveFeatureEnabled`, and AI Center's write routes have none.
A feature sold on a tier not in `FeatureTier` (AI Center is on Plus) needs that tier added there first.
- **Moving or renaming a route needs a `LEGACY_PATHS` row**, or every bookmark, doc link and in-flight
Stripe checkout to the old URL lands on a 404.
- **Source folders still follow the old grouping** (`routes/platform/security/embed`,
Expand Down
7 changes: 6 additions & 1 deletion packages/web/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -3144,5 +3144,10 @@
"{count} pieces, ready to run in every project MCP reaches.": "{count} pieces, ready to run in every project MCP reaches.",
"Reset {name}": "Reset {name}",
"Chat goes back to using {provider}.": "Chat goes back to using {provider}.",
"The assistant can't do this yet": "The assistant can't do this yet"
"The assistant can't do this yet": "The assistant can't do this yet",
"Unlock AI Center": "Unlock AI Center",
"Bring your own AI provider keys and choose the models that power your automations": "Bring your own AI provider keys and choose the models that power your automations",
"Connect OpenAI, Anthropic, Google and more": "Connect OpenAI, Anthropic, Google and more",
"Pick the default model for each AI capability": "Pick the default model for each AI capability",
"Pay your provider directly, with no model markup": "Pay your provider directly, with no model markup"
}
5 changes: 3 additions & 2 deletions packages/web/src/app/components/sidebar/ap-sidebar-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export const ApSidebarItem = (item: SidebarItemType) => {
<SidebarMenuSub className="mx-0 ml-7 border-0 px-0 py-1">
{subItems.map((subItem) => {
const shut = isSubItemLocked(subItem);
const subItemTier = subItem.tier ?? item.tier;
const subItemActive = isRouteActive({
pathname,
to: subItem.to,
Expand All @@ -159,7 +160,7 @@ export const ApSidebarItem = (item: SidebarItemType) => {
? () =>
captureLockedClick({
path: subItem.to,
tier: subItem.tier,
tier: subItemTier,
})
: undefined
}
Expand All @@ -174,7 +175,7 @@ export const ApSidebarItem = (item: SidebarItemType) => {
return (
<SidebarMenuSubItem key={subItem.to}>
{shut && !isCrowned ? (
<LockedTooltip tier={subItem.tier}>{subButton}</LockedTooltip>
<LockedTooltip tier={subItemTier}>{subButton}</LockedTooltip>
) : (
subButton
)}
Expand Down
2 changes: 2 additions & 0 deletions packages/web/src/app/components/sidebar/platform/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ export function PlatformSidebar() {
to: '/platform/ai',
label: t('AI Center'),
icon: SparklesIcon,
locked: !platform.plan.aiProvidersEnabled,
tier: PLATFORM_FEATURES.aiProviders.tier,
subItems:
edition === ApEdition.COMMUNITY
? undefined
Expand Down
16 changes: 10 additions & 6 deletions packages/web/src/app/routes/platform-routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,11 @@ export const platformRoutes = [
<PlatformLayout>
<PageTitle title="AI Center">
<LegacyTabRedirect basePath="/platform/ai" tabPaths={AI_TAB_PATHS}>
<SuspenseWrapper>
<AIProvidersPage section="providers" />
</SuspenseWrapper>
<PlanFeatureSample feature="aiProviders">
<SuspenseWrapper>
<AIProvidersPage section="providers" />
</SuspenseWrapper>
</PlanFeatureSample>
</LegacyTabRedirect>
</PageTitle>
</PlatformLayout>
Expand All @@ -168,9 +170,11 @@ export const platformRoutes = [
<PlatformLayout>
<PageTitle title="AI Capabilities">
<LegacyTabRedirect basePath="/platform/ai" tabPaths={AI_TAB_PATHS}>
<SuspenseWrapper>
<AIProvidersPage section="capabilities" />
</SuspenseWrapper>
<PlanFeatureSample feature="aiProviders">
<SuspenseWrapper>
<AIProvidersPage section="capabilities" />
</SuspenseWrapper>
</PlanFeatureSample>
</LegacyTabRedirect>
</PageTitle>
</PlatformLayout>
Expand Down
13 changes: 12 additions & 1 deletion packages/web/src/app/routes/platform/plan-feature-sample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,16 @@ const PLAN_FEATURE_SAMPLES: Record<PlanFeature, PlanFeatureSampleSpec> = {
tier: 'enterprise',
},
},
aiProviders: {
isLocked: (plan) => !plan.aiProvidersEnabled,
teaser: {
featureKey: 'UNIVERSAL_AI',
title: 'Unlock AI Center',
description:
'Bring your own AI provider keys and choose the models that power your automations',
tier: 'plus',
},
},
};

type PlanFeature =
Expand All @@ -125,7 +135,8 @@ type PlanFeature =
| 'apiKeys'
| 'secretManagers'
| 'auditLogs'
| 'eventStreaming';
| 'eventStreaming'
| 'aiProviders';

type PlanFeatureSampleSpec = {
isLocked: (plan: PlatformWithoutSensitiveData['plan']) => boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
aiToolConfigMutations,
aiToolConfigQueries,
} from '@/features/platform-admin';
import { platformHooks } from '@/hooks/platform-hooks';
import { cn } from '@/lib/utils';

import { AiCapabilityDialog } from '../../ai-capabilities/ai-capability-dialog';
Expand All @@ -34,8 +33,6 @@ export function CapabilitiesTab() {
isError: configsFailed,
refetch: refetchConfigs,
} = aiToolConfigQueries.useAiToolConfigs();
const { platform } = platformHooks.useCurrentPlatform();
const allowWrite = platform.plan.aiProvidersEnabled;
const {
data: providers,
isError: providersFailed,
Expand Down Expand Up @@ -86,7 +83,6 @@ export function CapabilitiesTab() {
? chatProvider
: undefined
}
allowWrite={allowWrite}
onDelete={() => config && remove(config.id)}
onSaved={() => refetchConfigs()}
/>
Expand All @@ -103,15 +99,13 @@ function CapabilityCard({
config,
providers,
chatProviderFallback,
allowWrite,
onDelete,
onSaved,
}: {
capabilityInfo: AiToolCapabilityInfo;
config?: AiToolConfigWithoutSensitiveData;
providers: AIProviderWithoutSensitiveData[];
chatProviderFallback?: AIProviderWithoutSensitiveData;
allowWrite: boolean;
onDelete: () => void;
onSaved: () => void;
}) {
Expand Down Expand Up @@ -160,7 +154,7 @@ function CapabilityCard({
</TextWithTooltip>
</span>
</div>
{config && allowWrite && (
{config && (
<ConfirmationDeleteDialog
title={t('Reset {name}', { name: capabilityInfo.name })}
message={
Expand Down Expand Up @@ -194,18 +188,16 @@ function CapabilityCard({
? t('Available to the assistant')
: t("The assistant can't do this yet")}
</span>
{allowWrite && (
<AiCapabilityDialog
capabilityInfo={capabilityInfo}
existingConfig={config}
defaultProviderId={chatProviderFallback?.id}
onSaved={onSaved}
>
<Button variant="outline" size="sm">
{inUse ? t('Change') : t('Connect')}
</Button>
</AiCapabilityDialog>
)}
<AiCapabilityDialog
capabilityInfo={capabilityInfo}
existingConfig={config}
defaultProviderId={chatProviderFallback?.id}
onSaved={onSaved}
>
<Button variant="outline" size="sm">
{inUse ? t('Change') : t('Connect')}
</Button>
</AiCapabilityDialog>
</div>
</div>
);
Expand Down
Loading
Loading