Skip to content

Commit bbd0a6b

Browse files
committed
feat: migrate workflow template site as apps/hub
Migrate workflow_templates/site into the frontend monorepo as apps/hub so the hub can use @comfyorg/design-system and shared packages. Changes to existing files: - pnpm-workspace.yaml: add @astrojs/sitemap, @astrojs/vercel, lucide-vue-next - eslint.config.ts: add hub ignores and i18n/import rule overrides - .oxlintrc.json: add hub scripts to ignore patterns - knip.config.ts: add hub workspace config apps/hub adaptations from source: - Replace local cn() with @comfyorg/tailwind-utils (19 files) - Integrate @comfyorg/design-system/css/base.css in global.css - Make TEMPLATES_DIR configurable via HUB_TEMPLATES_DIR env var - Add HUB_SKIP_SYNC flag for builds without template data - Remove Vite 8-incompatible rollupOptions.output.manualChunks - Fix stylelint violations (modern color notation, number precision) - Gitignore generated content (thumbnails, synced templates, AI cache)
1 parent 6c1bf7a commit bbd0a6b

245 files changed

Lines changed: 24185 additions & 32 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.oxlintrc.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
"playwright-report/*",
1313
"src/extensions/core/*",
1414
"src/scripts/*",
15+
"apps/hub/scripts/**/*",
16+
"apps/hub/src/scripts/*",
1517
"src/types/generatedManagerTypes.ts",
1618
"src/types/vue-shim.d.ts",
1719
"test-results/*",

apps/hub/.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
dist/
2+
.astro/
3+
.content-cache/
4+
src/content/templates/
5+
public/workflows/thumbnails/
6+
public/workflows/avatars/
7+
public/previews/
8+
public/search-index.json
9+
knowledge/tutorials/

