-
Notifications
You must be signed in to change notification settings - Fork 561
feat(website): SEO model pages — 207 models, FAQ JSON-LD, partner node support #11892
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
christian-byrne
wants to merge
23
commits into
main
Choose a base branch
from
glary/seo-model-pages
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
9bc0d2f
feat(website): add SEO model pages infrastructure
christian-byrne 8fe2dee
fix(website): address review comments on model pages PR
christian-byrne 8db4e46
[automated] Apply ESLint and Oxfmt fixes
actions-user 213870a
refactor(website): fix model pages architecture and design token viol…
christian-byrne a94c75c
[automated] Apply ESLint and Oxfmt fixes
actions-user d6704a9
refactor(website): fix model pages architecture and design token viol…
christian-byrne 2143f82
fix(website): fix knip violations, invalid variant, and update SKILL.md
christian-byrne 32a3161
[automated] Apply ESLint and Oxfmt fixes
actions-user a492d32
fix(website): remove unused type exports from models.ts
christian-byrne c38736c
revert(knip): revert website entry point expansion
christian-byrne c52e398
fix(discovery): read existing slugs from generated-models.json not mo…
christian-byrne 7e11c5a
feat(website): add model thumbnails via hub API; overhaul discovery w…
christian-byrne 5acb517
[automated] Apply ESLint and Oxfmt fixes
actions-user 6c9947c
fix(models): link primary CTA to /workflows/model/{slug} on comfy.org
christian-byrne ef8ec75
fix(skill): lowercase description and heading body
christian-byrne 4467e67
feat(models): auto-extract docsUrl from workflow_templates index; exp…
christian-byrne 9832571
chore: merge main into glary/seo-model-pages
christian-byrne 448953d
[automated] Apply ESLint and Oxfmt fixes
actions-user 924374f
feat(models): add hubSlug for verified hub pages; expand FAQ copy
christian-byrne 3ce00a8
feat(models): populate thumbnails from workflow_templates webp files
christian-byrne 2a81c2b
[automated] Apply ESLint and Oxfmt fixes
actions-user 037ae5a
feat(models): richer per-type copy, slug validation, SKILL.md docs up…
christian-byrne b0986f1
[automated] Apply ESLint and Oxfmt fixes
actions-user File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,173 @@ | ||
| --- | ||
| name: add-model-page | ||
| 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 | ||
|
|
||
| add, update, or remove model pages in the ComfyUI website. | ||
|
|
||
| ## Trigger phrases | ||
|
|
||
| - `Add a model page for <model-name>` | ||
| - `Update the model page for <model-name>` | ||
| - `Remove <model-name> from model pages` | ||
|
|
||
| ## Phase 1 — Parse the request | ||
|
|
||
| Extract: | ||
|
|
||
| - **action**: `add` | `update` | `remove` | ||
| - **model-name**: raw string (e.g. `flux1-schnell`, `flux1_dev.safetensors`) | ||
|
|
||
| Normalize to a slug: lowercase, replace `_` and `.` with `-`, strip file extensions. | ||
| Example: `flux1_dev.safetensors` → `flux1-dev` | ||
|
|
||
| ## Architecture overview | ||
|
|
||
| Models come from two sources merged at build time: | ||
|
|
||
| | File | Purpose | | ||
| | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | ||
| | `apps/website/src/config/generated-models.json` | Auto-generated from workflow_templates (slug, name, directory, huggingFaceUrl, workflowCount, displayName, thumbnailUrl, docsUrl) | | ||
| | `apps/website/src/config/model-metadata.ts` | Hand-curated overrides (docsUrl, blogUrl, featured) — only add entries that need overrides | | ||
| | `apps/website/src/config/models.ts` | Merges the two above; exports typed `Model[]` | | ||
|
|
||
| To regenerate the JSON from workflow_templates: | ||
|
|
||
| ```bash | ||
| pnpm tsx apps/website/scripts/generate-models.ts | ||
| ``` | ||
|
|
||
| This writes `apps/website/src/config/generated-models.json` directly. | ||
| Thumbnails are populated from local `.webp` files in `workflow_templates/templates/` — no network access needed. | ||
|
|
||
| --- | ||
|
|
||
| ## Phase 2 — Gather model data (ADD / UPDATE) | ||
|
|
||
| Run the generator to get fresh data, then find the model: | ||
|
|
||
| ```bash | ||
| pnpm tsx apps/website/scripts/generate-models.ts | ||
| jq '.[] | select(.slug | contains("MODEL_SLUG"))' \ | ||
| apps/website/src/config/generated-models.json | ||
| ``` | ||
|
|
||
| The JSON fields are: | ||
|
|
||
| - `slug` — URL slug | ||
| - `name` — exact filename or display name for partner nodes | ||
| - `huggingFaceUrl` — download URL (empty for partner nodes) | ||
| - `directory` — `diffusion_models` | `loras` | … | `partner_nodes` | ||
| - `workflowCount` — integer | ||
| - `displayName` — human-readable name | ||
|
|
||
| If no match and it is a known API/partner model, add it to `API_PROVIDER_MAP` in | ||
| `generate-models.ts` and re-run. Otherwise tell the user. | ||
|
|
||
| --- | ||
|
|
||
| ## Phase 3 — Check for existing entry | ||
|
|
||
| ```bash | ||
| jq --arg slug "${SLUG}" '.[] | select(.slug == $slug)' \ | ||
| apps/website/src/config/generated-models.json | ||
| ``` | ||
|
|
||
| - Match found + action is `add` → switch to UPDATE flow automatically | ||
| - No match + action is `update` → stop and tell the user | ||
|
|
||
| --- | ||
|
|
||
| ## Phase 4A — ADD: new partner/API model not in workflow_templates | ||
|
|
||
| For partner nodes (no local file), add an entry to `API_PROVIDER_MAP` in | ||
| `apps/website/scripts/generate-models.ts`: | ||
|
|
||
| ```typescript | ||
| mymodel: { name: 'My Model', slug: 'my-model' }, | ||
| ``` | ||
|
|
||
| Then re-run `pnpm tsx apps/website/scripts/generate-models.ts` — it will appear | ||
| in `generated-models.json` automatically. | ||
|
|
||
| If you also want a `docsUrl`, `blogUrl`, or a link to the hub model page, add an entry to `model-metadata.ts`: | ||
|
|
||
| ```typescript | ||
| 'my-model': { | ||
| docsUrl: 'https://docs.comfy.org/tutorials/...', | ||
| blogUrl: 'https://blog.comfy.org/...', | ||
| hubSlug: 'my-model', // slug at comfy.org/workflows/model/{hubSlug} — only set if the page exists (returns 200) | ||
| featured: true | ||
| } | ||
| ``` | ||
|
|
||
| No changes to `models.ts` or `translations.ts` are needed. | ||
|
|
||
| --- | ||
|
|
||
| ## Phase 4B — UPDATE: edit existing entry | ||
|
|
||
| Only `model-metadata.ts` needs editing for most updates (docsUrl, blogUrl, | ||
| featured). For `displayName` or `directory` changes, edit the entry directly in | ||
| `generated-models.json` (until the next generator run would overwrite it — then | ||
| fix the source in `generate-models.ts`). | ||
|
|
||
| --- | ||
|
|
||
| ## Phase 4C — REMOVE: delete entry | ||
|
|
||
| Remove the entry from `generated-models.json` (or mark it with `canonicalSlug` | ||
| pointing to the replacement). No translation file changes needed. | ||
|
|
||
| --- | ||
|
|
||
| ## Phase 5 — Verify TypeScript | ||
|
|
||
| ```bash | ||
| pnpm typecheck 2>&1 | grep -E "error|warning" | head -20 | ||
| ``` | ||
|
|
||
| Fix any type errors before proceeding. Common issues: | ||
|
|
||
| - `ModelDirectory` type not matching a new `directory` value — add it to the union | ||
| - JSON import shape mismatch — `generated-models.json` must match `OutputModel` | ||
|
|
||
| --- | ||
|
|
||
| ## Phase 6 — Create PR | ||
|
|
||
| ```bash | ||
| BRANCH="add-model-page-MODEL-SLUG" # or update- / remove- | ||
| git checkout -b $BRANCH | ||
| git add apps/website/src/config/generated-models.json \ | ||
| apps/website/scripts/generate-models.ts \ | ||
| apps/website/src/config/model-metadata.ts | ||
| git commit -m "feat(models): add model page for MODEL-SLUG" | ||
| git push -u origin $BRANCH | ||
| gh pr create \ | ||
| --title "Add model page: MODEL-SLUG" \ | ||
| --body "$(cat <<'EOF' | ||
| Adds a new model page entry for MODEL-SLUG. | ||
|
|
||
| ## Changes | ||
| - `generated-models.json`: regenerated with new entry (workflowCount N, directory DIRECTORY) | ||
| - `model-metadata.ts`: editorial overrides (docsUrl, featured) if needed | ||
| EOF | ||
| )" | ||
| ``` | ||
|
|
||
| For UPDATE use branch `update-model-page-MODEL-SLUG`. | ||
| For REMOVE use `remove-model-page-MODEL-SLUG`. | ||
|
|
||
| --- | ||
|
|
||
| ## Error states | ||
|
|
||
| | Situation | Response | | ||
| | ------------------------------- | ---------------------------------------------------------------- | | ||
| | Model not in workflow templates | Ask user to verify spelling or add it manually as a partner node | | ||
| | Slug already exists (add) | Switch to update flow automatically | | ||
| | Slug not found (update/remove) | Stop and ask user to confirm | | ||
| | Typecheck fails | Fix the error before pushing | | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| name: Model Page Discovery | ||
|
|
||
| on: | ||
| schedule: | ||
| - cron: '0 9 * * 1' | ||
| workflow_dispatch: | ||
|
|
||
| jobs: | ||
| discover: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| issues: write | ||
|
|
||
| steps: | ||
| - name: Fetch model labels from hub API | ||
| id: hub | ||
| shell: bash | ||
| run: | | ||
| set -euo pipefail | ||
| curl -fsSL 'https://comfy.org/api/hub/labels?type=model' -o hub-labels.json | ||
| echo "Fetched $(jq '.labels | length' hub-labels.json) model labels from hub" | ||
|
|
||
| - name: Checkout ComfyUI_frontend | ||
| uses: actions/checkout@v6 | ||
| with: | ||
| sparse-checkout: apps/website/src/config/generated-models.json | ||
|
|
||
| - name: Compare against existing models | ||
| id: compare | ||
| shell: bash | ||
| run: | | ||
| set -euo pipefail | ||
|
|
||
| HUB_SLUGS=$(jq -r '[.labels[].name]' hub-labels.json) | ||
|
|
||
| EXISTING_SLUGS=$(node -e " | ||
| const fs = require('fs'); | ||
| const models = JSON.parse( | ||
| fs.readFileSync( | ||
| 'apps/website/src/config/generated-models.json', | ||
| 'utf8' | ||
| ) | ||
| ); | ||
| console.log(JSON.stringify(models.map(m => m.slug))); | ||
| " 2>/dev/null || echo '[]') | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| ADDED_SLUGS=$(node -e " | ||
| const hub = $HUB_SLUGS; | ||
| const existing = new Set($EXISTING_SLUGS); | ||
| console.log(JSON.stringify(hub.filter(s => !existing.has(s)))); | ||
| ") | ||
|
|
||
| COUNT=$(node -e "console.log($ADDED_SLUGS.length)") | ||
| echo "new_count=$COUNT" >> \$GITHUB_OUTPUT | ||
| echo "new_slugs=$ADDED_SLUGS" >> \$GITHUB_OUTPUT | ||
|
|
||
| if [ "\$COUNT" -eq 0 ]; then | ||
| echo "No new models found." | ||
| else | ||
| echo "Found \$COUNT new model(s)" | ||
| fi | ||
|
|
||
| - name: Check for existing open discovery issue | ||
| id: existing_issue | ||
| if: steps.compare.outputs.new_count != '0' | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| shell: bash | ||
| run: | | ||
| COUNT=$(gh issue list \ | ||
| --repo "$GITHUB_REPOSITORY" \ | ||
| --state open \ | ||
| --search 'in:title "New models detected"' \ | ||
| --json number \ | ||
| --jq 'length') | ||
| echo "open_count=$COUNT" >> $GITHUB_OUTPUT | ||
|
|
||
| - name: Open GitHub issue for new models | ||
| if: | | ||
| steps.compare.outputs.new_count != '0' && | ||
| steps.existing_issue.outputs.open_count == '0' | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| NEW_SLUGS: ${{ steps.compare.outputs.new_slugs }} | ||
| NEW_COUNT: ${{ steps.compare.outputs.new_count }} | ||
| shell: bash | ||
| run: | | ||
| SLUG_LIST=$(node -e " | ||
| const slugs = $NEW_SLUGS; | ||
| console.log(slugs.map(s => '- \`' + s + '\`').join('\n')); | ||
| ") | ||
|
|
||
| gh issue create \ | ||
| --repo "$GITHUB_REPOSITORY" \ | ||
| --title "New models detected — add to model pages" \ | ||
| --body "## $NEW_COUNT new model(s) found in hub | ||
|
|
||
| The weekly model discovery scan found model labels on the hub not yet in | ||
| \`apps/website/src/config/generated-models.json\`. | ||
|
|
||
| ### New slugs ($NEW_COUNT) | ||
|
|
||
| $SLUG_LIST | ||
|
|
||
| ### Next steps | ||
|
|
||
| 1. Review which of these warrant an SEO model page | ||
| 2. For local models: run \`SKIP_THUMBNAILS=1 pnpm generate:models\` and commit the result | ||
| 3. For partner/API models: add to \`API_PROVIDER_MAP\` in \`generate-models.ts\`, regenerate, commit | ||
|
|
||
| --- | ||
| *Generated by the [model-page-discovery workflow](https://github.com/$GITHUB_REPOSITORY/actions/workflows/model-page-discovery.yaml)*" | ||
|
|
||
| - name: Skip — open issue already exists | ||
| if: | | ||
| steps.compare.outputs.new_count != '0' && | ||
| steps.existing_issue.outputs.open_count != '0' | ||
| run: echo "An open discovery issue already exists — skipping creation." | ||
|
|
||
| - name: No new models found | ||
| if: steps.compare.outputs.new_count == '0' | ||
| run: echo "No new models found — nothing to do." | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.