diff --git a/packages/api-v4/src/account/types.ts b/packages/api-v4/src/account/types.ts
index 22956b38a27..1454cca1ee9 100644
--- a/packages/api-v4/src/account/types.ts
+++ b/packages/api-v4/src/account/types.ts
@@ -60,6 +60,7 @@ export interface Account {
export type BillingSource = 'akamai' | 'linode';
export const accountCapabilities = [
+ 'AI',
'Akamai Cloud Load Balancer',
'Akamai Cloud Pulse',
'Akamai Cloud Pulse Logs',
diff --git a/packages/api-v4/src/cloudpulse/types.ts b/packages/api-v4/src/cloudpulse/types.ts
index 8a18b38cc0a..f277c36adbf 100644
--- a/packages/api-v4/src/cloudpulse/types.ts
+++ b/packages/api-v4/src/cloudpulse/types.ts
@@ -4,6 +4,7 @@ export type AlertSeverityType = 0 | 1 | 2 | 3;
export type MetricAggregationType = 'avg' | 'count' | 'max' | 'min' | 'sum';
export type MetricOperatorType = 'eq' | 'gt' | 'gte' | 'lt' | 'lte';
export type CloudPulseServiceType =
+ | 'ai'
| 'blockstorage'
| 'dbaas'
| 'firewall'
@@ -401,6 +402,7 @@ export const capabilityServiceTypeMapping: Record<
lke: 'Kubernetes',
netloadbalancer: 'Network LoadBalancer',
logs: 'Akamai Cloud Pulse Logs',
+ ai: 'AI',
};
/**
diff --git a/packages/manager/src/assets/icons/entityIcons/ai.svg b/packages/manager/src/assets/icons/entityIcons/ai.svg
new file mode 100644
index 00000000000..2cf5bcbce8c
--- /dev/null
+++ b/packages/manager/src/assets/icons/entityIcons/ai.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/packages/manager/src/components/PrimaryNav/PrimaryNav.tsx b/packages/manager/src/components/PrimaryNav/PrimaryNav.tsx
index 93ed6a9b6cc..3a367e41ef5 100644
--- a/packages/manager/src/components/PrimaryNav/PrimaryNav.tsx
+++ b/packages/manager/src/components/PrimaryNav/PrimaryNav.tsx
@@ -7,6 +7,7 @@ import { Box } from '@linode/ui';
import { useLocation } from '@tanstack/react-router';
import * as React from 'react';
+import AI from 'src/assets/icons/entityIcons/ai.svg';
import Compute from 'src/assets/icons/entityIcons/compute.svg';
import CoreUser from 'src/assets/icons/entityIcons/coreuser.svg';
import Database from 'src/assets/icons/entityIcons/database.svg';
@@ -26,6 +27,7 @@ import { useIsMarketplaceV2Enabled } from 'src/features/Marketplace/shared';
import { useIsNetworkLoadBalancerEnabled } from 'src/features/NetworkLoadBalancers/utils';
import { useIsPlacementGroupsEnabled } from 'src/features/PlacementGroups/utils';
import { useIsReserveIpEnabled } from 'src/features/ReservedIps/utils';
+import { useIsServerlessInferenceEnabled } from 'src/features/ServerlessInference/utils';
import { useFlags } from 'src/hooks/useFlags';
import PrimaryLink from './PrimaryLink';
@@ -67,6 +69,7 @@ export type NavEntity =
| 'Quick Deploy Apps'
| 'Quotas'
| 'Reserved IPs'
+ | 'Serverless Inference'
| 'Service Transfers'
| 'StackScripts'
| 'Users & Grants'
@@ -75,6 +78,7 @@ export type NavEntity =
export type ProductFamily =
| 'Administration'
+ | 'AI'
| 'Compute'
| 'Databases'
| 'Monitor'
@@ -140,6 +144,8 @@ export const PrimaryNav = (props: PrimaryNavProps) => {
const { isNetworkLoadBalancerEnabled } = useIsNetworkLoadBalancerEnabled();
+ const { isServerlessInferenceEnabled } = useIsServerlessInferenceEnabled();
+
const { isMarketplaceV2FeatureEnabled } = useIsMarketplaceV2Enabled();
const { isReserveIpEnabled } = useIsReserveIpEnabled();
@@ -254,6 +260,17 @@ export const PrimaryNav = (props: PrimaryNavProps) => {
],
name: 'Networking',
},
+ {
+ icon: ,
+ links: [
+ {
+ display: 'Serverless Inference',
+ hide: !isServerlessInferenceEnabled,
+ to: '/serverless-inference',
+ },
+ ],
+ name: 'AI',
+ },
{
icon: ,
links: [
@@ -372,6 +389,7 @@ export const PrimaryNav = (props: PrimaryNavProps) => {
isMarketplaceV2FeatureEnabled,
isNetworkLoadBalancerEnabled,
isReserveIpEnabled,
+ isServerlessInferenceEnabled,
limitsEvolution,
]
);
diff --git a/packages/manager/src/featureFlags.ts b/packages/manager/src/featureFlags.ts
index 02e26be0afc..98e6b3b87e8 100644
--- a/packages/manager/src/featureFlags.ts
+++ b/packages/manager/src/featureFlags.ts
@@ -291,6 +291,7 @@ export interface Flags {
resourceLock: ResourceLockFlag;
secureVmCopy: SecureVMCopy;
selfServeBetas: boolean;
+ serverlessInference: boolean;
soldOutChips: boolean;
supportTicketSeverity: boolean;
taxBanner: TaxBanner;
diff --git a/packages/manager/src/features/ServerlessInference/ApiKeyManagement/ApiKeyManagement.tsx b/packages/manager/src/features/ServerlessInference/ApiKeyManagement/ApiKeyManagement.tsx
new file mode 100644
index 00000000000..1a690ad60e0
--- /dev/null
+++ b/packages/manager/src/features/ServerlessInference/ApiKeyManagement/ApiKeyManagement.tsx
@@ -0,0 +1,5 @@
+import React from 'react';
+
+export const ApiKeyManagement = () => {
+ return
API Key Management content
;
+};
diff --git a/packages/manager/src/features/ServerlessInference/InferenceHub/InferenceHub.tsx b/packages/manager/src/features/ServerlessInference/InferenceHub/InferenceHub.tsx
new file mode 100644
index 00000000000..30d05c5fc9f
--- /dev/null
+++ b/packages/manager/src/features/ServerlessInference/InferenceHub/InferenceHub.tsx
@@ -0,0 +1,5 @@
+import React from 'react';
+
+export const InferenceHub = () => {
+ return Inference Hub content
;
+};
diff --git a/packages/manager/src/features/ServerlessInference/ModelLibrary/ModelLibrary.tsx b/packages/manager/src/features/ServerlessInference/ModelLibrary/ModelLibrary.tsx
new file mode 100644
index 00000000000..db30078f313
--- /dev/null
+++ b/packages/manager/src/features/ServerlessInference/ModelLibrary/ModelLibrary.tsx
@@ -0,0 +1,5 @@
+import React from 'react';
+
+export const ModelLibrary = () => {
+ return Model Library content
;
+};
diff --git a/packages/manager/src/features/ServerlessInference/ModelPlayground/ModelPlayground.tsx b/packages/manager/src/features/ServerlessInference/ModelPlayground/ModelPlayground.tsx
new file mode 100644
index 00000000000..4b58622955e
--- /dev/null
+++ b/packages/manager/src/features/ServerlessInference/ModelPlayground/ModelPlayground.tsx
@@ -0,0 +1,5 @@
+import React from 'react';
+
+export const ModelPlayground = () => {
+ return Model Playground content
;
+};
diff --git a/packages/manager/src/features/ServerlessInference/ServerlessInference.tsx b/packages/manager/src/features/ServerlessInference/ServerlessInference.tsx
new file mode 100644
index 00000000000..b7a580b5adb
--- /dev/null
+++ b/packages/manager/src/features/ServerlessInference/ServerlessInference.tsx
@@ -0,0 +1,83 @@
+import { useLocation } from '@tanstack/react-router';
+import * as React from 'react';
+
+import { DocumentTitleSegment } from 'src/components/DocumentTitle';
+import { LandingHeader } from 'src/components/LandingHeader';
+import { SuspenseLoader } from 'src/components/SuspenseLoader';
+import { SafeTabPanel } from 'src/components/Tabs/SafeTabPanel';
+import { TabPanels } from 'src/components/Tabs/TabPanels';
+import { Tabs } from 'src/components/Tabs/Tabs';
+import { TanStackTabLinkList } from 'src/components/Tabs/TanStackTabLinkList';
+import { Tab, useTabs } from 'src/hooks/useTabs';
+
+const InferenceHub = React.lazy(() =>
+ import('./InferenceHub/InferenceHub').then((m) => ({
+ default: m.InferenceHub,
+ }))
+);
+
+const ModelPlayground = React.lazy(() =>
+ import('./ModelPlayground/ModelPlayground').then((m) => ({
+ default: m.ModelPlayground,
+ }))
+);
+
+const ApiKeyManagement = React.lazy(() =>
+ import('./ApiKeyManagement/ApiKeyManagement').then((m) => ({
+ default: m.ApiKeyManagement,
+ }))
+);
+
+const ModelLibrary = React.lazy(() =>
+ import('./ModelLibrary/ModelLibrary').then((m) => ({
+ default: m.ModelLibrary,
+ }))
+);
+
+export const ServerlessInference = () => {
+ // useLocation subscribes to route changes, ensuring the component re-renders
+ // on navigation so useTabs can recompute the active tab index.
+ useLocation();
+
+ const tabs: Tab[] = [
+ { title: 'Inference Hub', to: '/serverless-inference/inference-hub' },
+ { title: 'Model Playground', to: '/serverless-inference/model-playground' },
+ { title: 'Model Library', to: '/serverless-inference/model-library' },
+ {
+ title: 'API Key Management',
+ to: '/serverless-inference/api-key-management',
+ },
+ ];
+
+ const { handleTabChange, tabIndex } = useTabs(tabs);
+
+ return (
+
+
+
+
+
+ }>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/manager/src/features/ServerlessInference/serverlessInferenceLazyRoute.ts b/packages/manager/src/features/ServerlessInference/serverlessInferenceLazyRoute.ts
new file mode 100644
index 00000000000..1e727ed9a36
--- /dev/null
+++ b/packages/manager/src/features/ServerlessInference/serverlessInferenceLazyRoute.ts
@@ -0,0 +1,9 @@
+import { createLazyRoute } from '@tanstack/react-router';
+
+import { ServerlessInference } from './ServerlessInference';
+
+export const serverlessInferenceLazyRoute = createLazyRoute(
+ '/serverless-inference'
+)({
+ component: ServerlessInference,
+});
diff --git a/packages/manager/src/features/ServerlessInference/utils.ts b/packages/manager/src/features/ServerlessInference/utils.ts
new file mode 100644
index 00000000000..1bcaba32490
--- /dev/null
+++ b/packages/manager/src/features/ServerlessInference/utils.ts
@@ -0,0 +1,23 @@
+import { useAccount } from '@linode/queries';
+import { isFeatureEnabledV2 } from '@linode/utilities';
+
+import { useFlags } from 'src/hooks/useFlags';
+
+export const useIsServerlessInferenceEnabled = (): {
+ isServerlessInferenceEnabled: boolean;
+} => {
+ const { data: account } = useAccount();
+ const flags = useFlags();
+
+ if (!flags) {
+ return { isServerlessInferenceEnabled: false };
+ }
+
+ const isServerlessInferenceEnabled = isFeatureEnabledV2(
+ 'AI',
+ Boolean(flags.serverlessInference),
+ account?.capabilities ?? []
+ );
+
+ return { isServerlessInferenceEnabled };
+};
diff --git a/packages/manager/src/mocks/serverHandlers.ts b/packages/manager/src/mocks/serverHandlers.ts
index 3e922548ced..dea4b86386d 100644
--- a/packages/manager/src/mocks/serverHandlers.ts
+++ b/packages/manager/src/mocks/serverHandlers.ts
@@ -3958,6 +3958,7 @@ export const handlers = [
http.get('*/monitor/services/:serviceType', ({ params }) => {
const serviceType = params.serviceType as CloudPulseServiceType;
const serviceTypesMap: Record = {
+ ai: 'AI',
linode: 'Linode',
dbaas: 'Databases',
nodebalancer: 'NodeBalancers',
diff --git a/packages/manager/src/routes/index.tsx b/packages/manager/src/routes/index.tsx
index 2c3ff935a35..07d21d4c410 100644
--- a/packages/manager/src/routes/index.tsx
+++ b/packages/manager/src/routes/index.tsx
@@ -40,6 +40,7 @@ import { quotasRouteTree } from './quotas';
import { reservedIpsRouteTree } from './reservedIps';
import { rootRoute } from './root';
import { searchRouteTree } from './search';
+import { serverlessInferenceRouteTree } from './serverlessInference';
import { serviceTransfersRouteTree } from './serviceTransfers';
import { stackScriptsRouteTree } from './stackscripts';
import { supportRouteTree } from './support';
@@ -91,6 +92,7 @@ export const routeTree = rootRoute.addChildren([
quotasRouteTree,
reservedIpsRouteTree,
searchRouteTree,
+ serverlessInferenceRouteTree,
serviceTransfersRouteTree,
settingsRouteTree,
stackScriptsRouteTree,
diff --git a/packages/manager/src/routes/serverlessInference/ServerlessInferenceRoute.tsx b/packages/manager/src/routes/serverlessInference/ServerlessInferenceRoute.tsx
new file mode 100644
index 00000000000..a8e1493b0f1
--- /dev/null
+++ b/packages/manager/src/routes/serverlessInference/ServerlessInferenceRoute.tsx
@@ -0,0 +1,21 @@
+import { NotFound } from '@linode/ui';
+import { Outlet } from '@tanstack/react-router';
+import React from 'react';
+
+import { DocumentTitleSegment } from 'src/components/DocumentTitle';
+import { SuspenseLoader } from 'src/components/SuspenseLoader';
+import { useIsServerlessInferenceEnabled } from 'src/features/ServerlessInference/utils';
+
+export const ServerlessInferenceRoute = () => {
+ const { isServerlessInferenceEnabled } = useIsServerlessInferenceEnabled();
+
+ if (!isServerlessInferenceEnabled) {
+ return ;
+ }
+ return (
+ }>
+
+
+
+ );
+};
diff --git a/packages/manager/src/routes/serverlessInference/index.ts b/packages/manager/src/routes/serverlessInference/index.ts
new file mode 100644
index 00000000000..2321c254e51
--- /dev/null
+++ b/packages/manager/src/routes/serverlessInference/index.ts
@@ -0,0 +1,67 @@
+import { createRoute, redirect } from '@tanstack/react-router';
+
+import { rootRoute } from '../root';
+import { ServerlessInferenceRoute } from './ServerlessInferenceRoute';
+
+const serverlessInferenceRoute = createRoute({
+ component: ServerlessInferenceRoute,
+ getParentRoute: () => rootRoute,
+ path: 'serverless-inference',
+});
+
+const serverlessInferenceIndexRoute = createRoute({
+ beforeLoad: async () => {
+ throw redirect({ to: '/serverless-inference/inference-hub' });
+ },
+ getParentRoute: () => serverlessInferenceRoute,
+ path: '/',
+}).lazy(() =>
+ import('src/features/ServerlessInference/serverlessInferenceLazyRoute').then(
+ (m) => m.serverlessInferenceLazyRoute
+ )
+);
+
+const serverlessInferenceInferenceHubRoute = createRoute({
+ getParentRoute: () => serverlessInferenceRoute,
+ path: 'inference-hub',
+}).lazy(() =>
+ import('src/features/ServerlessInference/serverlessInferenceLazyRoute').then(
+ (m) => m.serverlessInferenceLazyRoute
+ )
+);
+
+const serverlessInferenceModelPlaygroundRoute = createRoute({
+ getParentRoute: () => serverlessInferenceRoute,
+ path: 'model-playground',
+}).lazy(() =>
+ import('src/features/ServerlessInference/serverlessInferenceLazyRoute').then(
+ (m) => m.serverlessInferenceLazyRoute
+ )
+);
+
+const serverlessInferenceApiKeyManagementRoute = createRoute({
+ getParentRoute: () => serverlessInferenceRoute,
+ path: 'api-key-management',
+}).lazy(() =>
+ import('src/features/ServerlessInference/serverlessInferenceLazyRoute').then(
+ (m) => m.serverlessInferenceLazyRoute
+ )
+);
+
+const serverlessInferenceModelLibraryRoute = createRoute({
+ getParentRoute: () => serverlessInferenceRoute,
+ path: 'model-library',
+}).lazy(() =>
+ import('src/features/ServerlessInference/serverlessInferenceLazyRoute').then(
+ (m) => m.serverlessInferenceLazyRoute
+ )
+);
+
+export const serverlessInferenceRouteTree =
+ serverlessInferenceRoute.addChildren([
+ serverlessInferenceIndexRoute,
+ serverlessInferenceInferenceHubRoute,
+ serverlessInferenceModelPlaygroundRoute,
+ serverlessInferenceApiKeyManagementRoute,
+ serverlessInferenceModelLibraryRoute,
+ ]);