apps/hub/astro.config.mjs

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
import { defineConfig } from 'astro/config';
2+
import sitemap from '@astrojs/sitemap';
3+
import vercel from '@astrojs/vercel';
4+
import tailwindcss from '@tailwindcss/vite';
5+
import fs from 'node:fs';
6+
import path from 'node:path';
7+
import os from 'node:os';
8+
9+
import vue from '@astrojs/vue';
10+
11+
// Build template date lookup at config time
12+
const templatesDir = path.join(process.cwd(), 'src/content/templates');
13+
const templateDates = new Map();
14+
15+
if (fs.existsSync(templatesDir)) {
16+
const files = fs.readdirSync(templatesDir).filter((f) => f.endsWith('.json'));
17+
for (const file of files) {
18+
try {
19+
const content = JSON.parse(fs.readFileSync(path.join(templatesDir, file), 'utf-8'));
20+
if (content.name && content.date) {
21+
templateDates.set(content.name, content.date);
22+
}
23+
} catch {
24+
// Skip invalid JSON files
25+
}
26+
}
27+
}
28+
29+
// Build timestamp used as lastmod fallback for pages without a specific date
30+
const buildDate = new Date().toISOString();
31+
32+
// Supported locales (matches src/i18n/config.ts)
33+
const locales = ['en', 'zh', 'zh-TW', 'ja', 'ko', 'es', 'fr', 'ru', 'tr', 'ar', 'pt-BR'];
34+
const nonDefaultLocales = locales.filter((l) => l !== 'en');
35+
36+
// Custom sitemap pages for ISR routes not discovered at build time
37+
const siteOrigin = (process.env.PUBLIC_SITE_ORIGIN || 'https://www.comfy.org').replace(/\/$/, '');
38+
39+
// Creator profile pages — extract unique usernames from synced templates
40+
const creatorUsernames = new Set();
41+
if (fs.existsSync(templatesDir)) {
42+
const files = fs.readdirSync(templatesDir).filter((f) => f.endsWith('.json'));
43+
for (const file of files) {
44+
try {
45+
const content = JSON.parse(fs.readFileSync(path.join(templatesDir, file), 'utf-8'));
46+
if (content.username) creatorUsernames.add(content.username);
47+
} catch {
48+
// Skip invalid JSON
49+
}
50+
}
51+
}
52+
53+
const creatorPages = [...creatorUsernames].map((u) => `${siteOrigin}/workflows/${u}/`);
54+
const localeCustomPages = nonDefaultLocales.map((locale) =>
55+
`${siteOrigin}/${locale}/workflows/`
56+
);
57+
const customPages = [...creatorPages, ...localeCustomPages];
58+
59+
// https://astro.build/config
60+
export default defineConfig({
61+
site: (process.env.PUBLIC_SITE_ORIGIN || 'https://www.comfy.org').replace(/\/$/, ''),
62+
prefetch: {
63+
prefetchAll: false,
64+
defaultStrategy: 'hover',
65+
},
66+
i18n: {
67+
defaultLocale: 'en',
68+
locales: locales,
69+
routing: {
70+
prefixDefaultLocale: false, // English at root, others prefixed (/zh/, /ja/, etc.)
71+
},
72+
},
73+
integrations: [
74+
sitemap({
75+
// Use custom filename to avoid collision with Framer's /sitemap.xml
76+
filenameBase: 'sitemap-workflows',
77+
// Include Framer's marketing sitemap in the index
78+
customSitemaps: ['https://www.comfy.org/sitemap.xml'],
79+
// Include on-demand locale pages that aren't discovered at build time
80+
customPages: customPages,
81+
serialize(item) {
82+
const url = new URL(item.url);
83+
const pathname = url.pathname;
84+
85+
// Template detail pages: /workflows/{slug}/ or /{locale}/workflows/{slug}/
86+
const templateMatch = pathname.match(
87+
/^(?:\/([a-z]{2}(?:-[A-Z]{2})?))?\/workflows\/([^/]+)\/?$/
88+
);
89+
if (templateMatch) {
90+
const slug = templateMatch[2];
91+
const date = templateDates.get(slug);
92+
item.lastmod = date ? new Date(date).toISOString() : buildDate;
93+
// @ts-expect-error - sitemap types are stricter than actual API
94+
item.changefreq = 'monthly';
95+
item.priority = 0.8;
96+
return item;
97+
}
98+
99+
// Homepage
100+
if (pathname === '/' || pathname === '') {
101+
item.lastmod = buildDate;
102+
// @ts-expect-error - sitemap types are stricter than actual API
103+
item.changefreq = 'daily';
104+
item.priority = 1.0;
105+
return item;
106+
}
107+
108+
// Workflows index (including localized versions)
109+
if (pathname.match(/^(?:\/[a-z]{2}(?:-[A-Z]{2})?)?\/workflows\/?$/)) {
110+
item.lastmod = buildDate;
111+
// @ts-expect-error - sitemap types are stricter than actual API
112+
item.changefreq = 'daily';
113+
item.priority = 0.9;
114+
return item;
115+
}
116+
117+
// Category pages: /workflows/category/{type}/ or /{locale}/workflows/category/{type}/
118+
if (pathname.match(/^(?:\/[a-z]{2}(?:-[A-Z]{2})?)?\/workflows\/category\//)) {
119+
// @ts-expect-error - sitemap types are stricter than actual API
120+
item.changefreq = 'weekly';
121+
item.priority = 0.7;
122+
return item;
123+
}
124+
125+
// Model pages: /workflows/model/{model}/ or /{locale}/workflows/model/{model}/
126+
if (pathname.match(/^(?:\/[a-z]{2}(?:-[A-Z]{2})?)?\/workflows\/model\//)) {
127+
// @ts-expect-error - sitemap types are stricter than actual API
128+
item.changefreq = 'weekly';
129+
item.priority = 0.6;
130+
return item;
131+
}
132+
133+
// Tag pages: /workflows/tag/{tag}/ or /{locale}/workflows/tag/{tag}/
134+
if (pathname.match(/^(?:\/[a-z]{2}(?:-[A-Z]{2})?)?\/workflows\/tag\//)) {
135+
// @ts-expect-error - sitemap types are stricter than actual API
136+
item.changefreq = 'weekly';
137+
item.priority = 0.6;
138+
return item;
139+
}
140+
141+
// Default for other pages
142+
// @ts-expect-error - sitemap types are stricter than actual API
143+
item.changefreq = 'weekly';
144+
item.priority = 0.5;
145+
return item;
146+
},
147+
// Exclude OG image routes and legacy redirect pages from sitemap.
148+
// Legacy redirects are /workflows/{slug}/ without a 12-char hex share_id suffix.
149+
// Canonical detail pages are /workflows/{slug}-{shareId}/ (shareId = 12 hex chars).
150+
filter: (page) => {
151+
if (page.includes('/workflows/og/') || page.includes('/workflows/og.png')) return false;
152+
// Check if this is a workflow detail path (not category/tag/model/creators)
153+
const match = page.match(/\/workflows\/([^/]+)\/$/);
154+
if (match) {
155+
const segment = match[1];
156+
// Skip known sub-paths
157+
if (['category', 'tag', 'model', 'creators'].some((p) => page.includes(`/workflows/${p}/`))) return true;
158+
// Include if it has a share_id suffix (12 hex chars after last hyphen)
159+
const lastHyphen = segment.lastIndexOf('-');
160+
if (lastHyphen === -1) return false; // No hyphen = legacy redirect
161+
const candidate = segment.slice(lastHyphen + 1);
162+
if (candidate.length === 12 && /^[0-9a-f]+$/.test(candidate)) return true;
163+
return false; // Has hyphen but not a valid share_id = legacy redirect
164+
}
165+
return true;
166+
},
167+
}),
168+
vue(),
169+
],
170+
output: 'static',
171+
adapter: vercel({
172+
webAnalytics: { enabled: true },
173+
skewProtection: true,
174+
}),
175+
176+
// Build performance optimizations
177+
build: {
178+
// Increase concurrency for faster builds on multi-core systems
179+
concurrency: Math.max(1, os.cpus().length),
180+
// Inline small stylesheets automatically
181+
inlineStylesheets: 'auto',
182+
},
183+
184+
// HTML compression
185+
compressHTML: true,
186+
187+
// Image optimization settings
188+
image: {
189+
service: {
190+
entrypoint: 'astro/assets/services/sharp',
191+
config: {
192+
// Limit input pixels to prevent memory issues with large images
193+
limitInputPixels: 268402689, // ~16384x16384
194+
},
195+
},
196+
},
197+
198+
// Responsive images for automatic srcset generation (now stable in Astro 5)
199+
// Note: responsiveImages was moved from experimental to stable in Astro 5.x
200+
201+
vite: {
202+
plugins: [tailwindcss()],
203+
build: {
204+
chunkSizeWarningLimit: 1000,
205+
},
206+
optimizeDeps: {
207+
include: ['web-vitals'],
208+
},
209+
css: {
210+
devSourcemap: false,
211+
},
212+
},
213+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# 3D Generation
2+
3+
3D generation creates three-dimensional models — meshes, point clouds, or multi-view images — from text or image inputs. This enables rapid prototyping of 3D assets without manual modeling. In ComfyUI, several approaches exist: image-to-3D (lifting a single photo into a mesh), text-to-3D (generating a 3D object from a description), and multi-view generation (producing consistent views of an object that can be reconstructed into 3D).
4+
5+
## How It Works in ComfyUI
6+
7+
- Key nodes involved: Model-specific loaders (`TripoSR`, `InstantMesh`, `StableZero123`), `LoadImage`, `Save3D` / `Preview3D`, `CRM` nodes
8+
- Typical workflow pattern: Load image → Load 3D model → Run inference → Preview 3D result → Export mesh
9+
10+
## Key Settings
11+
12+
- **Inference steps**: Number of denoising/reconstruction steps. More steps generally improve quality but increase generation time.
13+
- **Elevation angle**: Camera elevation for multi-view generation, controlling the vertical viewing angle of the generated views.
14+
- **Guidance scale**: How closely the model follows the input image or text. Higher values increase fidelity to the input but may reduce diversity.
15+
- **Output format**: Export format for the 3D mesh — OBJ, GLB, and PLY are common options, each suited to different downstream tools.
16+
17+
## Tips
18+
19+
- Clean single-object images on white or simple backgrounds work best for image-to-3D conversion.
20+
- Multi-view approaches (like Zero123) often produce better geometry than single-view methods.
21+
- Post-process generated meshes in Blender for cleanup, retopology, or texturing before production use.
22+
- Start with TripoSR for quick results — it generates meshes in seconds and is a good baseline to compare against other methods.

0 commit comments

Comments
 (0)