diff --git a/docs-site/content/cookbook/pan2/pan2.mdx b/docs-site/content/cookbook/pan2/pan2.mdx
new file mode 100644
index 000000000..07836529a
--- /dev/null
+++ b/docs-site/content/cookbook/pan2/pan2.mdx
@@ -0,0 +1,7 @@
+---
+title: PAN 2
+description: Configure text-to-video and first-frame image-to-video generation with PAN 2.
+mode: frame
+---
+
+
diff --git a/docs-site/lib/cookbook-pages.ts b/docs-site/lib/cookbook-pages.ts
index c6d5d02bb..c2b2fe1a1 100644
--- a/docs-site/lib/cookbook-pages.ts
+++ b/docs-site/lib/cookbook-pages.ts
@@ -66,6 +66,15 @@ export const recipePages = [
description: "Turn a reference image into video with Wan2.1.",
tags: ["Image to video", "14B", "480p"],
},
+ {
+ id: "pan2",
+ family: "PAN 2",
+ title: "PAN 2",
+ mark: "PAN",
+ path: "/cookbook/pan2/pan2",
+ description: "Text-to-video and first-frame image-to-video from the 29B PAN 2 model.",
+ tags: ["Text or image to video", "29B"],
+ },
{
id: "ltx2-distilled",
family: "LTX",
@@ -128,6 +137,7 @@ const backendModels: Record = {
"FastVideo/FastWan2.1-T2V-1.3B-Diffusers": "fastwan21",
"Wan-AI/Wan2.1-I2V-14B-480P-Diffusers": "wan21-i2v",
"FastVideo/LTX2-Distilled-Diffusers": "ltx2-distilled",
+ "IFM/PAN2": "pan2",
};
const TASK_GROUPS: Array<[RegExp, string]> = [
[/world/, "Text to world"],
diff --git a/docs-site/navigation.json b/docs-site/navigation.json
index 3b328cf38..e70b0fa5c 100644
--- a/docs-site/navigation.json
+++ b/docs-site/navigation.json
@@ -259,7 +259,11 @@
"collapsed": true,
"items": [
{
- "label": "Recipes and setup",
+ "label": "PAN 2",
+ "href": "/cookbook/pan2/pan2"
+ },
+ {
+ "label": "Family guide",
"href": "/cookbook/pan2"
},
{
diff --git a/docs-site/tests/cookbook.spec.ts b/docs-site/tests/cookbook.spec.ts
index 644c17925..c7d7a95d6 100644
--- a/docs-site/tests/cookbook.spec.ts
+++ b/docs-site/tests/cookbook.spec.ts
@@ -443,19 +443,24 @@ test("source-backed serving pages expose their actual server settings", async ({
await expect(page.getByRole("button", { name: "Copy config + command", exact: true })).toBeEnabled();
});
-test("PAN 2 has a family card with the IFM logo and a source-backed serving page", async ({ page }) => {
- await page.goto("/FastVideo/cookbook/pan2");
- const pan2 = page.locator(".recipe-card").filter({ hasText: "PAN 2" });
+test("PAN 2 has a family card with the IFM logo and an editable serving deployment", async ({ page }) => {
+ await page.goto("/FastVideo/cookbook");
+ await page.getByLabel("Search recipes").fill("PAN 2");
+ const pan2 = page.locator(".recipe-card");
+ await expect(pan2).toHaveCount(1);
await expect(pan2.locator("img")).toHaveAttribute("src", /\/ifm\.webp$/);
await expect(pan2.locator(".recipe-card-kicker")).toHaveText("IFM");
await pan2.click();
+ await expect(page).toHaveURL(/cookbook\/pan2\/pan2$/);
+ await expect(page.locator(".recipe-matrix")).toBeVisible({ timeout: 30000 });
await expect(page.getByRole("heading", { level: 1 })).toHaveText("PAN 2");
- await chip(page, "Run mode", "Serving");
- await page.getByRole("tab", { name: "config.yaml", exact: true }).click();
- const config = page.getByLabel("Source example", { exact: true });
- await expect(config).toContainText("model_path: IFM/PAN2");
- await expect(config).toContainText("served_model_name: pan2");
- await expect(page.getByRole("button", { name: "Copy config + command", exact: true })).toBeEnabled();
+ await settings(page, "Serving");
+ await page.getByLabel("Port", { exact: true }).fill("9010");
+ await output(page, "config.yaml");
+ await expect(page.getByLabel("Generated YAML", { exact: true })).toContainText(/"port": 9010/);
+ await expect(page.getByLabel("Generated YAML", { exact: true })).toContainText(/"model_path": "IFM\/PAN2"/);
+ await expect(page.getByLabel("Generated YAML", { exact: true })).toContainText(/"dit_layerwise": false/);
+ await expect(page.getByRole("button", { name: "Download YAML", exact: false })).toBeEnabled();
});
test("source model hardware preferences survive hydration and reload", async ({ page }) => {