feat(website): SEO model pages — 207 models, FAQ JSON-LD, partner node support#11892
feat(website): SEO model pages — 207 models, FAQ JSON-LD, partner node support#11892christian-byrne wants to merge 23 commits intomainfrom
Conversation
- 103-model registry (100 local + 3 partner nodes) in models.ts with docsUrl/blogUrl fields - Dynamic [slug].astro route with SoftwareApplication + BreadcrumbList + FAQPage JSON-LD - ModelHeroSection.vue with partner node support and docs/tutorial CTAs - "What is X?" section on every model page targeting AI Overviews / PAA - generate-models.ts parser with API_PROVIDER_MAP for 30+ partner node providers - Auto-discovery GH Actions workflow (weekly, opens issue on new models) - add-model-page Glary-Bot skill for non-dev Slack-driven PRs - Index page listing all 103 models Closes FE-421
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a "Supported Models" feature: a generator extracts model entries from workflow templates into a JSON registry, typed model config and helpers, listing and detail pages with SEO/JSON‑LD and a hero component, i18n keys, contributor skill doc, and a weekly GitHub Actions job to surface new models. ChangesSupported Models feature
Sequence DiagramsequenceDiagram
actor User
participant Browser
participant ListingPage as "Listing Page"
participant DetailPage as "Detail Page"
participant Registry as "models (models.ts)"
participant Layout as "BaseLayout"
participant Hero as "ModelHeroSection"
User->>Browser: Navigate to /p/supported-models
Browser->>ListingPage: Request listing
ListingPage->>Registry: import models array
ListingPage->>Browser: Render grid of model cards
User->>Browser: Click a model card
Browser->>DetailPage: Request /p/supported-models/:slug
DetailPage->>Registry: getModelBySlug(slug)
DetailPage->>Layout: Render page with inline JSON-LD
DetailPage->>Hero: Pass model props
Hero->>Browser: Render hero + CTAs
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
🌐 Website E2ETip All tests passed.
🔗 Website PreviewWebsite Preview: https://comfy-website-preview-pr-11892.vercel.app This commit: https://website-frontend-j2nxtay6m-comfyui.vercel.app Last updated: 2026-05-04T23:57:51Z for |
🎭 Playwright: ✅ 1485 passed, 0 failed📊 Browser Reports
|
📦 Bundle: 5.26 MB gzip 🟢 -402 BDetailsSummary
Category Glance App Entry Points — 22.6 kB (baseline 22.6 kB) • ⚪ 0 BMain entry bundles and manifests
Status: 1 added / 1 removed Graph Workspace — 1.24 MB (baseline 1.24 MB) • ⚪ 0 BGraph editor runtime, canvas, workflow orchestration
Status: 1 added / 1 removed Views & Navigation — 82.3 kB (baseline 82.3 kB) • ⚪ 0 BTop-level views, pages, and routed surfaces
Status: 9 added / 9 removed / 2 unchanged Panels & Settings — 489 kB (baseline 489 kB) • ⚪ 0 BConfiguration panels, inspectors, and settings screens
Status: 10 added / 10 removed / 11 unchanged User & Accounts — 17.6 kB (baseline 17.6 kB) • ⚪ 0 BAuthentication, profile, and account management bundles
Status: 5 added / 5 removed / 2 unchanged Editors & Dialogs — 112 kB (baseline 112 kB) • ⚪ 0 BModals, dialogs, drawers, and in-app editors
Status: 4 added / 4 removed UI Components — 62.9 kB (baseline 62.9 kB) • ⚪ 0 BReusable component library chunks
Status: 5 added / 5 removed / 9 unchanged Data & Services — 3.05 MB (baseline 3.05 MB) • ⚪ 0 BStores, services, APIs, and repositories
Status: 13 added / 13 removed / 4 unchanged Utilities & Hooks — 365 kB (baseline 365 kB) • ⚪ 0 BHelpers, composables, and utility bundles
Status: 13 added / 13 removed / 18 unchanged Vendor & Third-Party — 9.94 MB (baseline 9.94 MB) • ⚪ 0 BExternal libraries and shared vendor chunks Status: 16 unchanged Other — 8.82 MB (baseline 8.82 MB) • 🟢 -1.37 kBBundles that do not match a named category
Status: 57 added / 57 removed / 79 unchanged ⚡ Performance Report
No regressions detected. All metrics
Historical variance (last 15 runs)
Trend (last 15 commits on main)
Raw data{
"timestamp": "2026-05-05T00:20:21.443Z",
"gitSha": "3d16f4ce0c5cb4079bad54bb41a56be5d3968307",
"branch": "glary/seo-model-pages",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2003.6059999999907,
"styleRecalcs": 9,
"styleRecalcDurationMs": 5.542,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 294.18,
"heapDeltaBytes": 22785616,
"heapUsedBytes": 71419324,
"domNodes": 17,
"jsHeapTotalBytes": 14942208,
"scriptDurationMs": 14.381,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-idle",
"durationMs": 2006.2920000000304,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.684999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 315.398,
"heapDeltaBytes": 22812240,
"heapUsedBytes": 72144588,
"domNodes": 17,
"jsHeapTotalBytes": 14942208,
"scriptDurationMs": 16.314000000000004,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-idle",
"durationMs": 2014.5820000000185,
"styleRecalcs": 8,
"styleRecalcDurationMs": 5.877000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 322.52599999999995,
"heapDeltaBytes": -5751596,
"heapUsedBytes": 42819120,
"domNodes": 16,
"jsHeapTotalBytes": 14807040,
"scriptDurationMs": 13.907000000000002,
"eventListeners": -131,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1706.905000000006,
"styleRecalcs": 72,
"styleRecalcDurationMs": 31.279,
"layouts": 12,
"layoutDurationMs": 3.1240000000000006,
"taskDurationMs": 652.6719999999999,
"heapDeltaBytes": 3241100,
"heapUsedBytes": 51842444,
"domNodes": -261,
"jsHeapTotalBytes": 14807040,
"scriptDurationMs": 99.25300000000001,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1708.3569999999781,
"styleRecalcs": 71,
"styleRecalcDurationMs": 28.727,
"layouts": 12,
"layoutDurationMs": 3.1400000000000006,
"taskDurationMs": 624.8000000000001,
"heapDeltaBytes": -1313896,
"heapUsedBytes": 47319504,
"domNodes": -260,
"jsHeapTotalBytes": 15593472,
"scriptDurationMs": 94.886,
"eventListeners": -131,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1704.733000000033,
"styleRecalcs": 73,
"styleRecalcDurationMs": 30.956999999999997,
"layouts": 12,
"layoutDurationMs": 3.239,
"taskDurationMs": 621.3989999999999,
"heapDeltaBytes": -628664,
"heapUsedBytes": 67332352,
"domNodes": 56,
"jsHeapTotalBytes": 19660800,
"scriptDurationMs": 100.697,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1723.444000000029,
"styleRecalcs": 31,
"styleRecalcDurationMs": 17.297,
"layouts": 6,
"layoutDurationMs": 0.715,
"taskDurationMs": 279.651,
"heapDeltaBytes": 553744,
"heapUsedBytes": 48522504,
"domNodes": 77,
"jsHeapTotalBytes": 14680064,
"scriptDurationMs": 26.652,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1705.284000000006,
"styleRecalcs": 30,
"styleRecalcDurationMs": 14.364,
"layouts": 6,
"layoutDurationMs": 0.5289999999999999,
"taskDurationMs": 240.355,
"heapDeltaBytes": 67484,
"heapUsedBytes": 48988036,
"domNodes": 76,
"jsHeapTotalBytes": 15204352,
"scriptDurationMs": 15.791,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1715.3469999999515,
"styleRecalcs": 33,
"styleRecalcDurationMs": 14.473,
"layouts": 6,
"layoutDurationMs": 0.619,
"taskDurationMs": 246.67999999999998,
"heapDeltaBytes": 331960,
"heapUsedBytes": 48956592,
"domNodes": 79,
"jsHeapTotalBytes": 14680064,
"scriptDurationMs": 18.138,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 542.8180000000111,
"styleRecalcs": 12,
"styleRecalcDurationMs": 9.404,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 314.408,
"heapDeltaBytes": 9115644,
"heapUsedBytes": 57084856,
"domNodes": 20,
"jsHeapTotalBytes": 15204352,
"scriptDurationMs": 56.351,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "dom-widget-clipping",
"durationMs": 467.2610000000077,
"styleRecalcs": 13,
"styleRecalcDurationMs": 13.998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 298.505,
"heapDeltaBytes": 9693740,
"heapUsedBytes": 57327904,
"domNodes": 21,
"jsHeapTotalBytes": 14680064,
"scriptDurationMs": 53.975,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999727
},
{
"name": "dom-widget-clipping",
"durationMs": 447.17000000002827,
"styleRecalcs": 12,
"styleRecalcDurationMs": 7.292,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 272.894,
"heapDeltaBytes": 9133564,
"heapUsedBytes": 56863812,
"domNodes": 19,
"jsHeapTotalBytes": 15204352,
"scriptDurationMs": 49.696,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "large-graph-idle",
"durationMs": 2019.3010000000413,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.889000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 507.93399999999997,
"heapDeltaBytes": 15547024,
"heapUsedBytes": 74529284,
"domNodes": -262,
"jsHeapTotalBytes": 4280320,
"scriptDurationMs": 80.51799999999997,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2035.368999999946,
"styleRecalcs": 10,
"styleRecalcDurationMs": 6.901000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 442.318,
"heapDeltaBytes": 4095828,
"heapUsedBytes": 60790616,
"domNodes": -259,
"jsHeapTotalBytes": 5009408,
"scriptDurationMs": 75.32999999999998,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-idle",
"durationMs": 2039.0149999999494,
"styleRecalcs": 10,
"styleRecalcDurationMs": 8.444999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 529.52,
"heapDeltaBytes": 3997056,
"heapUsedBytes": 60769308,
"domNodes": -259,
"jsHeapTotalBytes": 3698688,
"scriptDurationMs": 95.69800000000001,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-pan",
"durationMs": 2099.2430000000013,
"styleRecalcs": 69,
"styleRecalcDurationMs": 17.809,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 912.792,
"heapDeltaBytes": 9781876,
"heapUsedBytes": 68812604,
"domNodes": -257,
"jsHeapTotalBytes": 1019904,
"scriptDurationMs": 309.053,
"eventListeners": -125,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2090.828999999985,
"styleRecalcs": 69,
"styleRecalcDurationMs": 16.933000000000003,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 900.5509999999999,
"heapDeltaBytes": 6544088,
"heapUsedBytes": 65683220,
"domNodes": -258,
"jsHeapTotalBytes": -28672,
"scriptDurationMs": 318.169,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2073.4939999999824,
"styleRecalcs": 68,
"styleRecalcDurationMs": 17.220000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 868.386,
"heapDeltaBytes": -1986476,
"heapUsedBytes": 57294296,
"domNodes": -262,
"jsHeapTotalBytes": 5738496,
"scriptDurationMs": 292.566,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-zoom",
"durationMs": 3098.8739999999666,
"styleRecalcs": 66,
"styleRecalcDurationMs": 17.266,
"layouts": 60,
"layoutDurationMs": 6.957,
"taskDurationMs": 1077.664,
"heapDeltaBytes": 8588300,
"heapUsedBytes": 69144000,
"domNodes": -263,
"jsHeapTotalBytes": 4222976,
"scriptDurationMs": 387.473,
"eventListeners": -125,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-zoom",
"durationMs": 3093.2959999998957,
"styleRecalcs": 65,
"styleRecalcDurationMs": 18.225999999999996,
"layouts": 60,
"layoutDurationMs": 7.463000000000001,
"taskDurationMs": 1124.557,
"heapDeltaBytes": -9667604,
"heapUsedBytes": 52515552,
"domNodes": -263,
"jsHeapTotalBytes": 4542464,
"scriptDurationMs": 403.78400000000005,
"eventListeners": -125,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-zoom",
"durationMs": 3078.2729999999674,
"styleRecalcs": 66,
"styleRecalcDurationMs": 18.349000000000004,
"layouts": 60,
"layoutDurationMs": 7.3439999999999985,
"taskDurationMs": 1123.586,
"heapDeltaBytes": 18596156,
"heapUsedBytes": 79148184,
"domNodes": -262,
"jsHeapTotalBytes": -495616,
"scriptDurationMs": 407.627,
"eventListeners": -123,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "minimap-idle",
"durationMs": 2027.9449999999883,
"styleRecalcs": 8,
"styleRecalcDurationMs": 7.220999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 531.058,
"heapDeltaBytes": 4128632,
"heapUsedBytes": 63508496,
"domNodes": -263,
"jsHeapTotalBytes": 552960,
"scriptDurationMs": 86.019,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "minimap-idle",
"durationMs": 2037.8049999999348,
"styleRecalcs": 9,
"styleRecalcDurationMs": 6.777000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 434.767,
"heapDeltaBytes": 13160332,
"heapUsedBytes": 72865800,
"domNodes": -260,
"jsHeapTotalBytes": -757760,
"scriptDurationMs": 68.477,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2017.1589999999924,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.179000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 486.324,
"heapDeltaBytes": 13449492,
"heapUsedBytes": 73448796,
"domNodes": -260,
"jsHeapTotalBytes": -233472,
"scriptDurationMs": 78.116,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 529.389999999978,
"styleRecalcs": 47,
"styleRecalcDurationMs": 12.022000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 311.59000000000003,
"heapDeltaBytes": 8837864,
"heapUsedBytes": 57470028,
"domNodes": 19,
"jsHeapTotalBytes": 15990784,
"scriptDurationMs": 104.871,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999727
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 460.2909999999838,
"styleRecalcs": 47,
"styleRecalcDurationMs": 10.337000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 290.591,
"heapDeltaBytes": 9083024,
"heapUsedBytes": 57793752,
"domNodes": 20,
"jsHeapTotalBytes": 16252928,
"scriptDurationMs": 99.09,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 469.97399999997924,
"styleRecalcs": 48,
"styleRecalcDurationMs": 10.845,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 292.86500000000007,
"heapDeltaBytes": 8855148,
"heapUsedBytes": 57532420,
"domNodes": 22,
"jsHeapTotalBytes": 15990784,
"scriptDurationMs": 99.504,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.663333333333338,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "subgraph-idle",
"durationMs": 2007.2150000000306,
"styleRecalcs": 8,
"styleRecalcDurationMs": 7.210000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 282.62299999999993,
"heapDeltaBytes": 23087344,
"heapUsedBytes": 71738864,
"domNodes": 16,
"jsHeapTotalBytes": 15204352,
"scriptDurationMs": 11.837,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-idle",
"durationMs": 2007.3249999999234,
"styleRecalcs": 10,
"styleRecalcDurationMs": 7.607000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 302.328,
"heapDeltaBytes": 1021136,
"heapUsedBytes": 67382000,
"domNodes": 20,
"jsHeapTotalBytes": 19755008,
"scriptDurationMs": 15.638000000000003,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-idle",
"durationMs": 1998.613999999975,
"styleRecalcs": 11,
"styleRecalcDurationMs": 8.139999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 286.166,
"heapDeltaBytes": 23297596,
"heapUsedBytes": 71847096,
"domNodes": 22,
"jsHeapTotalBytes": 14680064,
"scriptDurationMs": 13.154,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1685.2439999999547,
"styleRecalcs": 76,
"styleRecalcDurationMs": 31.608999999999998,
"layouts": 16,
"layoutDurationMs": 4.031,
"taskDurationMs": 534.1479999999999,
"heapDeltaBytes": 14154440,
"heapUsedBytes": 63525716,
"domNodes": 63,
"jsHeapTotalBytes": 14942208,
"scriptDurationMs": 76.831,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1715.1499999999942,
"styleRecalcs": 76,
"styleRecalcDurationMs": 31.420999999999996,
"layouts": 16,
"layoutDurationMs": 3.815,
"taskDurationMs": 586.29,
"heapDeltaBytes": 2446328,
"heapUsedBytes": 50719044,
"domNodes": -259,
"jsHeapTotalBytes": 15593472,
"scriptDurationMs": 77.874,
"eventListeners": -131,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.669999999999998,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1733.3629999999403,
"styleRecalcs": 78,
"styleRecalcDurationMs": 32.848,
"layouts": 16,
"layoutDurationMs": 3.9230000000000005,
"taskDurationMs": 561.9909999999999,
"heapDeltaBytes": 14004872,
"heapUsedBytes": 63636036,
"domNodes": 65,
"jsHeapTotalBytes": 15466496,
"scriptDurationMs": 83.71600000000001,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "viewport-pan-sweep",
"durationMs": 8131.513999999981,
"styleRecalcs": 250,
"styleRecalcDurationMs": 53.346000000000004,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3435.94,
"heapDeltaBytes": 16714112,
"heapUsedBytes": 74988256,
"domNodes": -258,
"jsHeapTotalBytes": 8097792,
"scriptDurationMs": 1174.602,
"eventListeners": -113,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8099.481999999966,
"styleRecalcs": 250,
"styleRecalcDurationMs": 50.698,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 2988.256,
"heapDeltaBytes": 11426312,
"heapUsedBytes": 69627508,
"domNodes": -259,
"jsHeapTotalBytes": 757760,
"scriptDurationMs": 958.227,
"eventListeners": -113,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8101.828000000069,
"styleRecalcs": 249,
"styleRecalcDurationMs": 50.817,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3059.185,
"heapDeltaBytes": 10371956,
"heapUsedBytes": 68495368,
"domNodes": -260,
"jsHeapTotalBytes": 3903488,
"scriptDurationMs": 1035.0549999999998,
"eventListeners": -113,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 11365.854999999954,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 11353.082,
"heapDeltaBytes": -24498900,
"heapUsedBytes": 172026032,
"domNodes": -8331,
"jsHeapTotalBytes": 23392256,
"scriptDurationMs": 520.275,
"eventListeners": -16466,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333338,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 11216.272000000004,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 11163.776,
"heapDeltaBytes": -31684760,
"heapUsedBytes": 173247724,
"domNodes": -8331,
"jsHeapTotalBytes": 26537984,
"scriptDurationMs": 539.933,
"eventListeners": -16465,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 10387.022,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 10381.403,
"heapDeltaBytes": -29986612,
"heapUsedBytes": 172443044,
"domNodes": -8359,
"jsHeapTotalBytes": 24440832,
"scriptDurationMs": 456.07,
"eventListeners": -16468,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 13176.326000000017,
"styleRecalcs": 65,
"styleRecalcDurationMs": 17.904000000000003,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 13144.224000000002,
"heapDeltaBytes": -23750924,
"heapUsedBytes": 177130836,
"domNodes": -8331,
"jsHeapTotalBytes": -20471808,
"scriptDurationMs": 735.901,
"eventListeners": -16460,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-pan",
"durationMs": 13061.495000000035,
"styleRecalcs": 66,
"styleRecalcDurationMs": 17.733999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 13045.516,
"heapDeltaBytes": -46915840,
"heapUsedBytes": 164999056,
"domNodes": -8331,
"jsHeapTotalBytes": -5181440,
"scriptDurationMs": 778.866,
"eventListeners": -16464,
"totalBlockingTimeMs": 42,
"frameDurationMs": 17.219999999999953,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 12654.421999999955,
"styleRecalcs": 65,
"styleRecalcDurationMs": 16.351000000000006,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 12637.623999999998,
"heapDeltaBytes": -33476780,
"heapUsedBytes": 165176676,
"domNodes": -8331,
"jsHeapTotalBytes": -4132864,
"scriptDurationMs": 747.854,
"eventListeners": -16464,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.220000000000073,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "workflow-execution",
"durationMs": 100.89000000004944,
"styleRecalcs": 10,
"styleRecalcDurationMs": 14.054,
"layouts": 3,
"layoutDurationMs": 1.1049999999999998,
"taskDurationMs": 67.01000000000002,
"heapDeltaBytes": 3448168,
"heapUsedBytes": 53671316,
"domNodes": 146,
"jsHeapTotalBytes": 262144,
"scriptDurationMs": 13.83,
"eventListeners": 37,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.663333333333338,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "workflow-execution",
"durationMs": 445.73300000001836,
"styleRecalcs": 17,
"styleRecalcDurationMs": 20.122,
"layouts": 4,
"layoutDurationMs": 1.054,
"taskDurationMs": 107.58200000000001,
"heapDeltaBytes": 5155424,
"heapUsedBytes": 55309784,
"domNodes": 157,
"jsHeapTotalBytes": 0,
"scriptDurationMs": 24.172,
"eventListeners": 71,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "workflow-execution",
"durationMs": 442.0619999999644,
"styleRecalcs": 16,
"styleRecalcDurationMs": 18.730999999999998,
"layouts": 5,
"layoutDurationMs": 1.123,
"taskDurationMs": 99.60000000000001,
"heapDeltaBytes": 5177460,
"heapUsedBytes": 56170000,
"domNodes": 157,
"jsHeapTotalBytes": 524288,
"scriptDurationMs": 23.527,
"eventListeners": 71,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.663333333333338,
"p95FrameDurationMs": 16.700000000000273
}
]
} |
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## main #11892 +/- ##
===========================================
- Coverage 71.58% 56.29% -15.29%
===========================================
Files 1494 1385 -109
Lines 89332 70894 -18438
Branches 24945 18854 -6091
===========================================
- Hits 63951 39913 -24038
- Misses 24461 30451 +5990
+ Partials 920 530 -390
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (1)
apps/website/src/pages/p/supported-models/[slug].astro (1)
125-134: ⚡ Quick winAvoid hydrating this static hero.
ModelHeroSectionis presentational, soclient:loadships Vue runtime code to every model page without adding behavior. Server-rendering it keeps the SEO page lighter.Proposed fix
<ModelHeroSection displayName={displayName} description={description} huggingFaceUrl={model.huggingFaceUrl} docsUrl={model.docsUrl} blogUrl={model.blogUrl} workflowCount={model.workflowCount} directory={model.directory} - client:load />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/website/src/pages/p/supported-models/`[slug].astro around lines 125 - 134, ModelHeroSection is a purely presentational component but is being hydrated via the client:load directive, which unnecessarily ships the Vue runtime; remove the client:load attribute from the ModelHeroSection JSX/AST invocation so it is server-rendered only (leave all props like displayName, description, huggingFaceUrl, docsUrl, blogUrl, workflowCount, directory untouched), and if any interactive behavior is later required extract that behavior into a separate child component and hydrate only that child.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/model-page-discovery.yml:
- Around line 69-95: The script that builds NEW_SLUGS reads model objects' slug
property (m.slug) but generate-models.ts serializes each entry as suggestedSlug,
so m.slug is undefined; update the NEW_SLUGS node snippet to read the correct
property (suggestedSlug) instead of slug (or fall back to suggestedSlug when
slug is absent) so the extracted slugs reflect the generated out.json entries;
modify the map callback in the NEW_SLUGS extraction to use m.suggestedSlug (or
m.suggestedSlug || m.slug) and keep the rest of the JSON.stringify flow
unchanged.
In `@apps/website/src/components/models/ModelHeroSection.vue`:
- Around line 103-106: The anchor that opens the blog in a new tab (the <a>
using :href="blogUrl" and target="_blank" in the ModelHeroSection component)
exposes window.opener; add rel="noopener noreferrer" to that <a> tag so external
pages cannot access the opener and to prevent potential security/performance
issues.
In `@apps/website/src/config/models.ts`:
- Around line 24-31: The Hugging Face links for partner-only models should be
empty to avoid leaking misleading URLs; update the huggingFaceUrl property to an
empty string ('') for any model objects where directory === 'partner_nodes'
(e.g., the model with slug 'nano-banana') and similarly change the other
partner_nodes entries referenced in the review so their huggingFaceUrl is ''
instead of 'https://huggingface.co/'. Ensure you only modify the huggingFaceUrl
field and leave displayName, description, directory, docsUrl and ogImage
untouched.
In `@apps/website/src/i18n/translations.ts`:
- Around line 3598-3602: Add the missing trailing comma after the previous
translation entry ('zh-CN': 'Creative Studios AI 负责人') so the subsequent
translations object (starting at the 'models.hero.eyebrow' block) is valid
JSON/JS; update the translations map by inserting a comma after the 'zh-CN'
entry in translations.ts to fix the parse/build error.
In `@apps/website/src/pages/p/supported-models/`[slug].astro:
- Around line 143-150: The anchor rendering the tutorial link (the JSX that
checks model.docsUrl and renders <a href={model.docsUrl} target="_blank">)
should include rel="noopener noreferrer" to harden external links opened in a
new tab; update that anchor element so the tag that uses model.docsUrl and
target="_blank" also sets rel="noopener noreferrer".
In `@apps/website/src/pages/p/supported-models/index.astro`:
- Around line 9-22: The dirLabel mapping lacks an entry for 'partner_nodes',
causing partner-model cards to show the raw directory name; add a key
'partner_nodes' to the dirLabel Record (the dirLabel constant defined at top of
supported-models page) with an appropriate user-facing label like 'Partner' or
'Partner Nodes' so partner_nodes resolves to a friendly badge text wherever
dirLabel is used.
---
Nitpick comments:
In `@apps/website/src/pages/p/supported-models/`[slug].astro:
- Around line 125-134: ModelHeroSection is a purely presentational component but
is being hydrated via the client:load directive, which unnecessarily ships the
Vue runtime; remove the client:load attribute from the ModelHeroSection JSX/AST
invocation so it is server-rendered only (leave all props like displayName,
description, huggingFaceUrl, docsUrl, blogUrl, workflowCount, directory
untouched), and if any interactive behavior is later required extract that
behavior into a separate child component and hydrate only that child.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 93f33c1f-0aad-4922-b77e-700fb7dff071
📒 Files selected for processing (10)
.claude/skills/add-model-page/SKILL.md.github/workflows/model-page-discovery.ymlapps/website/scripts/generate-models.tsapps/website/src/components/models/ModelHeroSection.vueapps/website/src/config/models.tsapps/website/src/config/routes.tsapps/website/src/i18n/translations.tsapps/website/src/layouts/BaseLayout.astroapps/website/src/pages/p/supported-models/[slug].astroapps/website/src/pages/p/supported-models/index.astro
| # Extract slugs from the generated out.json | ||
| NEW_SLUGS=$(node -e " | ||
| const fs = require('fs'); | ||
| const models = JSON.parse(fs.readFileSync('out.json', 'utf8')); | ||
| const arr = Array.isArray(models) ? models : Object.values(models); | ||
| const slugs = arr.map(m => m.slug).filter(Boolean); | ||
| console.log(JSON.stringify(slugs)); | ||
| ") | ||
|
|
||
| # Extract slugs from existing models.ts config | ||
| EXISTING_SLUGS=$(node -e " | ||
| const fs = require('fs'); | ||
| const content = fs.readFileSync('apps/website/src/config/models.ts', 'utf8'); | ||
| // Match slug: 'value' or slug: \"value\" patterns | ||
| const matches = [...content.matchAll(/slug\s*:\s*['\"]([^'\"]+)['\"]/g)]; | ||
| const slugs = matches.map(m => m[1]); | ||
| console.log(JSON.stringify(slugs)); | ||
| " 2>/dev/null || echo '[]') | ||
|
|
||
| # Find new slugs not in existing config | ||
| ADDED_SLUGS=$(node -e " | ||
| const newSlugs = $NEW_SLUGS; | ||
| const existing = $EXISTING_SLUGS; | ||
| const existingSet = new Set(existing); | ||
| const added = newSlugs.filter(s => !existingSet.has(s)); | ||
| console.log(JSON.stringify(added)); | ||
| ") |
There was a problem hiding this comment.
Read the generated slug field here.
generate-models.ts serializes each entry as suggestedSlug, so m.slug is always undefined and this scan will never report new models.
Proposed fix
- const slugs = arr.map(m => m.slug).filter(Boolean);
+ const slugs = arr.map(m => m.suggestedSlug ?? m.slug).filter(Boolean);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/model-page-discovery.yml around lines 69 - 95, The script
that builds NEW_SLUGS reads model objects' slug property (m.slug) but
generate-models.ts serializes each entry as suggestedSlug, so m.slug is
undefined; update the NEW_SLUGS node snippet to read the correct property
(suggestedSlug) instead of slug (or fall back to suggestedSlug when slug is
absent) so the extracted slugs reflect the generated out.json entries; modify
the map callback in the NEW_SLUGS extraction to use m.suggestedSlug (or
m.suggestedSlug || m.slug) and keep the rest of the JSON.stringify flow
unchanged.
| @@ -0,0 +1,223 @@ | |||
| --- | |||
| name: add-model-page | |||
| description: 'Add, update, or remove a model page entry in ComfyUI_frontend. Triggered by Glary-Bot Slack commands. Creates a PR to Comfy-Org/ComfyUI_frontend with the change and posts a Vercel preview link back to Slack.' | |||
There was a problem hiding this comment.
| description: 'Add, update, or remove a model page entry in ComfyUI_frontend. Triggered by Glary-Bot Slack commands. Creates a PR to Comfy-Org/ComfyUI_frontend with the change and posts a Vercel preview link back to Slack.' | |
| description: 'Add, update, or remove a model page entry on the comfy org website. Creates a PR to Comfy-Org/ComfyUI_frontend apps/website folder with the change and posts a Vercel preview link back to Slack.' |
|
|
||
| # add-model-page | ||
|
|
||
| Handle Glary-Bot Slack commands that add, update, or remove model pages in the ComfyUI website. |
There was a problem hiding this comment.
| Handle Glary-Bot Slack commands that add, update, or remove model pages in the ComfyUI website. | |
| add, update, or remove model pages in the ComfyUI website. |
| - `@Glary-Bot Add a model page for <model-name>` | ||
| - `@Glary-Bot Update the model page for <model-name>` | ||
| - `@Glary-Bot Remove <model-name> from model pages` |
There was a problem hiding this comment.
| - `@Glary-Bot Add a model page for <model-name>` | |
| - `@Glary-Bot Update the model page for <model-name>` | |
| - `@Glary-Bot Remove <model-name> from model pages` | |
| - `Add a model page for <model-name>` | |
| - `Update the model page for <model-name>` | |
| - `Remove <model-name> from model pages` |
|
|
||
| ## Repos involved | ||
|
|
||
| - **comfy-router** (this repo): skill lives here, no code changes needed |
There was a problem hiding this comment.
| - **comfy-router** (this repo): skill lives here, no code changes needed |
| - **ComfyUI_frontend** (`Comfy-Org/ComfyUI_frontend`): all file edits happen here | ||
|
|
||
| Assume ComfyUI_frontend is checked out at a sibling path. Find it: | ||
|
|
||
| ```bash | ||
| find ~ -maxdepth 5 -name "models.ts" -path "*/website/src/config/*" 2>/dev/null | head -1 | ||
| ``` | ||
|
|
||
| Set `FRONTEND` to that repo root (parent four levels up from the found path). | ||
|
|
||
| Key files inside `$FRONTEND`: |
There was a problem hiding this comment.
| - **ComfyUI_frontend** (`Comfy-Org/ComfyUI_frontend`): all file edits happen here | |
| Assume ComfyUI_frontend is checked out at a sibling path. Find it: | |
| ```bash | |
| find ~ -maxdepth 5 -name "models.ts" -path "*/website/src/config/*" 2>/dev/null | head -1 | |
| ``` | |
| Set `FRONTEND` to that repo root (parent four levels up from the found path). | |
| Key files inside `$FRONTEND`: |
| Run the generator to find the model: | ||
|
|
||
| ```bash | ||
| cd $FRONTEND |
There was a problem hiding this comment.
| cd $FRONTEND | |
| cd ComfyUI_frontend |
- Fix missing comma in translations.ts (broke build/oxfmt parse) - Fix partner node huggingFaceUrl: use empty string instead of HF homepage - Add rel="noopener noreferrer" to external anchor tags - Remove client:load from ModelHeroSection (presentational, no JS needed) - Fix GH Actions workflow: rename .yml→.yaml, bump to @v6 actions (pinact exempt), pin pnpm action to SHA, fix m.slug→m.suggestedSlug in discovery comparison - Rewrite add-model-page skill to be repo-agnostic (no Glary-Bot references, no hardcoded paths/system assumptions)
|
All review comments addressed in the second commit (8fe2dee): Build-breaking fix:
Review comments:
On |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.claude/skills/add-model-page/SKILL.md:
- Around line 110-118: The Chinese translation entries for the keys
'models.MODEL-SLUG.displayName' and 'models.MODEL-SLUG.description' are empty
strings, which prevents i18n fallback to English; update the 'zh-CN' values for
both keys to the same text as their 'en' values (i.e., set 'zh-CN' to "Human
Display Name" for models.MODEL-SLUG.displayName and to "One sentence describing
what this model does." for models.MODEL-SLUG.description) so the UI shows the
English text instead of blank in zh-CN.
In @.github/workflows/model-page-discovery.yaml:
- Around line 63-74: The current step that sets EXISTING_SLUGS swallows
node/script failures by falling back to '[]' (the "|| echo '[]'") which can mark
every discovered slug as new; change the shell logic so the node -e script reads
apps/website/src/config/models.ts and any runtime/file/parse error causes the
workflow to fail (remove the fallback echo and instead propagate non‑zero exit,
or explicitly check the node process exit status and exit with an error
message). Locate the EXISTING_SLUGS assignment and the inline node -e script
(variables like matches, slugs) and ensure errors from that script are not
masked but returned to the caller so the job fails fast.
- Around line 94-127: The workflow currently always creates a new GitHub issue
when NEW_COUNT != 0; add a pre-check that searches for an existing open
discovery issue and skips creation if one exists. Before the gh issue create
step, run a shell step that uses the GH CLI (or gh api) to list open issues in
the repo filtering by a stable title or label (e.g., "New models detected — add
to model pages" or a new dedicated label) or by matching any slug from
NEW_SLUGS; if a matching open issue is found, exit the step successfully without
calling gh issue create. Keep references to the same env vars (NEW_SLUGS,
NEW_COUNT) and the SLUG_LIST generation logic, and only run gh issue create when
the pre-check returns no matches.
In `@apps/website/src/i18n/translations.ts`:
- Around line 4288-4290: Update the English text for the translation key
'models.whisper-large-v3-fp16.description' to fix the article grammar by
changing "a audio encoder" to "an audio encoder"; locate the object keyed by
'models.whisper-large-v3-fp16.description' and replace the en string accordingly
while leaving the 'zh-CN' entry unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a4420a99-ac21-4ef9-bf27-4e88f6f6d794
📒 Files selected for processing (6)
.claude/skills/add-model-page/SKILL.md.github/workflows/model-page-discovery.yamlapps/website/src/components/models/ModelHeroSection.vueapps/website/src/config/models.tsapps/website/src/i18n/translations.tsapps/website/src/pages/p/supported-models/[slug].astro
✅ Files skipped from review due to trivial changes (1)
- apps/website/src/config/models.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/website/src/components/models/ModelHeroSection.vue
- apps/website/src/pages/p/supported-models/[slug].astro
| ```typescript | ||
| 'models.MODEL-SLUG.displayName': { | ||
| en: 'Human Display Name', | ||
| 'zh-CN': '' | ||
| }, | ||
| 'models.MODEL-SLUG.description': { | ||
| en: 'One sentence describing what this model does.', | ||
| 'zh-CN': '' | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify the skill currently instructs empty zh-CN values
rg -n "'zh-CN': ''" .claude/skills/add-model-page/SKILL.md
# Verify fallback logic in i18n helper uses nullish-coalescing (does not treat empty string as missing)
rg -n "return translations\\[key\\]\\[locale\\] \\?\\? translations\\[key\\]\\.en" apps/website/src/i18n/translations.tsRepository: Comfy-Org/ComfyUI_frontend
Length of output: 174
🏁 Script executed:
# Check if there are other instances of empty zh-CN in the skills directory
rg -n "'zh-CN': ''" .claude/skills/ --type markdown
# Verify the context around lines 113 and 117 to ensure they match the snippet
sed -n '110,120p' .claude/skills/add-model-page/SKILL.mdRepository: Comfy-Org/ComfyUI_frontend
Length of output: 412
Populate empty zh-CN translation values in the model template.
Empty strings in translation keys prevent fallback to English because the i18n handler uses nullish coalescing (??), which treats empty string as a valid value. This causes blank Chinese UI text. Update both keys on lines 113 and 117 to use the English values:
Suggested fix
'models.MODEL-SLUG.displayName': {
en: 'Human Display Name',
- 'zh-CN': ''
+ 'zh-CN': 'Human Display Name'
},
'models.MODEL-SLUG.description': {
en: 'One sentence describing what this model does.',
- 'zh-CN': ''
+ 'zh-CN': 'One sentence describing what this model does.'
},🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.claude/skills/add-model-page/SKILL.md around lines 110 - 118, The Chinese
translation entries for the keys 'models.MODEL-SLUG.displayName' and
'models.MODEL-SLUG.description' are empty strings, which prevents i18n fallback
to English; update the 'zh-CN' values for both keys to the same text as their
'en' values (i.e., set 'zh-CN' to "Human Display Name" for
models.MODEL-SLUG.displayName and to "One sentence describing what this model
does." for models.MODEL-SLUG.description) so the UI shows the English text
instead of blank in zh-CN.
| 'models.whisper-large-v3-fp16.description': { | ||
| en: 'Whisper Large V3 Fp16 — a audio encoder for use in ComfyUI workflows.', | ||
| 'zh-CN': 'Whisper Large V3 Fp16 — 用于 ComfyUI 工作流的audio encoder。' |
There was a problem hiding this comment.
Fix article grammar in the Whisper description.
The English copy says “a audio encoder”; this should be “an audio encoder”.
✏️ Proposed fix
'models.whisper-large-v3-fp16.description': {
- en: 'Whisper Large V3 Fp16 — a audio encoder for use in ComfyUI workflows.',
+ en: 'Whisper Large V3 Fp16 — an audio encoder for use in ComfyUI workflows.',
'zh-CN': 'Whisper Large V3 Fp16 — 用于 ComfyUI 工作流的audio encoder。'
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 'models.whisper-large-v3-fp16.description': { | |
| en: 'Whisper Large V3 Fp16 — a audio encoder for use in ComfyUI workflows.', | |
| 'zh-CN': 'Whisper Large V3 Fp16 — 用于 ComfyUI 工作流的audio encoder。' | |
| 'models.whisper-large-v3-fp16.description': { | |
| en: 'Whisper Large V3 Fp16 — an audio encoder for use in ComfyUI workflows.', | |
| 'zh-CN': 'Whisper Large V3 Fp16 — 用于 ComfyUI 工作流的audio encoder。' | |
| }, |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/website/src/i18n/translations.ts` around lines 4288 - 4290, Update the
English text for the translation key 'models.whisper-large-v3-fp16.description'
to fix the article grammar by changing "a audio encoder" to "an audio encoder";
locate the object keyed by 'models.whisper-large-v3-fp16.description' and
replace the en string accordingly while leaving the 'zh-CN' entry unchanged.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
apps/website/src/config/models.ts (1)
35-35: ⚡ Quick winAdd runtime invariants for slug integrity in the registry.
getModelBySlugreturns the first match, so duplicateslugvalues or invalidcanonicalSlugreferences will silently produce wrong routing/redirect behavior. Add a one-time registry validation on module load.♻️ Proposed patch
export const models: readonly Model[] = [ @@ ] +function validateModelsRegistry(input: readonly Model[]): void { + const slugs = new Set<string>() + + for (const model of input) { + if (slugs.has(model.slug)) { + throw new Error(`Duplicate model slug: ${model.slug}`) + } + slugs.add(model.slug) + } + + for (const model of input) { + if ( + model.canonicalSlug !== undefined && + (!slugs.has(model.canonicalSlug) || + model.canonicalSlug === model.slug) + ) { + throw new Error( + `Invalid canonicalSlug "${model.canonicalSlug}" on "${model.slug}"` + ) + } + } +} + +validateModelsRegistry(models) + export function getModelBySlug(slug: string): Model | undefined { return models.find((m) => m.slug === slug) }Also applies to: 2059-2061
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/website/src/config/models.ts` at line 35, Add a one-time runtime validation that ensures each entry in the exported models registry has a unique slug and that any canonicalSlug refers to an existing model slug: on module load iterate over the models array (reference: models) and build a slug set, assert no duplicate slug is found (throw with the duplicate slug and model id/name), then for each model with a canonicalSlug ensure that canonicalSlug exists in the slug set (throw with the invalid canonicalSlug and model slug if missing); this validation will prevent getModelBySlug from silently returning the wrong model and should run immediately when the module is imported.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@apps/website/src/config/models.ts`:
- Line 35: Add a one-time runtime validation that ensures each entry in the
exported models registry has a unique slug and that any canonicalSlug refers to
an existing model slug: on module load iterate over the models array (reference:
models) and build a slug set, assert no duplicate slug is found (throw with the
duplicate slug and model id/name), then for each model with a canonicalSlug
ensure that canonicalSlug exists in the slug set (throw with the invalid
canonicalSlug and model slug if missing); this validation will prevent
getModelBySlug from silently returning the wrong model and should run
immediately when the module is imported.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 87d4350c-1bae-43d8-807d-d7a4f886ddce
📒 Files selected for processing (5)
.claude/skills/add-model-page/SKILL.mdapps/website/scripts/generate-models.tsapps/website/src/components/models/ModelHeroSection.vueapps/website/src/config/models.tsapps/website/src/i18n/translations.ts
✅ Files skipped from review due to trivial changes (1)
- apps/website/src/i18n/translations.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- apps/website/src/components/models/ModelHeroSection.vue
- .claude/skills/add-model-page/SKILL.md
- apps/website/scripts/generate-models.ts
…ations
Architecture:
- Replace 1585-line hand-coded models.ts with generated-models.json (207 models,
auto-generated by running pnpm generate:models) + 40-line model-metadata.ts for
editorial overrides (docsUrl, blogUrl, featured) + slim 65-line models.ts that
merges both. No more manually maintained data file.
- Remove 830 per-model translation keys from translations.ts — displayName is now
a plain string from the generator, not a TranslationKey
- models.ts no longer imports TranslationKey; displayName/description are strings
- Add generate:models script to website package.json
Design token fixes (pattern-compliance violations):
- Replace text-[var(--color-primary-comfy-yellow)] with text-primary-comfy-yellow
- Replace text-white/70 with text-primary-comfy-canvas/70 (matches existing components)
- Replace invented --color-accent/--color-text-secondary with real tokens
- Fix h1 text from text-white to text-primary-comfy-canvas (matches HeroSection pattern)
i18n fixes:
- Add models.hero.tutorialCta, models.hero.blogLink, models.whatIs.heading,
models.whatIs.tutorialLink keys
- Use t() for all button labels and UI strings in ModelHeroSection.vue
- partner_nodes added to dirLabel in index.astro
Other:
- H1 now reads "{displayName} in ComfyUI" (FE-421 spec)
- Remove description prop from hero (redundant with What-is section)
- Fix GH Actions discovery: m.slug field now matches generator output
- Update add-model-page skill to document new 3-file architecture
|
All alerts resolved. Learn more about Socket for GitHub. This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored. |
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (1)
.github/workflows/model-page-discovery.yaml (1)
94-127:⚠️ Potential issue | 🟠 MajorSkip issue creation when an open discovery issue already exists.
This still opens a fresh issue on every scheduled run while unresolved slugs remain. Please keep the open-issue precheck from the earlier review before calling
gh issue create.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/model-page-discovery.yaml around lines 94 - 127, The "Open GitHub issue for new models" step currently creates a new issue every run; add a precheck that queries existing open discovery issues and skips creating a new one if any match. Before running gh issue create, run a check (e.g., using gh issue list or the GitHub Search API) for open issues with the same title/label/marker used by this workflow; if the check returns a match, exit the step without calling gh issue create. Use the existing env vars (NEW_COUNT, NEW_SLUGS) and the step name "Open GitHub issue for new models" to locate and update this step. Ensure the check runs in the same shell context and gates the gh issue create invocation so no duplicate issue is opened while an unresolved discovery issue exists.
🧹 Nitpick comments (2)
apps/website/scripts/generate-models.ts (1)
5-7: ⚡ Quick winHonor
WORKFLOW_TEMPLATES_PATHbefore falling back to the sibling checkout.
.github/workflows/model-page-discovery.yamlalready provides this path, but the script ignores it and always resolves a hard-coded relative location. That makes local/manual runs depend on a specific directory layout and leaves the CI env setting as dead configuration.Suggested fix
-const TEMPLATES_DIR = fileURLToPath( - new URL('../../../../workflow_templates/templates', import.meta.url) -) +const DEFAULT_TEMPLATES_DIR = fileURLToPath( + new URL('../../../../workflow_templates/templates', import.meta.url) +) + +const TEMPLATES_DIR = process.env.WORKFLOW_TEMPLATES_PATH + ? join(process.env.WORKFLOW_TEMPLATES_PATH, 'templates') + : DEFAULT_TEMPLATES_DIR🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/website/scripts/generate-models.ts` around lines 5 - 7, The TEMPLATES_DIR constant currently always resolves a hard-coded relative path; change its initialization to first check process.env.WORKFLOW_TEMPLATES_PATH and use that value (as an absolute path) when present, otherwise fall back to the existing fileURLToPath(new URL('../../../../workflow_templates/templates', import.meta.url)) behavior; update the declaration of TEMPLATES_DIR so code that uses TEMPLATES_DIR (the constant) will honor the WORKFLOW_TEMPLATES_PATH env var set by CI or local overrides.apps/website/src/pages/p/supported-models/index.astro (1)
9-23: ⚡ Quick winExtract the directory label map into a shared helper.
This table is duplicated in
apps/website/src/components/models/ModelHeroSection.vue(Lines 23-37), so the index badge and detail-page eyebrow can drift again the next time a directory is added or renamed. Centralizing it would prevent anotherpartner_nodes-style mismatch.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/website/src/pages/p/supported-models/index.astro` around lines 9 - 23, The directory-to-label map defined as dirLabel in index.astro is duplicated in ModelHeroSection.vue; extract this mapping into a shared helper (e.g., a new exported constant or function) and replace the local maps in both apps/website/src/pages/p/supported-models/index.astro (dirLabel) and the ModelHeroSection component with imports from that helper so both use the single source of truth for labels like 'partner_nodes' and 'diffusion_models'.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.claude/skills/add-model-page/SKILL.md:
- Around line 76-78: The current existence check looks for literal "slug:
'${SLUG}'" in models.ts but models.ts now builds entries by mapping over
generated-models.json, so change the check to look directly in
generated-models.json (or parse it) for the slug; update any logic that decides
add vs update/remove to inspect generated-models.json (e.g. search for the JSON
key "slug" matching SLUG or use jq/node to parse and check for an object with
slug === SLUG) instead of grepping models.ts so the add→update switch and
remove/update commands correctly detect existing models.
- Around line 141-155: Update the PR recipe's git and commit steps that
currently stage and mention models.ts and translations.ts: replace the git add
and commit descriptions that reference "models.ts" and "translations.ts" with
the current source-of-truth files "generated-models.json" and
"model-metadata.ts", and remove any mention of per-model translation keys
(translations.ts) from the commit message/body; ensure the branch name and PR
title still reference MODEL-SLUG but the bullet list under "Changes" reflects
adding/updating generated-models.json and model-metadata.ts (and note that zh-CN
translation is no longer required per-model).
In @.github/workflows/model-page-discovery.yaml:
- Around line 63-74: The EXISTING_SLUGS extraction currently reads the source
text of models.ts and fails because slugs are generated from
generated-models.json; update the node snippet that sets EXISTING_SLUGS to read
and parse the JSON file (generated-models.json) and extract the slug fields
(e.g., map over parsedModels.map(m => m.slug)) instead of regexing models.ts,
preserving the same JSON-printed fallback (echo '[]') and error suppression so
the workflow still yields a JSON array of slugs when the file is present.
In `@apps/website/scripts/generate-models.ts`:
- Around line 187-193: The try/catch around reading/parsing templates in
generate-models.ts (where readFileSync(filePath), JSON.parse(raw), and
extractModels(data, file, models) are used) currently swallows errors and only
writes a warning; instead abort the generation on any read/parse/extract
failure: catch the error, log a descriptive error to stderr (including the
filename `file` and the actual error message/stack), and exit with a non-zero
status (e.g., process.exit(1)) so the process fails fast and prevents writing a
partial registry.
In `@apps/website/src/config/models.ts`:
- Around line 34-58: The code sets a hardcoded TODAY constant and assigns
publishedDate/modifiedDate for every model, causing stale identical timestamps;
update the models construction in the models export to stop using TODAY—either
populate publishedDate and modifiedDate from existing inputs (e.g., prefer
fields on generatedModels or modelMetadata for each m.slug) or omit
publishedDate/modifiedDate entirely until accurate values are available; remove
the TODAY constant and adjust the mapping that builds models (referencing
models, generatedModels, modelMetadata, publishedDate, modifiedDate) accordingly
so each entry uses real metadata or has no date fields.
---
Duplicate comments:
In @.github/workflows/model-page-discovery.yaml:
- Around line 94-127: The "Open GitHub issue for new models" step currently
creates a new issue every run; add a precheck that queries existing open
discovery issues and skips creating a new one if any match. Before running gh
issue create, run a check (e.g., using gh issue list or the GitHub Search API)
for open issues with the same title/label/marker used by this workflow; if the
check returns a match, exit the step without calling gh issue create. Use the
existing env vars (NEW_COUNT, NEW_SLUGS) and the step name "Open GitHub issue
for new models" to locate and update this step. Ensure the check runs in the
same shell context and gates the gh issue create invocation so no duplicate
issue is opened while an unresolved discovery issue exists.
---
Nitpick comments:
In `@apps/website/scripts/generate-models.ts`:
- Around line 5-7: The TEMPLATES_DIR constant currently always resolves a
hard-coded relative path; change its initialization to first check
process.env.WORKFLOW_TEMPLATES_PATH and use that value (as an absolute path)
when present, otherwise fall back to the existing fileURLToPath(new
URL('../../../../workflow_templates/templates', import.meta.url)) behavior;
update the declaration of TEMPLATES_DIR so code that uses TEMPLATES_DIR (the
constant) will honor the WORKFLOW_TEMPLATES_PATH env var set by CI or local
overrides.
In `@apps/website/src/pages/p/supported-models/index.astro`:
- Around line 9-23: The directory-to-label map defined as dirLabel in
index.astro is duplicated in ModelHeroSection.vue; extract this mapping into a
shared helper (e.g., a new exported constant or function) and replace the local
maps in both apps/website/src/pages/p/supported-models/index.astro (dirLabel)
and the ModelHeroSection component with imports from that helper so both use the
single source of truth for labels like 'partner_nodes' and 'diffusion_models'.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 906acb13-0ee9-4f05-8467-db42f39b564c
📒 Files selected for processing (11)
.claude/skills/add-model-page/SKILL.md.github/workflows/model-page-discovery.yamlapps/website/package.jsonapps/website/scripts/generate-models.tsapps/website/src/components/models/ModelHeroSection.vueapps/website/src/config/generated-models.jsonapps/website/src/config/model-metadata.tsapps/website/src/config/models.tsapps/website/src/i18n/translations.tsapps/website/src/pages/p/supported-models/[slug].astroapps/website/src/pages/p/supported-models/index.astro
✅ Files skipped from review due to trivial changes (4)
- apps/website/package.json
- apps/website/src/config/model-metadata.ts
- apps/website/src/config/generated-models.json
- apps/website/src/i18n/translations.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/website/src/pages/p/supported-models/[slug].astro
There was a problem hiding this comment.
♻️ Duplicate comments (1)
apps/website/scripts/generate-models.ts (1)
187-193:⚠️ Potential issue | 🟠 Major | ⚡ Quick winFail fast on template parse/read errors instead of continuing with partial output.
Line 191 swallows the exception and still writes a potentially incomplete registry with exit code 0. This can silently drop models and skew discovery results.
Suggested fix
for (const file of files) { const filePath = join(TEMPLATES_DIR, file) try { const raw = readFileSync(filePath, 'utf8') const data: unknown = JSON.parse(raw) extractModels(data, file, models) - } catch { - process.stderr.write(`Warning: failed to parse ${file}\n`) + } catch (error) { + throw new Error( + `Failed to parse ${file}: ${ + error instanceof Error ? error.message : String(error) + }` + ) } }As per coding guidelines, "Implement proper error handling in all code".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/website/scripts/generate-models.ts` around lines 187 - 193, The try/catch around reading/parsing templates (readFileSync, JSON.parse) and calling extractModels(file, models) currently swallows errors and continues, producing partial output; change it to fail fast by catching the error, log a clear error via process.stderr.write including the error.message and file name, and then exit non-zero (e.g., process.exit(1)) or rethrow the error so the process stops instead of writing an incomplete models registry. Ensure the catch references the thrown error (e) and includes the file variable and e.message when reporting.
🧹 Nitpick comments (2)
apps/website/scripts/generate-models.ts (2)
254-263: ⚡ Quick winAdd a duplicate-slug guard before writing output.
If two distinct model names normalize to the same slug, the generated data will contain route-key collisions and downstream model pages can become ambiguous.
Suggested fix
const combined = [...apiOutput, ...output] + + const seen = new Set<string>() + const duplicateSlugs = new Set<string>() + for (const model of combined) { + if (seen.has(model.slug)) duplicateSlugs.add(model.slug) + seen.add(model.slug) + } + if (duplicateSlugs.size > 0) { + throw new Error( + `Duplicate slug(s) detected: ${[...duplicateSlugs].join(', ')}` + ) + } const defaultOut = join( fileURLToPath(new URL('.', import.meta.url)), '../src/config/generated-models.json' )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/website/scripts/generate-models.ts` around lines 254 - 263, Before calling writeFileSync(outputArg, json, 'utf8'), compute the slug for each entry in combined (using your existing slug/normalization function or a new helper like normalizeSlug(model.name)) and check for duplicate slugs; if any duplicates are found, throw or log an error and exit (e.g., process.exit(1)) so you never write ambiguous output. Implement the check right after building combined and before JSON.stringify (referencing combined, json, outputArg, and writeFileSync) and include the conflicting model names/slugs in the error message to aid debugging.
183-200: ⚡ Quick winMake generation deterministic for equal-count ties.
Line 183 relies on unsorted
readdirSyncorder, and Lines 198-200 / 214-216 sort only by template count. When counts tie, ordering (and thereforecanonicalSlugchoice) can vary across environments.Suggested fix
- const files = readdirSync(TEMPLATES_DIR).filter((f) => f.endsWith('.json')) + const files = readdirSync(TEMPLATES_DIR) + .filter((f) => f.endsWith('.json')) + .sort((a, b) => a.localeCompare(b)) - const sorted = [...models.entries()].sort( - ([, a], [, b]) => b.templates.size - a.templates.size - ) + const sorted = [...models.entries()].sort(([nameA, a], [nameB, b]) => { + const byCount = b.templates.size - a.templates.size + return byCount !== 0 ? byCount : nameA.localeCompare(nameB) + }) - const membersSorted = [...members].sort( - ([, a], [, b]) => b.templates.size - a.templates.size - ) + const membersSorted = [...members].sort( + ([nameA, a], [nameB, b]) => { + const byCount = b.templates.size - a.templates.size + return byCount !== 0 ? byCount : nameA.localeCompare(nameB) + } + )Also applies to: 214-216
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/website/scripts/generate-models.ts` around lines 183 - 200, The directory read and subsequent sorts are non-deterministic for ties: ensure determinism by sorting the files array immediately after readdirSync(TEMPLATES_DIR) (e.g., sort by filename) so extractModels/extractApiModels always process in a stable order, and update the later sort that creates sorted (and the other sort at lines 214-216) to include a deterministic tiebreaker (e.g., secondary comparison by model slug/name or filename) so when b.templates.size === a.templates.size the order (and chosen canonicalSlug) is consistent across runs; update references: files, TEMPLATES_DIR, extractModels, extractApiModels, models, and sorted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@apps/website/scripts/generate-models.ts`:
- Around line 187-193: The try/catch around reading/parsing templates
(readFileSync, JSON.parse) and calling extractModels(file, models) currently
swallows errors and continues, producing partial output; change it to fail fast
by catching the error, log a clear error via process.stderr.write including the
error.message and file name, and then exit non-zero (e.g., process.exit(1)) or
rethrow the error so the process stops instead of writing an incomplete models
registry. Ensure the catch references the thrown error (e) and includes the file
variable and e.message when reporting.
---
Nitpick comments:
In `@apps/website/scripts/generate-models.ts`:
- Around line 254-263: Before calling writeFileSync(outputArg, json, 'utf8'),
compute the slug for each entry in combined (using your existing
slug/normalization function or a new helper like normalizeSlug(model.name)) and
check for duplicate slugs; if any duplicates are found, throw or log an error
and exit (e.g., process.exit(1)) so you never write ambiguous output. Implement
the check right after building combined and before JSON.stringify (referencing
combined, json, outputArg, and writeFileSync) and include the conflicting model
names/slugs in the error message to aid debugging.
- Around line 183-200: The directory read and subsequent sorts are
non-deterministic for ties: ensure determinism by sorting the files array
immediately after readdirSync(TEMPLATES_DIR) (e.g., sort by filename) so
extractModels/extractApiModels always process in a stable order, and update the
later sort that creates sorted (and the other sort at lines 214-216) to include
a deterministic tiebreaker (e.g., secondary comparison by model slug/name or
filename) so when b.templates.size === a.templates.size the order (and chosen
canonicalSlug) is consistent across runs; update references: files,
TEMPLATES_DIR, extractModels, extractApiModels, models, and sorted.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 26a8aedd-a4b8-455a-9475-c22e97b57c2a
📒 Files selected for processing (4)
.claude/skills/add-model-page/SKILL.mdapps/website/scripts/generate-models.tsapps/website/src/config/generated-models.jsonapps/website/src/config/model-metadata.ts
✅ Files skipped from review due to trivial changes (2)
- apps/website/src/config/generated-models.json
- apps/website/src/config/model-metadata.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- .claude/skills/add-model-page/SKILL.md
…ations
Architecture:
- Replace 1585-line hand-coded models.ts with generated-models.json (207 models,
auto-generated by running pnpm generate:models) + 40-line model-metadata.ts for
editorial overrides (docsUrl, blogUrl, featured) + slim 65-line models.ts that
merges both. No more manually maintained data file.
- Remove 830 per-model translation keys from translations.ts — displayName is now
a plain string from the generator, not a TranslationKey
- models.ts no longer imports TranslationKey; displayName/description are strings
- Add generate:models script to website package.json
Design token fixes (pattern-compliance violations):
- Replace text-[var(--color-primary-comfy-yellow)] with text-primary-comfy-yellow
- Replace text-white/70 with text-primary-comfy-canvas/70 (matches existing components)
- Replace invented --color-accent/--color-text-secondary with real tokens
- Fix h1 text from text-white to text-primary-comfy-canvas (matches HeroSection pattern)
i18n fixes:
- Add models.hero.tutorialCta, models.hero.blogLink, models.whatIs.heading,
models.whatIs.tutorialLink keys
- Use t() for all button labels and UI strings in ModelHeroSection.vue
- partner_nodes added to dirLabel in index.astro
Other:
- H1 now reads "{displayName} in ComfyUI" (FE-421 spec)
- Remove description prop from hero (redundant with What-is section)
- Fix GH Actions discovery: m.slug field now matches generator output
- Update add-model-page skill to document new 3-file architecture
- knip.config.ts: add Astro pages/layouts as entry points for website workspace so exports used only by Astro pages are correctly traced - models.ts: remove unused exports getFeaturedModels, getModelsByDirectory - routes.ts: remove unused modelDetail export - ModelHeroSection: variant="primary" -> "solid" (only valid variants: solid, outline, outline-dark) - SKILL.md: update description, Phase 3 grep, Phase 6 git add to match 3-file architecture Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ModelDirectory and Model were exported but not imported by any consumer. Make them module-private to fix knip unused-export check. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adding Astro pages as entry points surfaced pre-existing unused dep warnings (@comfyorg/design-system, tailwindcss) that are unrelated to this PR. The type export fix (non-export ModelDirectory/Model) is sufficient to resolve the original knip failures. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
||
| // Maps api_*.json filename prefix to a canonical display name and slug. | ||
| // Add entries here as new partner integrations land in workflow_templates. | ||
| const API_PROVIDER_MAP: Record<string, { name: string; slug: string }> = { |
There was a problem hiding this comment.
Question: why don't we get models through api https://cloud.comfy.org/api/hub/labels?type=model
There was a problem hiding this comment.
The discovery workflow (model-page-discovery.yaml) already uses the hub API to find new slugs — it runs curl https://comfy.org/api/hub/labels?type=model and opens a PR if any new slugs are missing from generated-models.json. The generator script (generate-models.ts) reads local workflow_templates files because that's the only place we can extract workflowCount, directory, and huggingFaceUrl per model — data not available from the labels API. The hub API is also used at generate time for thumbnails (/api/hub/workflows?tag={slug}&limit=1).
…dels.ts models.ts is now a 65-line merger file with no slug strings. The regex match against models.ts would always return empty [], making every model appear new. Read from generated-models.json instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…orkflow
generate-models.ts:
- Add thumbnailUrl field to OutputModel
- Fetch representative thumbnail from /api/hub/workflows?tag={slug} at generate time
- Set SKIP_THUMBNAILS=1 to skip network calls for offline/CI use
- Fix error handling: throw on parse failure instead of warn-and-continue
- make run() async; top-level error handler exits with code 1
models.ts:
- Add thumbnailUrl to Model interface
- Remove hardcoded publishedDate/modifiedDate (unreliable for 207 models)
[slug].astro:
- Pass model.thumbnailUrl as ogImage to BaseLayout (falls back to default)
- Remove article:published_time/modified_time meta tags (dates were hardcoded)
- Remove datePublished/dateModified from SoftwareApplication JSON-LD
model-page-discovery.yaml:
- Replace workflow_templates clone + pnpm generator with hub API call
(curl /api/hub/labels?type=model — no repo clone, no Node setup needed)
- Use sparse-checkout for generated-models.json only (no full clone)
- Add duplicate-issue guard: skip creation if open discovery issue exists
- Fix path inconsistency: single checkout at repo root, no working-directory hops
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Why is there so few? shouldn't this be easy to get? In Comfy-Org/workflow_templates in the the index.json we have tutorialUrl fields. we can also just search Comfy-Org/docs repo and also search blog.comfy.org.
| // Wave 1 — partner nodes (FE-421 priority) | ||
| 'nano-banana': { | ||
| docsUrl: | ||
| 'https://docs.comfy.org/tutorials/partner-nodes/google/nano-banana-pro', | ||
| featured: true | ||
| }, | ||
| 'kling-ai': { | ||
| docsUrl: | ||
| 'https://docs.comfy.org/tutorials/partner-nodes/kling/kling-motion-control', | ||
| featured: true | ||
| }, | ||
| 'meshy-ai': { | ||
| docsUrl: 'https://docs.comfy.org/tutorials/partner-nodes/meshy/meshy-6', | ||
| featured: true | ||
| }, | ||
| // Wave 1 — local models | ||
| 'wan-2-2': { | ||
| docsUrl: 'https://docs.comfy.org/tutorials/video/wan/wan2_2', | ||
| featured: true | ||
| }, | ||
| 'wan-2-1': { | ||
| docsUrl: 'https://docs.comfy.org/tutorials/video/wan/wan-video', | ||
| featured: true | ||
| }, | ||
| // Wave 2 | ||
| 'flux-1-kontext-dev': { | ||
| docsUrl: 'https://docs.comfy.org/tutorials/flux/flux-1-kontext-dev', | ||
| featured: true | ||
| }, | ||
| 'hunyuan-video': { | ||
| docsUrl: 'https://docs.comfy.org/tutorials/video/hunyuan/hunyuan-video', | ||
| featured: true | ||
| }, | ||
| // Wave 3 |
There was a problem hiding this comment.
Remove these useless comments.
…and metadata generate-models.ts: - Add buildTutorialUrlMap() — reads all locale index*.json files, maps each template's tutorialUrl to the raw model filenames in that template file - Adds docsUrl to OutputModel; populated for 106 of 207 models at generate time model-metadata.ts: - Remove Wave-N section comments - Add docsUrl for openai-dall-e, ltxv-api, wan-api, flux-1-kontext-dev models.ts: - Forward docsUrl from generated JSON into Model (metadata overrides still apply) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolved conflict in translations.ts: keep models UI keys from this branch and payment status page keys added in main. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- model-metadata.ts: add hubSlug field (only where comfy.org/workflows/model/{slug} returns 200)
- models.ts: add hubSlug to Model interface
- ModelHeroSection: use hubSlug for CTA, fall back to /workflows if absent
- [slug].astro: pass hubSlug, expand FAQ to 4 questions, improve pageDescription
- SKILL.md: document hubSlug field with verification note
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Replace broken hub API thumbnail fetch with local webp lookup. Each template JSON has a companion -1.webp; we use the first template's webp per model, served via raw.githubusercontent.com. 178/207 models now have thumbnails.
…date - [slug].astro: directory-aware whatIsDescription used in body + FAQ JSON-LD - models.ts: runtime invariant validation for duplicate slugs and invalid canonicalSlug refs - SKILL.md: note that thumbnails come from local webp files, no network needed
Updates since last reviewThumbnails now workingReplaced the broken hub API thumbnail fetch with direct webp files from Hub model page URLs validated
Richer per-model SEO copyThe "What is {name}" body now generates directory-aware descriptions (e.g. "flux1-dev is a diffusion model that generates images or video from text and image prompts...") instead of repeating the meta description. The FAQ JSON-LD also uses this richer copy. Models registry validation
Review comments addressed
|
Summary
/p/supported-models/[slug]for 207 models auto-generated fromworkflow_templates(180 local + 27 partner nodes)generated-models.json(auto-generated, checked in) +model-metadata.ts(editorial overrides) +models.ts(65-line merger)SoftwareApplication+BreadcrumbList+FAQPage(targeting AI Overviews / People Also Ask)directory: 'partner_nodes'hides Download button, shows VIEW TUTORIALgenerate-models.ts: walksworkflow_templatesfor local models +API_PROVIDER_MAPfor 30+ partner integrations (Kling, Meshy, Luma, Runway, Stability AI, ByteDance, Google, etc.)workflow_templatesbut not ingenerated-models.jsonadd-model-pageClaude skill for Slack-driven model page PRsFiles changed
apps/website/src/config/generated-models.jsonapps/website/src/config/model-metadata.tsapps/website/src/config/models.tsmodels+getModelBySlugapps/website/scripts/generate-models.tspnpm generate:modelsapps/website/src/i18n/translations.tsapps/website/src/pages/p/supported-models/[slug].astroapps/website/src/pages/p/supported-models/index.astroapps/website/src/components/models/ModelHeroSection.vue.github/workflows/model-page-discovery.yaml.claude/skills/add-model-page/SKILL.mdTest plan
pnpm buildpasses inapps/website/p/supported-modelsindex renders 207 model cards/p/supported-models/kling-aishows Partner Node eyebrow, no Download button, VIEW TUTORIAL CTA/p/supported-models/flux-1-devshows Diffusion Model eyebrow, Download + Tutorial buttons/p/supported-models/umt5-xxl-fp8-e4m3fn-scaledredirects 301 toumt5-xxl-fp16(canonicalSlug)Fixes FE-421