mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
62facacee1
Every integration quickstart in docs/ and showcase/shell-docs/ now opens with a "Create a free account" step that points the reader at the Enterprise Intelligence Platform before the framework path. Existing top-of-page <OpsPlatformCTA> blocks on the six integrations that already had one are left in place. - New <SignupLink surface="docs_<int>_quickstart_step1">…</SignupLink> MDX component in both apps. It mirrors OpsPlatformCTA's URL+UTM contract (https://dashboard.operations.copilotkit.ai/ with the canonical docs UTMs, picked up from NEXT_PUBLIC_INTELLIGENCE_SIGNUP_URL when set) and fires the same PostHog event the other CTAs use: posthog.capture("try_for_free_clicked", { location: surface }). - Registered as an MDX global in: docs/app/integrations/[[...slug]]/page.tsx docs/app/(home)/[[...slug]]/page.tsx showcase/shell-docs/src/lib/mdx-registry.tsx - All 28 integration quickstart .mdx files now lead with a Step that uses this component as an inline link inside a single sentence of prose — no CTA card inside <Steps>. The <TailoredContent> "Choose your starting point" / "How do you want to get started?" selector is now wrapped in its own <Step> so it advances the counter, and the inner CLI/manual paths render as steps 3, 4, 5, … instead of 2, 3, 4, …. Applies to all 20 quickstarts that use the picker. - Indigo→purple gradient text on the Step 1 heading on both surfaces (`.fd-steps > .fd-step:first-child h3` on docs/, `.docs-steps > div:first-child h3` on shell-docs). Direct-child combinator scopes it to the outer first Step so inner first-children inside TailoredContentOption don't pick it up. Bump weight to 700 and font-size to 1.375rem on docs/ to compensate for the background-clip:text rendering path (grayscale AA, no solid fill) which makes glyphs look lighter/smaller than the adjacent solid 600/20px headings. - Tone down the selected TailoredContent option card on both surfaces to a near-grayscale wash (from-slate-50 → to-indigo-50/30) and shorten the card itself (smaller padding, smaller icon, smaller title; extra left padding for breathing room) so the picker takes less vertical space and doesn't compete with the Step 1 gradient heading. Indigo ring still does the "selected" signal. - Bump the tablist's bottom margin in shell-docs (my-2 → mt-2 mb-6) so the gap between the picker and the first inner Step matches the 1.5rem gap that every other consecutive-Step transition uses. - Black SignupLink color in Step 1 on shell-docs so the link doesn't clash with the gradient heading above it. - Shell-docs: reset margin-top on the first heading inside any Step so the badge and heading align, and nudge the badge top from -0.125rem to 0.1875rem so its vertical center matches the heading line center. Moved the badge's appearance (background/border/color/font-weight) out of inline style and into globals.css so :first-child overrides can win without fighting inline-style specificity.
256 lines
8.6 KiB
TypeScript
256 lines
8.6 KiB
TypeScript
import { source } from "@/app/source";
|
|
import type { Metadata } from "next";
|
|
import {
|
|
DocsPage,
|
|
DocsBody,
|
|
DocsDescription,
|
|
DocsTitle,
|
|
} from "fumadocs-ui/page";
|
|
import { notFound } from "next/navigation";
|
|
import defaultMdxComponents from "fumadocs-ui/mdx";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { CloudIcon } from "lucide-react";
|
|
import { Tabs, Tab } from "@/components/react/tabs";
|
|
import { Steps, Step } from "fumadocs-ui/components/steps";
|
|
import { TypeTable } from "fumadocs-ui/components/type-table";
|
|
import { Pre, CodeBlock } from "fumadocs-ui/components/codeblock";
|
|
import { Callout } from "fumadocs-ui/components/callout";
|
|
import { Frame } from "@/components/react/frame";
|
|
import { Mermaid } from "@theguild/remark-mermaid/mermaid";
|
|
import { Cards, Card } from "fumadocs-ui/components/card";
|
|
import { PropertyReference } from "@/components/react/property-reference";
|
|
import { InsecurePasswordProtected } from "@/components/react/insecure-password-protected";
|
|
import { LinkToCopilotCloud } from "@/components/react/link-to-copilot-cloud";
|
|
import { OpsPlatformCTA } from "@/components/react/ops-platform-cta";
|
|
import { SignupLink } from "@/components/react/signup-link";
|
|
import { Accordions, Accordion } from "fumadocs-ui/components/accordion";
|
|
import { NavigationLink } from "@/components/react/subdocs-menu";
|
|
import { getSnippetTOCForPage } from "@/lib/snippet-toc";
|
|
import { CustomPager } from "@/components/ui/custom-pager";
|
|
import { PageBreadcrumb } from "fumadocs-ui/layouts/docs/page";
|
|
import Link from "next/link";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
/**
|
|
* TODO: This should be dynamic, but it's not working.
|
|
*/
|
|
const cloudOnlyFeatures = ["Authenticated Actions", "Guardrails"];
|
|
const premiumFeatureTitles = [
|
|
"Observability Hooks",
|
|
"Error Observability Connectors",
|
|
]; // heuristic for pages that import premium snippets
|
|
|
|
const mdxComponents = {
|
|
...defaultMdxComponents,
|
|
InsecurePasswordProtected: InsecurePasswordProtected,
|
|
LinkToCopilotCloud: LinkToCopilotCloud,
|
|
OpsPlatformCTA: OpsPlatformCTA,
|
|
SignupLink: SignupLink,
|
|
Accordions: Accordions,
|
|
Accordion: Accordion,
|
|
Tabs: Tabs,
|
|
Tab: Tab,
|
|
Steps: Steps,
|
|
Step: Step,
|
|
TypeTable: TypeTable,
|
|
Callout: Callout,
|
|
Frame: Frame,
|
|
Mermaid: Mermaid,
|
|
Cards: Cards,
|
|
Card: Card,
|
|
PropertyReference: PropertyReference,
|
|
a: ({ href, children, className, ...props }: any) => {
|
|
// Check if link is external (for suppressHydrationWarning)
|
|
const isExternal =
|
|
href &&
|
|
typeof href === "string" &&
|
|
(href.startsWith("http://") || href.startsWith("https://"));
|
|
|
|
// Don't wrap anchor links (hash links) in NavigationLink to avoid nested <a> tags
|
|
if (href && typeof href === "string" && href.startsWith("#")) {
|
|
return (
|
|
<a
|
|
href={href}
|
|
className={className}
|
|
{...props}
|
|
suppressHydrationWarning={isExternal}
|
|
>
|
|
{children}
|
|
</a>
|
|
);
|
|
}
|
|
// Don't wrap links that have data-card attribute or peer className (fumadocs heading anchors)
|
|
if (
|
|
props &&
|
|
(props["data-card"] !== undefined || props["data-heading"] !== undefined)
|
|
) {
|
|
return (
|
|
<a
|
|
href={href}
|
|
className={className}
|
|
{...props}
|
|
suppressHydrationWarning={isExternal}
|
|
>
|
|
{children}
|
|
</a>
|
|
);
|
|
}
|
|
// Don't wrap links with 'peer' className (fumadocs uses this for heading anchor links)
|
|
if (
|
|
className &&
|
|
typeof className === "string" &&
|
|
className.includes("peer")
|
|
) {
|
|
return (
|
|
<a
|
|
href={href}
|
|
className={className}
|
|
{...props}
|
|
suppressHydrationWarning={isExternal}
|
|
>
|
|
{children}
|
|
</a>
|
|
);
|
|
}
|
|
return (
|
|
<NavigationLink href={href as string} className={className} {...props}>
|
|
{children}
|
|
</NavigationLink>
|
|
);
|
|
},
|
|
// HTML `ref` attribute conflicts with `forwardRef`
|
|
pre: ({ ref: _ref, ...props }: any) => (
|
|
<CodeBlock {...props}>
|
|
<Pre>{props.children}</Pre>
|
|
</CodeBlock>
|
|
),
|
|
};
|
|
|
|
export default async function Page({
|
|
params,
|
|
}: {
|
|
params: Promise<{ slug?: string[] }>;
|
|
}) {
|
|
const resolvedParams = await params;
|
|
const page = source.getPage(["integrations", ...(resolvedParams.slug || [])]);
|
|
if (!page) notFound();
|
|
const MDX = page.data.body;
|
|
const cloudOnly = cloudOnlyFeatures.includes(page.data.title);
|
|
|
|
// Consider a page "Premium" if its slug path contains a "premium" segment OR title matches known premium features OR frontmatter premium flag
|
|
const bySlugPremium = Array.isArray(page.slugs)
|
|
? page.slugs.includes("premium")
|
|
: false;
|
|
const byTitlePremium = premiumFeatureTitles.includes(page.data.title || "");
|
|
const byFrontmatterPremium = Boolean((page as any).data?.premium);
|
|
const isPremium = bySlugPremium || byTitlePremium || byFrontmatterPremium;
|
|
// Compute premium overview href based on current section (first slug segment)
|
|
const baseSegment =
|
|
Array.isArray(page.slugs) && page.slugs.length ? `/${page.slugs[0]}` : "/";
|
|
const premiumOverviewHref =
|
|
baseSegment === "/"
|
|
? "/premium/overview"
|
|
: `${baseSegment}/premium/overview`;
|
|
|
|
// Check if the page should hide the header or TOC
|
|
const hideHeader = (page.data as any).hideHeader || false;
|
|
const hideTOC = (page.data as any).hideTOC || false;
|
|
|
|
// Get TOC from imported snippets and merge with page TOC (only if TOC is not hidden)
|
|
// Use try-catch to handle build-time issues gracefully
|
|
let snippetTOC: any[] = [];
|
|
if (!hideTOC) {
|
|
try {
|
|
snippetTOC = await getSnippetTOCForPage([
|
|
"integrations",
|
|
...(resolvedParams.slug || []),
|
|
]);
|
|
} catch (error) {
|
|
console.warn("Failed to load snippet TOC:", error);
|
|
snippetTOC = [];
|
|
}
|
|
}
|
|
const combinedTOC = hideTOC ? [] : [...(page.data.toc || []), ...snippetTOC];
|
|
|
|
return (
|
|
<DocsPage
|
|
toc={combinedTOC}
|
|
full={page.data.full}
|
|
footer={{ component: <CustomPager tree={source.pageTree} page={page} /> }}
|
|
tableOfContent={{
|
|
style: "clerk",
|
|
}}
|
|
breadcrumb={{ enabled: false }}
|
|
>
|
|
<div className="px-8 py-6 xl:py-12 xl:px-16 max-sm:px-4 max-sm:py-6">
|
|
<PageBreadcrumb className="mb-4" />
|
|
<div>
|
|
{!hideHeader && (
|
|
<div className="gap-5 flex flex-col">
|
|
<div className="flex gap-3 items-center">
|
|
<DocsTitle className="flex items-center text-[32px] font-medium text-[#010507] dark:text-white md:text-[40px] leading-12">
|
|
{page.data.title}
|
|
{cloudOnly && (
|
|
<Badge
|
|
variant="secondary"
|
|
className="ml-3 mt-1 inline-flex items-center gap-1.5 py-1.5 px-3 bg-indigo-600/90 text-white hover:bg-indigo-600 border-0 rounded-md transition-colors"
|
|
>
|
|
<CloudIcon className="w-3 h-3" />
|
|
<span className="text-xs">Cloud Only</span>
|
|
</Badge>
|
|
)}
|
|
{isPremium && (
|
|
<a href={premiumOverviewHref} className="ml-3">
|
|
<Badge
|
|
variant="secondary"
|
|
className="inline-flex items-center gap-2 py-2 px-3.5 bg-indigo-100 text-indigo-700 dark:bg-indigo-900/40 dark:text-indigo-200 hover:bg-indigo-200 border-0 rounded-md transition-colors"
|
|
>
|
|
<img
|
|
src="https://cdn.copilotkit.ai/docs/copilotkit/icons/copilotkit-color.svg"
|
|
alt="CopilotKit"
|
|
className="w-4 h-4"
|
|
/>
|
|
<span className="text-sm font-semibold tracking-tight">
|
|
Premium
|
|
</span>
|
|
</Badge>
|
|
</a>
|
|
)}
|
|
</DocsTitle>
|
|
</div>
|
|
<DocsDescription>{page.data.description}</DocsDescription>
|
|
</div>
|
|
)}
|
|
<DocsBody>
|
|
<MDX components={mdxComponents} />
|
|
</DocsBody>
|
|
</div>
|
|
</div>
|
|
</DocsPage>
|
|
);
|
|
}
|
|
|
|
export async function generateStaticParams() {
|
|
return source
|
|
.generateParams()
|
|
.filter((params) => params.slug && params.slug[0] === "integrations")
|
|
.map((params) => ({
|
|
slug: params.slug?.slice(1) || [],
|
|
}));
|
|
}
|
|
|
|
export async function generateMetadata({
|
|
params,
|
|
}: {
|
|
params: Promise<{ slug?: string[] }>;
|
|
}) {
|
|
const resolvedParams = await params;
|
|
const page = source.getPage(["integrations", ...(resolvedParams.slug || [])]);
|
|
if (!page) notFound();
|
|
|
|
return {
|
|
title: page.data.title,
|
|
description: page.data.description,
|
|
} satisfies Metadata;
|
|
}
|