chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:58:18 +08:00
commit 6d5d58c1a9
18293 changed files with 3502153 additions and 0 deletions
@@ -0,0 +1,46 @@
"use client";
import { Tabs, Tab } from "../docs-tabs";
interface IframeSwitcherProps {
id?: string;
exampleUrl: string;
codeUrl: string;
exampleLabel?: string;
codeLabel?: string;
height?: string;
}
export function IframeSwitcher({
id,
exampleUrl,
codeUrl,
exampleLabel = "Demo",
codeLabel = "Code",
height = "600px",
}: IframeSwitcherProps) {
return (
<div id={id}>
<Tabs items={[exampleLabel, codeLabel]}>
<Tab value={exampleLabel}>
<iframe
src={exampleUrl}
className="shell-docs-radius-surface w-full border-0 bg-[var(--bg-surface)]"
style={{ height }}
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
loading="lazy"
/>
</Tab>
<Tab value={codeLabel}>
<iframe
src={codeUrl}
className="shell-docs-radius-surface w-full border-0 bg-[var(--bg-surface)]"
style={{ height }}
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
loading="lazy"
/>
</Tab>
</Tabs>
</div>
);
}
@@ -0,0 +1 @@
export { IframeSwitcher } from "./iframe-switcher";
@@ -0,0 +1,62 @@
import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it } from "vitest";
import { FrameworkOverview } from "../framework-overview";
import type { FrameworkOverviewData } from "@/data/frameworks/types";
const overviewData: FrameworkOverviewData = {
slug: "langgraph-python",
frameworkName: "LangChain",
iconKey: "langgraph",
header: "Bring your LangChain agents to your users",
subheader: "Build rich, interactive, agent-powered applications.",
guideLink: "/langgraph-python/quickstart",
initCommand: "npx copilotkit@latest init",
featuresLink: "/langgraph-python",
supportedFeatures: [],
liveDemos: [],
};
describe("FrameworkOverview", () => {
it("renders the primary quickstart CTA and optional agent CLI setup action", () => {
const markup = renderToStaticMarkup(
<FrameworkOverview
data={overviewData}
currentFramework="langgraph-python"
/>,
);
expect(markup).toContain("Quickstart");
expect(markup).toContain("Start using agents");
expect(markup).toContain('aria-controls="hero-cli-commands"');
expect(markup).toContain("border-[var(--accent)]");
expect(markup).toContain("bg-[var(--accent)]");
expect(markup).toContain("shell-docs-primary-cta");
expect(markup).toContain("text-[var(--primary-foreground)]");
});
it("renders the framework identity icon in accent purple", () => {
const markup = renderToStaticMarkup(
<FrameworkOverview
data={overviewData}
currentFramework="langgraph-python"
/>,
);
expect(markup).toContain(
"shell-docs-radius-icon flex h-10 w-10 items-center justify-center border border-[var(--accent)] bg-[var(--accent-dim)] text-[var(--accent)]",
);
});
it("does not add top padding before the framework hero", () => {
const markup = renderToStaticMarkup(
<FrameworkOverview
data={overviewData}
currentFramework="langgraph-python"
/>,
);
expect(markup).toContain('class="pb-8 sm:pb-12"');
expect(markup).not.toContain("pt-2 sm:pt-4");
});
});
@@ -0,0 +1,463 @@
"use client";
import { ArrowRight, Copy, Check, ExternalLink } from "lucide-react";
import Link from "next/link";
import Image from "next/image";
import { useState } from "react";
import type { ReactNode } from "react";
import { customIcons } from "@/components/icons";
import type { IconKey } from "@/components/icons";
import {
HeroStartActions,
QuickstartLinkButton,
} from "@/components/hero-start-commands";
import { OpsPlatformCTA } from "@/components/react/ops-platform-cta";
import type {
FrameworkOverviewData,
OpsPlatformCTAData,
} from "@/data/frameworks/types";
export interface FrameworkOverviewProps {
data: FrameworkOverviewData;
/**
* The framework slug from the current URL (e.g. "langgraph-typescript").
* Used to rewrite the data record's links so they stay within the user's
* selected variant — without this, langgraph-typescript users clicking
* "Quickstart" land on langgraph-python's quickstart via SLUG_RENAMES,
* because the data record's `guideLink` embeds the primary variant's slug.
*/
currentFramework: string;
/**
* Optional slot rendered between the supported-features section and the
* architecture section. When supplied, this takes precedence over `data.cta`
* (which is the structured fallback). Routes that pre-render
* `after-features.mdx` should pass the compiled MDX here.
*/
afterFeatures?: ReactNode;
/**
* Optional override for the framework icon. Takes precedence over the
* `iconKey` lookup in `data.iconKey`. Used by the MDX adapter
* (`MdxFrameworkOverview`) so authored `index.mdx` files can pass a
* concrete `<XIcon />` JSX node instead of having to use a registered
* iconKey. When supplied, `data.iconKey` is ignored.
*/
iconOverride?: ReactNode;
}
/**
* Swap the framework slug embedded in a URL for the user's currently selected
* variant. Applied to both in-app internal paths and feature-viewer external
* URLs (which encode the framework slug as a path segment).
*/
function rewriteHref(href: string, fromSlug: string, toSlug: string): string {
if (!fromSlug || fromSlug === toSlug) return href;
if (href === `/${fromSlug}`) return `/${toSlug}`;
if (href.startsWith(`/${fromSlug}/`)) {
return `/${toSlug}${href.slice(fromSlug.length + 1)}`;
}
const featureViewerNeedle = `feature-viewer.copilotkit.ai/${fromSlug}/`;
if (href.includes(featureViewerNeedle)) {
return href.replace(
featureViewerNeedle,
`feature-viewer.copilotkit.ai/${toSlug}/`,
);
}
return href;
}
/**
* Map Track A's `OpsPlatformCTAData.variant` ("card" | "banner") onto the
* variants supported by shell-docs's `OpsPlatformCTA` ("tile" | "inline" |
* "card" | "info"). "banner" => "inline" preserves the full-width prominent
* CTA intent without introducing a new variant.
*/
function ctaVariantFor(data: OpsPlatformCTAData): "card" | "inline" {
return data.variant === "banner" ? "inline" : "card";
}
/**
* Section eyebrow — small sans-serif label with a hairline rule. Dropped
* the prior monospace + wide-tracking treatment because it read as
* editorial pastiche on a developer-docs surface.
*/
function SectionEyebrow({ label }: { label: string }) {
return (
<div className="flex items-center gap-3 mb-6">
<span className="text-sm font-medium text-[var(--text-secondary)] whitespace-nowrap">
{label}
</span>
<div className="flex-1 h-px bg-[var(--border)]" />
</div>
);
}
// Docs framework slug -> the `copilotkit` CLI's own `--framework` value. The
// CLI uses different identifiers than our docs slugs, so the create command on
// a framework page only pre-selects a framework when there's a verified 1:1
// template match (values confirmed against the CLI's AGENT_FRAMEWORKS enum).
// Slugs intentionally omitted fall back to a bare `npx copilotkit create`:
// - crewai-crews: the CLI ships "CrewAI Flows" (`flows`), not Crews — different
// - langgraph-fastapi, claude-sdk-*, langroid, ms-agent-harness-dotnet,
// spring-ai, agent-spec, deepagents: no matching CLI template
const DOCS_SLUG_TO_CLI_FRAMEWORK: Record<string, string> = {
"langgraph-python": "langgraph-py",
"langgraph-typescript": "langgraph-js",
"google-adk": "adk",
strands: "aws-strands-py",
"ms-agent-dotnet": "microsoft-agent-framework-dotnet",
"ms-agent-python": "microsoft-agent-framework-py",
mastra: "mastra",
"pydantic-ai": "pydantic-ai",
llamaindex: "llamaindex",
agno: "agno",
ag2: "ag2",
};
export function FrameworkOverview({
data,
currentFramework,
afterFeatures,
iconOverride,
}: FrameworkOverviewProps) {
const {
frameworkName,
iconKey,
header,
subheader,
guideLink: rawGuideLink,
initCommand,
supportedFeatures = [],
architectureImage,
architectureVideo,
liveDemos = [],
cta,
} = data;
// Derive the primary variant's slug from the data record's own links —
// typically the path segment after the leading `/` of `guideLink`
// (e.g. "/langgraph/quickstart" → "langgraph"). This is the slug we
// rewrite *away from* so that variant users land on their own variant's
// sub-pages.
const fromSlug = rawGuideLink.split("/")[1] ?? "";
const link = (href: string) => rewriteHref(href, fromSlug, currentFramework);
// Frameworks whose init is the generic top-level command get the unified
// two-command recommendation (matching the home hero). Frameworks with
// bespoke setup (e.g. a2a's `git clone`, ms-agent-dotnet) keep their own
// single command chip — those commands aren't interchangeable with the CLI.
const isGenericInit = initCommand.trim() === "npx copilotkit@latest init";
const createFramework = DOCS_SLUG_TO_CLI_FRAMEWORK[currentFramework];
const [activeDemo, setActiveDemo] = useState<string>(
liveDemos[0]?.type || "saas",
);
const [copied, setCopied] = useState(false);
// Look up the icon by key. If the key isn't registered (forward-compat with
// string IconKey from Track A), fall back to rendering nothing rather than
// crashing — the framework name still appears next to it.
const IconComponent = customIcons[iconKey as IconKey];
const hasIcon = Boolean(iconOverride || IconComponent);
const handleCopyCommand = async () => {
try {
await navigator.clipboard.writeText(initCommand);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
// clipboard.writeText rejects in non-secure contexts (http://),
// when the document isn't focused, or when the user has denied
// permission. Don't flip the "copied" indicator on failure — the
// user would see a checkmark and paste an empty/stale buffer.
console.error(
"[framework-overview] clipboard write failed; copy button no-op",
err,
);
}
};
// If no explicit afterFeatures slot is supplied, render the structured cta
// (if any) so data-driven intros still get a CTA without needing MDX.
const resolvedAfterFeatures: ReactNode =
afterFeatures ??
(cta ? (
<OpsPlatformCTA
variant={ctaVariantFor(cta)}
title={cta.title}
body={cta.body}
ctaLabel={cta.ctaLabel}
surface={cta.surface}
/>
) : null);
const activeDemoData = liveDemos.find((demo) => demo.type === activeDemo);
return (
<div className="relative pb-24">
<div className="relative z-10">
{/* =========================================================
HERO
========================================================= */}
<header className="pb-8 sm:pb-12">
{/* Framework identity: icon + name in a horizontal lockup. */}
<div className="flex items-center gap-3 mb-5">
{hasIcon && (
<div className="shell-docs-radius-icon flex h-10 w-10 items-center justify-center border border-[var(--accent)] bg-[var(--accent-dim)] text-[var(--accent)]">
{iconOverride ??
(IconComponent ? (
<IconComponent className="h-6 w-6" />
) : null)}
</div>
)}
<span className="text-base font-semibold tracking-tight text-[var(--text)]">
{frameworkName}
</span>
</div>
{/* Headline + supporting copy — tightened from the prior
display-scale type. Still left-aligned with balanced wrap. */}
<h1 className="text-[1.75rem] sm:text-[2.25rem] md:text-[2.5rem] font-semibold leading-[1.1] tracking-[-0.02em] text-[var(--text)] text-balance max-w-[24ch]">
{header}
</h1>
<p className="mt-4 max-w-[58ch] text-base sm:text-lg text-[var(--text-muted)] leading-[1.55] text-pretty">
{subheader}
</p>
{/* Action cluster — the same <HeroStartActions> block as the home
hero, with Quickstart primary and the agent CLI setup menu
secondary. The quickstart slot is a direct link here because a
framework is already selected. Frameworks with bespoke setup
(e.g. a2a's `git clone`, ms-agent-dotnet) keep their own
copy-command chip because those commands aren't interchangeable
with the CLI. */}
<div className="mt-7">
{isGenericInit ? (
<HeroStartActions
createFramework={createFramework}
quickstart={<QuickstartLinkButton href={link(rawGuideLink)} />}
/>
) : (
<div className="flex flex-col gap-3 sm:flex-row sm:items-center">
<QuickstartLinkButton href={link(rawGuideLink)} />
<button
type="button"
onClick={handleCopyCommand}
className="shell-docs-radius-control group inline-flex h-11 w-full cursor-pointer items-center justify-between gap-3 border border-[var(--border)] bg-[var(--bg-surface)] px-4 text-[var(--text)] shadow-[var(--shadow-control)] transition-colors hover:bg-[var(--bg-elevated)] sm:w-auto sm:justify-start"
aria-label="Copy install command"
>
<span className="flex items-center gap-2 text-[13.5px]">
<span className="text-[var(--accent)] opacity-70 font-mono">
$
</span>
<span className="font-mono text-[13px] text-[var(--text-secondary)] group-hover:text-[var(--text)]">
{initCommand}
</span>
</span>
<span className="text-[var(--text-muted)] group-hover:text-[var(--text)]">
{copied ? (
<Check className="h-4 w-4 text-[var(--accent)]" />
) : (
<Copy className="h-4 w-4" />
)}
</span>
</button>
</div>
)}
</div>
</header>
{/* =========================================================
SUPPORTED FEATURES — numbered milestone list
========================================================= */}
{supportedFeatures.length > 0 && (
<section className="mb-20 sm:mb-28">
<SectionEyebrow label="What you can build" />
<div className="mb-12 max-w-[58ch]">
<h2 className="text-[2rem] sm:text-[2.5rem] font-semibold tracking-[-0.02em] leading-[1.1] text-[var(--text)]">
Build with {frameworkName}
</h2>
<p className="mt-3 text-[15px] sm:text-base text-[var(--text-muted)] leading-relaxed">
The user-facing primitives every {frameworkName} integration
ships with pick the one that fits your product and drop the
code in.
</p>
</div>
<div className="flex flex-col gap-16 sm:gap-24">
{supportedFeatures.map((feature) => {
const hasMedia = Boolean(feature.videoUrl);
return (
<article
key={feature.title}
className="grid lg:grid-cols-12 gap-8 lg:gap-12 items-start"
>
{/* Left column: title + description + links */}
<div className="lg:col-span-5">
<h3 className="text-[1.5rem] sm:text-[1.75rem] font-semibold tracking-[-0.015em] leading-[1.15] text-[var(--text)]">
{feature.title}
</h3>
<p className="mt-3 text-[15px] text-[var(--text-muted)] leading-[1.6]">
{feature.description}
</p>
<div className="mt-6 flex flex-wrap items-center gap-x-5 gap-y-2">
<Link
href={link(feature.documentationLink)}
className="inline-flex items-center gap-1.5 text-[14px] font-medium text-[var(--accent)] hover:text-[var(--accent)] hover:brightness-110 no-underline group"
>
Read the docs
<ArrowRight className="h-3.5 w-3.5 transition-transform group-hover:translate-x-0.5" />
</Link>
{feature.demoLink && (
<Link
href={link(feature.demoLink)}
className="inline-flex items-center gap-1.5 text-[14px] text-[var(--text-muted)] hover:text-[var(--text)] no-underline transition-colors"
>
<ExternalLink className="h-3.5 w-3.5" />
Live demo
</Link>
)}
</div>
</div>
{/* Right column: video. If no media, the left column
spans wider and we leave the right empty (graceful
fallback for sparse data records). */}
{hasMedia && (
<div className="lg:col-span-7">
<div className="shell-docs-radius-surface relative overflow-hidden border border-[var(--border)] bg-[var(--bg-surface)] shadow-[var(--shadow-panel)]">
<video
src={feature.videoUrl}
className="w-full block"
autoPlay
muted
loop
playsInline
/>
<div
aria-hidden
className="shell-docs-radius-surface pointer-events-none absolute inset-0 ring-1 ring-inset ring-white/5"
/>
</div>
</div>
)}
</article>
);
})}
</div>
</section>
)}
{/* =========================================================
AFTER FEATURES (CTA or MDX escape hatch)
========================================================= */}
{resolvedAfterFeatures && (
<section className="mb-20 sm:mb-28">{resolvedAfterFeatures}</section>
)}
{/* =========================================================
ARCHITECTURE
========================================================= */}
{(architectureImage || architectureVideo) && (
<section className="mb-20 sm:mb-28">
<SectionEyebrow label="How it fits together" />
<div className="mb-10 max-w-[58ch]">
<h2 className="text-[2rem] sm:text-[2.5rem] font-semibold tracking-[-0.02em] leading-[1.1] text-[var(--text)]">
Architecture
</h2>
<p className="mt-3 text-[15px] sm:text-base text-[var(--text-muted)] leading-relaxed">
The shape of a CopilotKit + {frameworkName} application from
your UI down to the agent runtime.
</p>
</div>
<div className="shell-docs-radius-surface overflow-hidden border border-[var(--border)] bg-[var(--bg-surface)] shadow-[var(--shadow-panel)]">
{architectureImage && (
<Image
src={architectureImage}
alt={`CopilotKit ${frameworkName} architecture diagram`}
height={800}
width={1600}
className="w-full h-auto block"
/>
)}
{architectureVideo && (
<video
src={architectureVideo}
className="w-full block"
autoPlay
muted
loop
playsInline
/>
)}
</div>
</section>
)}
{/* =========================================================
LIVE DEMOS
========================================================= */}
{liveDemos.length > 0 && (
<section className="mb-20 sm:mb-28">
<SectionEyebrow label="Live example" />
<div className="mb-8 max-w-[58ch]">
<h2 className="text-[2rem] sm:text-[2.5rem] font-semibold tracking-[-0.02em] leading-[1.1] text-[var(--text)]">
Run {frameworkName} in your browser
</h2>
<p className="mt-3 text-[15px] sm:text-base text-[var(--text-muted)] leading-relaxed">
Two patterns we see most often drive a SaaS workflow, or
collaborate on a canvas with your agent.
</p>
</div>
{/* Segmented control — flat, single-row, with a moving accent
underline. Mirrors the dojo's "view toggle" treatment but
in a flatter style that suits a landing page. */}
{liveDemos.length > 1 && (
<div className="shell-docs-radius-control mb-6 inline-flex items-center gap-1 border border-[var(--border)] bg-[var(--bg-surface)] p-1 shadow-[var(--shadow-control)]">
{liveDemos.map((demo) => {
const active = activeDemo === demo.type;
return (
<button
key={demo.type}
type="button"
onClick={() => setActiveDemo(demo.type)}
className={`shell-docs-radius-control h-8 px-4 text-[13px] font-medium transition-colors ${
active
? "bg-[var(--bg-elevated)] text-[var(--text)] shadow-[var(--shadow-control)]"
: "bg-transparent text-[var(--text-muted)] hover:text-[var(--text)]"
}`}
>
{demo.title}
</button>
);
})}
</div>
)}
{activeDemoData && (
<p className="mb-5 text-[14.5px] text-[var(--text-muted)] leading-[1.6] max-w-[68ch]">
{activeDemoData.description}
</p>
)}
<div className="shell-docs-radius-surface relative overflow-hidden border border-[var(--border)] bg-[var(--bg-surface)] shadow-[var(--shadow-panel)]">
{activeDemoData && (
<iframe
src={activeDemoData.iframeUrl}
className="w-full h-[480px] sm:h-[600px] block"
title={`${activeDemoData.title} Demo`}
/>
)}
<div
aria-hidden
className="shell-docs-radius-surface pointer-events-none absolute inset-0 ring-1 ring-inset ring-white/5"
/>
</div>
</section>
)}
</div>
</div>
);
}
@@ -0,0 +1,110 @@
import type { ReactNode } from "react";
import { FrameworkOverview } from "./framework-overview";
import type {
FrameworkOverviewData,
LiveDemo,
SupportedFeature,
} from "@/data/frameworks/types";
/**
* MDX-friendly adapter for `<FrameworkOverview>`. Accepts the flat props
* the v1 docs MDX uses (e.g. `frameworkName`, `frameworkIcon`, `header`,
* `supportedFeatures`, ...) and renders the existing data-driven
* `FrameworkOverview` component with a synthesized `FrameworkOverviewData`.
*
* Why this exists: `docs_mode: authored` frameworks ship their own
* `integrations/<folder>/index.mdx` ported from v1, which uses flat
* props that don't match the typed `data: FrameworkOverviewData` shape
* the data-driven path uses. Without this adapter, MDX `<FrameworkOverview>`
* would render as the registry shim (empty div + children) and the
* ported props would be dropped on the floor.
*
* The icon is passed through as a JSX node via `iconOverride`, so MDX
* files keep using `frameworkIcon={<MastraIcon className="..." />}`
* without needing to know about the iconKey registry.
*
* `currentFramework` is the URL framework slug (e.g. `langgraph-fastapi`)
* supplied by the page render site via a per-render override of this
* component in the MDX components map. It feeds into
* `FrameworkOverview`'s `rewriteHref` link rewriter so internal links
* and feature-viewer URLs land on the URL-active variant. Two cases:
*
* 1. Same-slug case (e.g. `/mastra` rendering `integrations/mastra/index.mdx`,
* where the MDX's `guideLink="/mastra/quickstart"` already embeds the
* URL slug). `fromSlug === toSlug` → `rewriteHref` short-circuits and
* every link passes through unchanged.
* 2. Shared-folder case (e.g. `/langgraph-fastapi` rendering
* `integrations/langgraph/index.mdx`, where the MDX embeds
* `/langgraph/...` and we need to rewrite to `/langgraph-fastapi/...`).
* Without this prop the adapter passed an empty `currentFramework`,
* so the rewriter stripped `/langgraph/` entirely (toSlug was empty),
* producing broken `//` URLs.
*
* Fallback: when the page render site doesn't override this component
* (e.g. an MDX rendered outside the framework-scoped router), we derive
* the slug from `guideLink` itself — that makes `fromSlug === toSlug`
* so the rewriter no-ops, preserving the historical empty-slug behavior
* for the same-slug case.
*/
export interface MdxFrameworkOverviewProps {
frameworkName: string;
frameworkIcon?: ReactNode;
header: string;
subheader?: string;
bannerVideo?: string;
guideLink?: string;
initCommand?: string;
featuresLink?: string;
supportedFeatures?: SupportedFeature[];
architectureImage?: string;
architectureVideo?: string;
liveDemos?: LiveDemo[];
tutorialLink?: string;
/**
* URL framework slug bound by the per-render override in
* `app/[framework]/[[...slug]]/page.tsx` (see header comment). Authored
* MDX never sets this directly — the render site injects it via the
* components map so the rewriter has the URL-active variant to rewrite
* toward.
*/
currentFramework?: string;
}
export function MdxFrameworkOverview(props: MdxFrameworkOverviewProps) {
// Derive the "from" slug embedded in guideLink so the fallback path
// (no `currentFramework` from the render site) makes rewriteHref a
// no-op. Mirrors `FrameworkOverview`'s own `fromSlug` derivation.
const guideLinkSlug = (props.guideLink ?? "").split("/")[1] ?? "";
const currentFramework = props.currentFramework ?? guideLinkSlug;
const synthData: FrameworkOverviewData = {
// slug is only used by FrameworkOverview's link-rewriting logic to
// strip a "from-slug" prefix off internal links. We pass an empty
// string here because the `data.slug` field is no longer the source
// of truth for the rewriter — `FrameworkOverview` derives `fromSlug`
// from `guideLink` directly. Leaving this empty avoids a stale
// duplicate of the prefix.
slug: "",
frameworkName: props.frameworkName,
// iconKey is ignored because we always pass `iconOverride` below.
iconKey: "",
header: props.header,
subheader: props.subheader ?? "",
bannerVideo: props.bannerVideo,
guideLink: props.guideLink ?? "",
initCommand: props.initCommand ?? "npx copilotkit@latest init",
featuresLink: props.featuresLink ?? "",
supportedFeatures: props.supportedFeatures ?? [],
architectureImage: props.architectureImage,
architectureVideo: props.architectureVideo,
liveDemos: props.liveDemos ?? [],
tutorialLink: props.tutorialLink,
};
return (
<FrameworkOverview
data={synthData}
currentFramework={currentFramework}
iconOverride={props.frameworkIcon}
/>
);
}