docs(nx-dev): update ai page (#31669)

Co-authored-by: Juri <juri.strumpflohner@gmail.com>
This commit is contained in:
Benjamin Cabanes 2025-06-23 09:42:11 -04:00 committed by GitHub
parent 755de341a4
commit 4e55020b1b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 982 additions and 97 deletions

7
.cursor/mcp.json Normal file
View File

@ -0,0 +1,7 @@
{
"mcpServers": {
"nx-mcp": {
"url": "http://localhost:9470/sse"
}
}
}

2
.gitignore vendored
View File

@ -101,7 +101,7 @@ node_modules/
# OS specific # OS specific
# Task files # Task files
tasks.json tasks.json
tasks/ tasks/
# Raw docs local configuration (machine-specific) # Raw docs local configuration (machine-specific)
.rawdocs.local.json .rawdocs.local.json

View File

@ -1,76 +0,0 @@
import type { Metadata } from 'next';
import { DefaultLayout } from '@nx/nx-dev/ui-common';
import { Hero } from '@nx/nx-dev/ui-ai-landing-page';
import { ProblemStatement } from '@nx/nx-dev/ui-ai-landing-page';
import { Features } from '@nx/nx-dev/ui-ai-landing-page';
import { CallToAction } from '@nx/nx-dev/ui-ai-landing-page';
import { TechnicalImplementation } from '@nx/nx-dev/ui-ai-landing-page';
export const metadata: Metadata = {
title: 'Nx - Make AI work in large codebases',
description:
'Empower your AI assistants with workspace intelligence to understand your codebase structure, project dependencies, and build processes at a glance.',
alternates: {
canonical: 'https://nx.dev/ai',
},
openGraph: {
title: 'Nx - Make AI work in large codebases',
description:
'Empower your AI assistants with workspace intelligence to understand your codebase structure, project dependencies, and build processes at a glance.',
url: 'https://nx.dev/ai',
siteName: 'Nx',
images: [
{
url: 'https://nx.dev/images/nx-ai-landing-og.png',
width: 1200,
height: 630,
},
],
locale: 'en_US',
type: 'website',
},
keywords: [
'nx',
'ai',
'workspace',
'architecture',
'codebase',
'llm',
'AI workspace development',
'LLM code assistant',
'Nx AI integration',
'multi-project AI tools',
'enterprise AI development',
'intelligent code generation',
'MCP server',
'workspace AI tools',
'monorepo AI',
'architectural intelligence',
'code assistant',
'workspace intelligence',
],
};
export default function AiLandingPage() {
return (
<DefaultLayout>
<Hero />
<div className="mt-32 lg:mt-56" id="problem-statement">
<ProblemStatement />
</div>
<div className="mt-32 lg:mt-56" id="features">
<Features />
</div>
<div className="mt-32 lg:mt-56" id="how-it-works">
<TechnicalImplementation />
</div>
<div className="mt-32 lg:mt-56">
<CallToAction />
</div>
</DefaultLayout>
);
}

View File

@ -0,0 +1,60 @@
import type { Metadata } from 'next';
import { DefaultLayout } from '@nx/nx-dev/ui-common';
import {
AiHero,
CallToAction,
WhileCoding,
WhileRunningCi,
WhileScalingYourOrganization,
} from '@nx/nx-dev/ui-ai-landing-page';
import type { ReactElement } from 'react';
import { NextSeo } from 'next-seo';
export function Ai(): ReactElement {
return (
<>
<NextSeo
title="From your editor to CI, Nx makes your AI a lot more powerful."
description="Empower your AI assistants with workspace intelligence to understand your codebase structure, project dependencies, and build processes at a glance."
openGraph={{
url: 'https://nx.dev/ai',
title:
'From your editor to CI, Nx makes your AI a lot more powerful.',
description:
'Empower your AI assistants with workspace intelligence to understand your codebase structure, project dependencies, and build processes at a glance.',
images: [
{
url: 'https://nx.dev/socials/nx-media.png',
width: 800,
height: 421,
alt: 'Nx: Smart Repos · Fast Builds',
type: 'image/jpeg',
},
],
siteName: 'Nx',
type: 'website',
}}
canonical="https://nx.dev/ai"
/>
<DefaultLayout>
<AiHero />
<div className="mt-12 lg:mt-24">
<WhileCoding />
</div>
<div className="mt-32 lg:mt-56">
<WhileRunningCi />
</div>
<div className="mt-32 lg:mt-56">
<WhileScalingYourOrganization />
</div>
<div className="mt-32 lg:mt-56">
<CallToAction />
</div>
</DefaultLayout>
</>
);
}
export default Ai;

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 87 KiB

View File

@ -1,4 +1,8 @@
export * from './lib/hero'; export * from './lib/hero';
export * from './lib/new/ai-hero';
export * from './lib/new/while-coding';
export * from './lib/new/while-running-ci';
export * from './lib/new/while-scaling-your-organization';
export * from './lib/problem-statement'; export * from './lib/problem-statement';
export * from './lib/features'; export * from './lib/features';
export * from './lib/technical-implementation'; export * from './lib/technical-implementation';

View File

@ -56,7 +56,7 @@ export function CallToAction(): ReactElement {
</div> </div>
{/* Content */} {/* Content */}
<div className="mx-auto max-w-2xl text-center"> <div className="mx-auto max-w-3xl text-center">
<h2 className="text-3xl font-medium tracking-tight text-slate-950 sm:text-5xl dark:text-white"> <h2 className="text-3xl font-medium tracking-tight text-slate-950 sm:text-5xl dark:text-white">
Transform your AI assistant in minutes Transform your AI assistant in minutes
</h2> </h2>
@ -75,14 +75,6 @@ export function CallToAction(): ReactElement {
</span> </span>
</Link> </Link>
{/* <Link
href="https://youtu.be/RNilYmJJzdk"
target="_blank"
rel="noopener noreferrer"
className="text-sm font-semibold leading-6 text-slate-900 dark:text-white"
>
Watch 3-min Demo <span aria-hidden="true"></span>
</Link> */}
</div> </div>
</div> </div>
</section> </section>

View File

@ -0,0 +1,69 @@
import type { ReactElement } from 'react';
import { ButtonLink, SectionHeading } from '@nx/nx-dev/ui-common';
import {
CodeBracketIcon,
ServerStackIcon,
UserGroupIcon,
} from '@heroicons/react/24/outline';
import { NxPowerAi } from './nx-power-ai';
export function AiHero(): ReactElement {
return (
<section>
<div className="mx-auto flex max-w-7xl">
<div className="max-w-4xl px-6 pb-24 pt-12 lg:mx-0 lg:shrink-0 lg:px-8">
<SectionHeading
id="get-speed-and-scale"
as="h1"
variant="display"
className="text-pretty tracking-tight"
>
From editor to CI, <br /> Nx makes your AI <br />
<span className="rounded-lg bg-gradient-to-r from-pink-500 to-fuchsia-500 bg-clip-text text-transparent">
a lot more powerful.
</span>
</SectionHeading>
<div className="mt-6">
<ButtonLink
href="/getting-started/ai-integration"
title="Nx AI Integration"
variant="primary"
size="small"
>
Integrate Nx with your Coding Assistant
</ButtonLink>
</div>
<div className="mt-8 flex flex-wrap items-center gap-4">
<a
href="#while-coding"
className="inline-flex items-center gap-1.5 rounded-full bg-slate-100 px-2 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-200 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-slate-800 dark:text-slate-300 dark:hover:bg-slate-700"
aria-label="Jump to While Coding section"
>
<CodeBracketIcon aria-hidden="true" className="size-4 shrink-0" />
<span>Coding</span>
</a>
<a
href="#while-running-ci"
className="inline-flex items-center gap-1.5 rounded-full bg-slate-100 px-2 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-200 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-slate-800 dark:text-slate-300 dark:hover:bg-slate-700"
aria-label="Jump to While Running CI section"
>
<ServerStackIcon aria-hidden="true" className="size-4 shrink-0" />
<span>Running CI</span>
</a>
<a
href="#while-scaling-your-organization"
className="inline-flex items-center gap-1.5 rounded-full bg-slate-100 px-2 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-200 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-slate-800 dark:text-slate-300 dark:hover:bg-slate-700"
aria-label="Jump to Scaling Your Organization section"
>
<UserGroupIcon aria-hidden="true" className="size-4 shrink-0" />
<span>Scaling Your Organization</span>
</a>
</div>
</div>
<div className="hidden w-auto grow grid-cols-1 place-items-center lg:grid">
<NxPowerAi className="-mt-8" />
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,84 @@
import { ReactElement, ReactNode } from 'react';
import Image from 'next/image';
import { PlayButton } from './play-button';
type VideoFeature = {
type: 'video';
videoUrl: string;
onPlayClick?: (videoUrl: string) => void;
};
type LinkFeature = {
type?: 'link';
videoUrl?: never;
onPlayClick?: never;
};
interface BaseFeatureCardProps {
isAvailable: boolean;
id: string;
title: string;
subtitle: string;
description: ReactNode;
imageUrl: string;
}
export type FeatureCardProps = BaseFeatureCardProps &
(VideoFeature | LinkFeature);
export function FeatureCard({
isAvailable,
id,
title,
subtitle,
description,
type = 'link',
imageUrl,
videoUrl,
onPlayClick,
}: FeatureCardProps): ReactElement {
const handlePlayClick = () => {
if (type === 'video' && videoUrl && onPlayClick) {
onPlayClick(videoUrl);
} else if (type === 'video' && !videoUrl) {
console.warn(
`Video type specified for ${title} but no videoUrl provided`
);
}
};
return (
<div key={id} className="flex flex-col">
<dt className="text-base/7 font-semibold">
<div className="relative mb-6 aspect-video max-h-52 w-full overflow-hidden rounded-xl bg-slate-200 dark:bg-slate-700/60">
<Image
src={imageUrl}
alt={`Thumbnail for ${title}`}
width={1280}
height={720}
loading="lazy"
unoptimized
/>
{type === 'video' && videoUrl && onPlayClick ? (
<div className="absolute inset-0 grid h-full w-full items-center justify-center">
<PlayButton onClick={handlePlayClick} />
</div>
) : null}
{!isAvailable && (
<span className="absolute bottom-2 right-2 inline-flex items-center rounded-md bg-blue-50 px-2 py-1 text-xs font-medium text-blue-700 ring-1 ring-inset ring-blue-700/10 dark:bg-blue-400/10 dark:text-blue-400 dark:ring-blue-400/30">
Coming soon
</span>
)}
</div>
{title}
<div className="text-xs/7 font-normal italic opacity-80">
{subtitle}
</div>
</dt>
<dd className="mt-1 flex flex-auto flex-col text-base/7">
{description}
</dd>
</div>
);
}

View File

@ -0,0 +1,182 @@
import type { FC, ReactElement, SVGProps } from 'react';
import {
ClaudeIcon,
GoogleGeminiIcon,
MetaIcon,
OpenAiIcon,
} from '@nx/nx-dev/ui-icons';
import { cx } from '@nx/nx-dev/ui-primitives';
const HexagonShape: FC<SVGProps<SVGSVGElement>> = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 22 24"
{...props}
>
<path d="M8.6 1.386C9.474.88 9.911.628 10.376.53a3 3 0 0 1 1.248 0c.464.098.902.35 1.776.856l5.592 3.228c.875.505 1.312.758 1.63 1.11.281.313.494.681.623 1.081.147.452.147.957.147 1.966v6.458c0 1.01 0 1.514-.146 1.966a3 3 0 0 1-.624 1.08c-.318.353-.755.606-1.63 1.11l-5.592 3.23c-.874.504-1.312.757-1.776.855a2.997 2.997 0 0 1-1.248 0c-.465-.098-.902-.35-1.776-.856l-5.592-3.228c-.875-.505-1.312-.758-1.63-1.11a3 3 0 0 1-.623-1.081c-.147-.452-.147-.957-.147-1.966V8.77c0-1.01 0-1.514.147-1.966a3 3 0 0 1 .623-1.08c.318-.353.755-.606 1.63-1.11L8.6 1.384Z" />
</svg>
);
export function NxPowerAi({
className = '',
}: {
className?: string;
}): ReactElement {
return (
<div className={cx('relative mx-auto w-full max-w-md p-4', className)}>
<div className="pointer-events-none">
<ul className="grid grid-cols-5 gap-x-2">
{/*Row 1*/}
<li className="invisible aspect-square" />
<li className="relative isolate flex aspect-square h-20 items-center justify-center">
<HexagonShape
aria-hidden="true"
className="absolute inset-0 z-0 text-slate-50/90 drop-shadow-sm dark:text-white/5"
/>
</li>
<li className="relative isolate flex aspect-square h-20 items-center justify-center">
<HexagonShape
aria-hidden="true"
className="absolute inset-0 z-0 text-white/70 drop-shadow-lg dark:text-white/15"
/>
<OpenAiIcon
aria-hidden="true"
className="z-10 size-8 text-black dark:text-white"
/>
</li>
<li className="relative isolate flex aspect-square h-20 items-center justify-center">
<HexagonShape
aria-hidden="true"
className="absolute inset-0 z-0 text-slate-50/90 drop-shadow-sm dark:text-white/5"
/>
</li>
<li className="invisible aspect-square" />
{/*Row 2*/}
<li className="flex aspect-square translate-x-1/2 items-center justify-center">
<HexagonShape
aria-hidden="true"
className="absolute inset-0 z-0 text-slate-50/90 drop-shadow-sm dark:text-white/5"
/>
</li>
<li className="relative isolate flex aspect-square h-20 translate-x-1/2 items-center justify-center">
<HexagonShape
aria-hidden="true"
className="absolute inset-0 z-0 text-white/70 drop-shadow-lg dark:text-white/15"
/>
<ClaudeIcon
aria-hidden="true"
className="z-10 size-8 text-[#D97757]"
/>
</li>
<li className="relative isolate flex aspect-square h-20 translate-x-1/2 items-center justify-center">
<HexagonShape
aria-hidden="true"
className="absolute inset-0 z-0 text-white/70 drop-shadow-lg dark:text-white/15"
/>
<GoogleGeminiIcon
aria-hidden="true"
className="z-10 size-8 text-[#8E75B2]"
/>
</li>
<li className="flex aspect-square translate-x-1/2 items-center justify-center">
<HexagonShape
aria-hidden="true"
className="absolute inset-0 z-0 text-slate-50/90 drop-shadow-sm dark:text-white/5"
/>
</li>
<li className="invisible"></li>
{/*Row 3*/}
<li className="invisible" />
<li className="relative isolate flex aspect-square h-20 items-center justify-center">
<HexagonShape
aria-hidden="true"
className="absolute inset-0 z-0 text-slate-50/90 drop-shadow-sm dark:text-white/5"
/>
</li>
<li className="relative isolate flex aspect-square h-20 items-center justify-center">
<HexagonShape
aria-hidden="true"
className="absolute inset-0 z-0 text-white/70 drop-shadow-lg dark:text-white/15"
/>
<MetaIcon
aria-hidden="true"
className="z-10 size-8 text-[#0467DF]"
/>
</li>
<li className="relative isolate flex aspect-square h-20 items-center justify-center">
<HexagonShape
aria-hidden="true"
className="absolute inset-0 z-0 text-slate-50/90 drop-shadow-sm dark:text-white/5"
/>
</li>
<li className="invisible" />
</ul>
</div>
{/* Steps List */}
<div className="relative mt-12 space-y-2">
{/* Active Step */}
<div className="rounded-xl border border-slate-200 bg-slate-50 p-4 shadow-sm backdrop-blur-sm dark:border-slate-800/40 dark:bg-slate-800/60">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<div className="flex size-5 items-center justify-center rounded-full border border-white/25 bg-gradient-to-br from-slate-200 to-slate-300 dark:border-slate-900 dark:from-slate-700 dark:to-slate-800">
<div className="size-2 rounded-full bg-white dark:bg-slate-900" />
</div>
<div className="flex-1">
<p className="font-mono text-xs leading-tight text-slate-900 dark:text-slate-100">
Generating a fix for test "ui-profile:test:save-preferences"
</p>
</div>
</div>
<div className="flex items-center space-x-2">
<span className="text-xs text-slate-600 dark:text-slate-400">
Working...
</span>
<span className="relative flex size-1.5">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-orange-400 opacity-75" />
<span className="relative inline-flex size-1.5 rounded-full bg-orange-500" />
</span>
</div>
</div>
</div>
{/* Completed Step */}
<div className="rounded-xl border border-slate-200 bg-slate-50 p-4 opacity-50 shadow-sm backdrop-blur-sm dark:border-slate-800/40 dark:bg-slate-800/60">
<div className="flex items-center justify-between gap-4">
<div className="flex grow items-center space-x-3">
<div className="flex size-5 items-center justify-center rounded-full border border-white/25 bg-gradient-to-br from-slate-200 to-slate-300 dark:border-slate-900 dark:from-slate-700 dark:to-slate-800">
<div className="size-2 rounded-full bg-white dark:bg-slate-900" />
</div>
<div className="w-full grow space-y-1">
<div className="h-1 w-8 rounded-sm bg-slate-800/15 dark:bg-slate-200/35" />
<div className="h-1 w-1/2 rounded-sm bg-slate-500/10 dark:bg-slate-400/35" />
</div>
</div>
<div className="flex items-center space-x-2">
<span className="text-xs text-slate-600 dark:text-slate-300">
Done
</span>
<div className="size-1.5 rounded-full bg-green-500 shadow-sm" />
</div>
</div>
</div>
{/* Pending Step */}
<div className="rounded-xl border border-slate-200 bg-slate-50 p-4 opacity-40 shadow-sm backdrop-blur-sm dark:border-slate-800/40 dark:bg-slate-800/60">
<div className="flex items-center space-x-3">
<div className="flex size-5 items-center justify-center rounded-full border border-white/25 bg-gradient-to-br from-slate-200 to-slate-300 dark:border-slate-900 dark:from-slate-700 dark:to-slate-800">
<div className="size-2 rounded-full bg-white dark:bg-slate-900" />
</div>
<div className="flex-1">
<div className="h-1 w-16 rounded-sm bg-slate-400/20" />
</div>
</div>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,70 @@
import { ComponentProps, ReactElement } from 'react';
import { cx } from '@nx/nx-dev/ui-primitives';
import { MovingBorder } from '@nx/nx-dev/ui-animations';
import { motion } from 'framer-motion';
import { PlayIcon } from '@heroicons/react/24/outline';
export function PlayButton({
className,
...props
}: ComponentProps<'div'>): ReactElement {
const parent = {
initial: {
width: 82,
transition: {
when: 'afterChildren',
},
},
hover: {
width: 296,
transition: {
duration: 0.125,
type: 'tween',
ease: 'easeOut',
},
},
};
const child = {
initial: {
opacity: 0,
x: -6,
},
hover: {
x: 0,
opacity: 1,
transition: {
duration: 0.015,
type: 'tween',
ease: 'easeOut',
},
},
};
return (
<div
className={cx(
'group relative overflow-hidden rounded-full bg-transparent p-[1px] shadow-md',
className
)}
{...props}
>
<div className="absolute inset-0">
<MovingBorder duration={5000} rx="5%" ry="5%">
<div className="size-20 bg-[radial-gradient(var(--blue-500)_40%,transparent_60%)] opacity-[0.8] dark:bg-[radial-gradient(var(--pink-500)_40%,transparent_60%)]" />
</MovingBorder>
</div>
<motion.div
initial="initial"
whileHover="hover"
variants={parent}
className="relative isolate flex size-20 cursor-pointer items-center justify-center gap-6 rounded-full border-2 border-slate-100 bg-white/10 p-6 text-white antialiased backdrop-blur-xl"
>
<PlayIcon aria-hidden="true" className="absolute left-6 top-6 size-8" />
<motion.div variants={child} className="absolute left-20 top-4 w-48">
<p className="text-base font-medium">Watch the video</p>
<p className="text-xs">Make your AI work.</p>
</motion.div>
</motion.div>
</div>
);
}

View File

@ -0,0 +1,146 @@
import { ReactElement, useState } from 'react';
import { SectionHeading, VideoModal } from '@nx/nx-dev/ui-common';
import Link from 'next/link';
import { FeatureCard, type FeatureCardProps } from './feature-card';
const features: FeatureCardProps[] = [
{
isAvailable: true,
id: 'deep-project-understanding',
title: 'Deep Project Understanding Through MCP',
subtitle: '"Context is worth 80 IQ points" - Alan Kay',
description: (
<>
<p className="flex-auto">
Nx connects your AI assistant to its comprehensive workspace knowledge
through the Model Context Protocol (MCP), delivering complete insights
into project graphs, dependencies, and code ownership.
</p>
<div className="mt-4">
<Link
href="/blog/nx-mcp-vscode-copilot"
title="How to setup Nx MCP to your LLM"
className="text-sm/6 font-semibold"
>
How to setup Nx MCP to your LLM <span aria-hidden="true"></span>
</Link>
</div>
</>
),
type: 'video',
videoUrl: 'https://youtu.be/RNilYmJJzdk',
imageUrl: '/images/ai/nx-copilot-mcp-yt-thumb.avif',
},
{
isAvailable: true,
id: 'terminal-awareness-in-real-time',
title: 'Terminal Awareness in Real-Time',
subtitle: 'Your AI sees what you see, when you see it.',
description: (
<>
<p className="flex-auto">
Your AI assistant can access the terminal output on-demand through
MCP. When you ask about failing builds or broken tests, it retrieves
the relevant error messages and combines them with full codebase
context.
</p>
<div className="mt-4">
<Link
href="/blog/nx-terminal-integration-ai"
title="How to set AI terminal integration"
className="text-sm/6 font-semibold"
>
How to set terminal AI integration with Nx{' '}
<span aria-hidden="true"></span>
</Link>
</div>
</>
),
type: 'video',
videoUrl: 'https://youtu.be/Cbc9_W5J6DA',
imageUrl: '/images/ai/terminal-llm-comm-thumb.avif',
},
{
isAvailable: true,
id: 'predictable-workspace-aware-code-generation',
title: 'Predictable, Workspace-Aware Code Generation',
subtitle:
'Combine AI intelligence with consistent generators that follow team standards',
description: (
<>
<p className="flex-auto">
Your AI assistant can trigger code generation using predictable Nx
generators, then take it from there to intelligently integrate the
result into your existing workspace architecture.
</p>
<div className="mt-4">
<Link
href="/blog/nx-generators-ai-integration"
title="How to generate code that works with AI"
className="text-sm/6 font-semibold"
>
How to generate code that works with AI{' '}
<span aria-hidden="true"></span>
</Link>
</div>
</>
),
type: 'video',
videoUrl: 'https://youtu.be/PXNjedYhZDs',
imageUrl: '/images/ai/video-code-gen-and-ai-thumb.avif',
},
];
export function WhileCoding(): ReactElement {
const [openVideoUrl, setOpenVideoUrl] = useState<string | null>(null);
return (
<div
id="while-coding"
className="mx-auto max-w-7xl scroll-mt-32 px-6 lg:px-8"
>
<div className="max-w-2xl">
<div className="h-8 w-36 border-t-2 border-blue-500 dark:border-sky-500" />
<SectionHeading as="h2" variant="title" id="while-coding-title">
While Coding
</SectionHeading>
</div>
<div className="mt-16 sm:mt-20 lg:mt-24">
<dl className="grid grid-cols-1 gap-x-8 gap-y-16 sm:grid-cols-2 lg:grid-cols-3">
{features.map((feature) =>
feature.type === 'video' ? (
<FeatureCard
key={feature.id}
isAvailable={feature.isAvailable}
id={feature.id}
title={feature.title}
subtitle={feature.subtitle}
description={feature.description}
type={feature.type}
imageUrl={feature.imageUrl}
videoUrl={feature.videoUrl}
onPlayClick={setOpenVideoUrl}
/>
) : (
<FeatureCard
key={feature.id}
isAvailable={feature.isAvailable}
id={feature.id}
title={feature.title}
subtitle={feature.subtitle}
description={feature.description}
type={feature.type}
imageUrl={feature.imageUrl}
/>
)
)}
</dl>
</div>
<VideoModal
isOpen={openVideoUrl !== null}
onClose={() => setOpenVideoUrl(null)}
videoUrl={openVideoUrl || ''}
/>
</div>
);
}

View File

@ -0,0 +1,144 @@
import { ReactElement, useState } from 'react';
import { SectionHeading, VideoModal } from '@nx/nx-dev/ui-common';
import Link from 'next/link';
import { FeatureCard, type FeatureCardProps } from './feature-card';
const features: FeatureCardProps[] = [
{
isAvailable: true,
id: 'reliable-tests',
title: 'Reliable Tests',
subtitle: 'Turn flaky tests from time-wasters into non-issues.',
description: (
<>
<p className="flex-auto">
Nx automatically identifies and fixes flaky tests using AI that
analyzes failures with workspace context and generates actual code
fixes.
</p>
<div className="mt-4">
<Link
href="/ci/features/flaky-tasks"
title="How to identify and rerun flaky tasks"
className="text-sm/6 font-semibold"
>
How to identify and rerun flaky tasks{' '}
<span aria-hidden="true"></span>
</Link>
</div>
</>
),
type: 'link',
imageUrl: '/images/ai/nx-flaky-tasks-detection-thumb.avif',
},
{
isAvailable: true,
id: 'self-healing-ci',
title: 'Self-Healing CI',
subtitle: 'Stop babysitting PRs. AI fixes your CI failures automatically.',
description: (
<>
<p className="flex-auto">
AI agents detect, analyze, and propose fixes for CI failures using
your workspace context. Stay focused on features while AI handles the
debugging.
</p>
<div className="mt-4">
<Link
href="/blog/nx-self-healing-ci"
title="Stop babysitting PRs with Self-Healing CI"
className="text-sm/6 font-semibold"
>
Stop babysitting PRs with Self-Healing CI{' '}
<span aria-hidden="true"></span>
</Link>
</div>
</>
),
type: 'video',
videoUrl: 'https://youtu.be/JW5Ki3PkRWA',
imageUrl: '/images/ai/self-healing-ci-thumb.avif',
},
{
isAvailable: true,
id: 'autonomous-ci-optimization',
title: 'Autonomous CI Optimization',
subtitle: 'Let AI manage your CI resources for optimal performance.',
description: (
<>
<p className="flex-auto">
AI-driven resource allocation that learns from your CI patterns to
automatically scale agents and optimize build times.
</p>
<div className="mt-4">
<Link
href="/ci/features/dynamic-agents"
title="How to setup dynamic agents"
className="text-sm/6 font-semibold"
>
How to setup Autonomous CI Optimization{' '}
<span aria-hidden="true"></span>
</Link>
</div>
</>
),
type: 'link',
imageUrl: '/images/ai/autonomous-ci-optimization-thumb.avif',
},
];
export function WhileRunningCi(): ReactElement {
const [openVideoUrl, setOpenVideoUrl] = useState<string | null>(null);
return (
<div className="relative bg-slate-50 py-32 dark:bg-slate-800">
<div
id="while-running-ci"
className="mx-auto max-w-7xl scroll-mt-32 px-6 lg:px-8"
>
<div className="max-w-2xl">
<div className="h-8 w-36 border-t-2 border-emerald-500" />
<SectionHeading as="h2" variant="title" id="while-running-ci-title">
While Running CI
</SectionHeading>
</div>
<div className="mt-16 sm:mt-20 lg:mt-24">
<dl className="grid grid-cols-1 gap-x-8 gap-y-16 sm:grid-cols-2 lg:grid-cols-3">
{features.map((feature) =>
feature.type === 'video' ? (
<FeatureCard
key={feature.id}
isAvailable={feature.isAvailable}
id={feature.id}
title={feature.title}
subtitle={feature.subtitle}
description={feature.description}
type={feature.type}
imageUrl={feature.imageUrl}
videoUrl={feature.videoUrl}
onPlayClick={setOpenVideoUrl}
/>
) : (
<FeatureCard
key={feature.id}
isAvailable={feature.isAvailable}
id={feature.id}
title={feature.title}
subtitle={feature.subtitle}
description={feature.description}
type={feature.type}
imageUrl={feature.imageUrl}
/>
)
)}
</dl>
</div>
<VideoModal
isOpen={openVideoUrl !== null}
onClose={() => setOpenVideoUrl(null)}
videoUrl={openVideoUrl || ''}
/>
</div>
</div>
);
}

View File

@ -0,0 +1,113 @@
import { ReactElement, useState } from 'react';
import { SectionHeading, VideoModal } from '@nx/nx-dev/ui-common';
import { FeatureCard, type FeatureCardProps } from './feature-card';
const features: FeatureCardProps[] = [
{
isAvailable: false,
id: 'architectural-queries',
title: 'Architectural Queries',
subtitle: 'Ask questions about your entire system in plain English.',
description: (
<p className="flex-auto">
Query your workspace data naturally: "Extract all failed CI runs from
the last month" or "What patterns are causing our tests to fail?" AI
analyzes your CI pipeline data, cache performance, and build patterns to
identify bottlenecks and optimization opportunities.
</p>
),
type: 'link',
imageUrl: '/images/ai/ci-querying-thumb.avif',
},
{
isAvailable: false,
id: 'cross-repository-intelligence',
title: 'Cross-Repository Intelligence',
subtitle: 'AI that understands your entire organization.',
description: (
<p className="flex-auto">
Nx Polygraph will extend AI context across multiple repositories,
enabling system-wide refactoring and cross-repo analysis.
</p>
),
type: 'link',
imageUrl: '/images/ai/cross-repository-intelligence-thumb.avif',
},
{
isAvailable: false,
id: 'cross-repository-agentic-refactorings',
title: 'Cross-Repository Agentic Refactorings',
subtitle: 'Modernize your entire organization with AI agents.',
description: (
<>
<p className="flex-auto">
Use autonomous agents to perform large-scale migrations and tech debt
cleanup across all repositories. They understand cross-repo
dependencies to execute complex, org-wide changes safely.
</p>
</>
),
type: 'link',
imageUrl: '/images/ai/cross-repository-agentic-refactorings-thumb.avif',
},
];
export function WhileScalingYourOrganization(): ReactElement {
const [openVideoUrl, setOpenVideoUrl] = useState<string | null>(null);
return (
<div className="relative">
<div
id="while-scaling-your-organization"
className="mx-auto max-w-7xl scroll-mt-32 px-6 lg:px-8"
>
<div className="max-w-2xl">
<div className="h-8 w-36 border-t-2 border-fuchsia-500" />
<SectionHeading
as="h2"
variant="title"
id="while-scaling-your-organization-title"
>
While Scaling Your Organization
</SectionHeading>
</div>
<div className="mt-16 sm:mt-20 lg:mt-24">
<dl className="grid grid-cols-1 gap-x-8 gap-y-16 sm:grid-cols-2 lg:grid-cols-3">
{features.map((feature) =>
feature.type === 'video' ? (
<FeatureCard
key={feature.id}
isAvailable={feature.isAvailable}
id={feature.id}
title={feature.title}
subtitle={feature.subtitle}
description={feature.description}
type={feature.type}
imageUrl={feature.imageUrl}
videoUrl={feature.videoUrl}
onPlayClick={setOpenVideoUrl}
/>
) : (
<FeatureCard
key={feature.id}
isAvailable={feature.isAvailable}
id={feature.id}
title={feature.title}
subtitle={feature.subtitle}
description={feature.description}
type={feature.type}
imageUrl={feature.imageUrl}
/>
)
)}
</dl>
</div>
<VideoModal
isOpen={openVideoUrl !== null}
onClose={() => setOpenVideoUrl(null)}
videoUrl={openVideoUrl || ''}
/>
</div>
</div>
);
}

View File

@ -210,6 +210,14 @@ export function Header({ ctaButtons }: HeaderProps): ReactElement {
)} )}
</Popover> </Popover>
<div className="hidden h-6 w-px bg-slate-200 md:block dark:bg-slate-700" /> <div className="hidden h-6 w-px bg-slate-200 md:block dark:bg-slate-700" />
<Link
href="/ai"
title="AI"
className="hidden gap-2 px-3 py-2 font-medium leading-tight hover:text-blue-500 md:inline-flex dark:text-slate-200 dark:hover:text-sky-500"
prefetch={false}
>
AI
</Link>
<Link <Link
href="/remote-cache" href="/remote-cache"
title="Nx Remote Cache" title="Nx Remote Cache"
@ -253,9 +261,11 @@ export function Header({ ctaButtons }: HeaderProps): ReactElement {
{/*SECONDARY NAVIGATION*/} {/*SECONDARY NAVIGATION*/}
<div className="flex-shrink-0 text-sm"> <div className="flex-shrink-0 text-sm">
<nav className="flex items-center justify-center space-x-1"> <nav className="flex items-center justify-center space-x-1">
{buttonsToRender.map((buttonProps, index) => ( <div className="hidden xl:block">
<ButtonLink key={index} {...buttonProps} /> {buttonsToRender.map((buttonProps, index) => (
))} <ButtonLink key={index} {...buttonProps} />
))}
</div>
<a <a
title="Nx is open source, check the code on GitHub" title="Nx is open source, check the code on GitHub"
href="https://github.com/nrwl/nx" href="https://github.com/nrwl/nx"
@ -473,6 +483,22 @@ export function Header({ ctaButtons }: HeaderProps): ReactElement {
</> </>
)} )}
</Disclosure> </Disclosure>
<Link
href="/ai"
title="AI"
className="flex w-full gap-2 py-4 font-medium leading-tight hover:text-blue-500 dark:text-slate-200 dark:hover:text-sky-500"
prefetch={false}
>
AI
</Link>
<Link
href="/remote-cache"
title="Nx Remote Cache"
className="flex w-full gap-2 py-4 font-medium leading-tight hover:text-blue-500 dark:text-slate-200 dark:hover:text-sky-500"
prefetch={false}
>
Remote Cache
</Link>
<Link <Link
href="/nx-cloud" href="/nx-cloud"
title="Nx Cloud" title="Nx Cloud"
@ -489,14 +515,6 @@ export function Header({ ctaButtons }: HeaderProps): ReactElement {
> >
Pricing Pricing
</Link> </Link>
<Link
href="/remote-cache"
title="Nx Remote Cache"
className="flex w-full gap-2 py-4 font-medium leading-tight hover:text-blue-500 dark:text-slate-200 dark:hover:text-sky-500"
prefetch={false}
>
Remote Cache
</Link>
<Disclosure as="div"> <Disclosure as="div">
{({ open }) => ( {({ open }) => (
<> <>

View File

@ -1,8 +1,11 @@
// AI // AI
export * from './lib/ai/claude';
export * from './lib/ai/cursor'; export * from './lib/ai/cursor';
export * from './lib/ai/github-copilot'; export * from './lib/ai/github-copilot';
export * from './lib/ai/google-gemini';
export * from './lib/ai/intellij-ai'; export * from './lib/ai/intellij-ai';
export * from './lib/ai/model-context-protocol'; export * from './lib/ai/model-context-protocol';
export * from './lib/ai/open-ai';
// CI PROVIDERS // CI PROVIDERS
export * from './lib/ci-providers/azure-devops'; export * from './lib/ci-providers/azure-devops';
@ -112,6 +115,7 @@ export * from './lib/products';
// SOCIALS // SOCIALS
export * from './lib/socials/discord-icon'; export * from './lib/socials/discord-icon';
export * from './lib/socials/meta';
export * from './lib/socials/x-icon'; export * from './lib/socials/x-icon';
export * from './lib/socials/youtube'; export * from './lib/socials/youtube';

View File

@ -0,0 +1,17 @@
import { FC, SVGProps } from 'react';
/**
* Use `#D97757` for a colored version.
*/
export const ClaudeIcon: FC<SVGProps<SVGSVGElement>> = (props) => (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
{...props}
>
<title>Claude</title>
<path d="m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z" />
</svg>
);

View File

@ -0,0 +1,17 @@
import { FC, SVGProps } from 'react';
/**
* Use `#8E75B2` for a colored version.
*/
export const GoogleGeminiIcon: FC<SVGProps<SVGSVGElement>> = (props) => (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
{...props}
>
<title>Google Gemini</title>
<path d="M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81" />
</svg>
);

View File

@ -0,0 +1,17 @@
import { FC, SVGProps } from 'react';
/**
* Use `#412991` for a colored version.
*/
export const OpenAiIcon: FC<SVGProps<SVGSVGElement>> = (props) => (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
{...props}
>
<title>OpenAI</title>
<path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
</svg>
);

View File

@ -0,0 +1,17 @@
import { FC, SVGProps } from 'react';
/**
* Use `#0467DF` for a colored version.
*/
export const MetaIcon: FC<SVGProps<SVGSVGElement>> = (props) => (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
{...props}
>
<title>Meta</title>
<path d="M6.915 4.03c-1.968 0-3.683 1.28-4.871 3.113C.704 9.208 0 11.883 0 14.449c0 .706.07 1.369.21 1.973a6.624 6.624 0 0 0 .265.86 5.297 5.297 0 0 0 .371.761c.696 1.159 1.818 1.927 3.593 1.927 1.497 0 2.633-.671 3.965-2.444.76-1.012 1.144-1.626 2.663-4.32l.756-1.339.186-.325c.061.1.121.196.183.3l2.152 3.595c.724 1.21 1.665 2.556 2.47 3.314 1.046.987 1.992 1.22 3.06 1.22 1.075 0 1.876-.355 2.455-.843a3.743 3.743 0 0 0 .81-.973c.542-.939.861-2.127.861-3.745 0-2.72-.681-5.357-2.084-7.45-1.282-1.912-2.957-2.93-4.716-2.93-1.047 0-2.088.467-3.053 1.308-.652.57-1.257 1.29-1.82 2.05-.69-.875-1.335-1.547-1.958-2.056-1.182-.966-2.315-1.303-3.454-1.303zm10.16 2.053c1.147 0 2.188.758 2.992 1.999 1.132 1.748 1.647 4.195 1.647 6.4 0 1.548-.368 2.9-1.839 2.9-.58 0-1.027-.23-1.664-1.004-.496-.601-1.343-1.878-2.832-4.358l-.617-1.028a44.908 44.908 0 0 0-1.255-1.98c.07-.109.141-.224.211-.327 1.12-1.667 2.118-2.602 3.358-2.602zm-10.201.553c1.265 0 2.058.791 2.675 1.446.307.327.737.871 1.234 1.579l-1.02 1.566c-.757 1.163-1.882 3.017-2.837 4.338-1.191 1.649-1.81 1.817-2.486 1.817-.524 0-1.038-.237-1.383-.794-.263-.426-.464-1.13-.464-2.046 0-2.221.63-4.535 1.66-6.088.454-.687.964-1.226 1.533-1.533a2.264 2.264 0 0 1 1.088-.285z" />
</svg>
);