Compare commits

...

3 Commits

Author SHA1 Message Date
Michael B
3b8a4edf0c feat(website): clarify API workflow limits as concurrent runs
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-17 21:11:52 -04:00
Michael B
ed1b8e5644 feat(website): add enterprise contact for higher API rate limits
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-17 20:59:19 -04:00
Michael B
ef6e594204 feat(website): add video captions to learning tutorials and pricing API copy
Wires per-locale VTT caption tracks into the learning tutorial dialog so
the CC toggle in VideoPlayer is now functional. Also updates pricing plan
copy to surface "workflows via API" instead of "concurrent API jobs" and
adds a new "Run Workflows via API" included-feature entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-17 18:05:56 -04:00
6 changed files with 106 additions and 37 deletions

View File

@@ -15,7 +15,7 @@ import { t } from '../../i18n/translations'
import type { Locale } from '../../i18n/translations'
import PlayPauseButton from './PlayPauseButton.vue'
type VideoTrack = {
export type VideoTrack = {
src: string
kind: 'subtitles' | 'captions' | 'descriptions'
srclang: string
@@ -35,7 +35,7 @@ const {
locale?: Locale
src?: string
poster?: string
tracks?: VideoTrack[]
tracks?: readonly VideoTrack[]
autoplay?: boolean
loop?: boolean
minimal?: boolean

View File

@@ -64,6 +64,7 @@ onUnmounted(() => {
:locale
:src="tutorial.videoSrc"
:poster="tutorial.poster"
:tracks="tutorial.caption"
autoplay
class="w-full"
/>

View File

@@ -68,7 +68,8 @@ const plans: PricingPlan[] = [
: undefined,
features: [
{ text: 'pricing.plan.standard.feature1' },
{ text: 'pricing.plan.standard.feature2' }
{ text: 'pricing.plan.standard.feature2' },
{ text: 'pricing.plan.standard.feature3' }
]
},
{
@@ -122,11 +123,11 @@ const enterprisePlan = plans.find((p) => p.isEnterprise)!
<!-- Header -->
<div class="mx-auto mb-8 max-w-3xl text-center lg:mb-10">
<h1
class="text-primary-comfy-canvas font-formula text-4xl font-light lg:text-5xl"
class="font-formula text-4xl font-light text-primary-comfy-canvas lg:text-5xl"
>
{{ t('pricing.title', locale) }}
</h1>
<p class="text-primary-comfy-canvas mt-3 text-base">
<p class="mt-3 text-base text-primary-comfy-canvas">
{{ t('pricing.subtitle', locale) }}
</p>
</div>
@@ -156,7 +157,7 @@ const enterprisePlan = plans.find((p) => p.isEnterprise)!
aria-hidden="true"
/>
<span
class="bg-primary-comfy-yellow font-formula-narrow text-primary-comfy-ink flex items-center px-2 text-sm font-bold tracking-wider"
class="bg-primary-comfy-yellow font-formula-narrow flex items-center px-2 text-sm font-bold tracking-wider text-primary-comfy-ink"
>
<span class="ppformula-text-center">
{{ t('pricing.badge.popular', locale) }}
@@ -172,18 +173,18 @@ const enterprisePlan = plans.find((p) => p.isEnterprise)!
</div>
<!-- Summary -->
<p class="text-primary-comfy-canvas px-6 text-sm">
<p class="px-6 text-sm text-primary-comfy-canvas">
{{ t(plan.summaryKey, locale) }}
</p>
<!-- Price -->
<div v-if="plan.priceKey" class="flex items-baseline gap-1 px-6 pt-2">
<span
class="text-primary-comfy-canvas font-formula text-5xl font-light"
class="font-formula text-5xl font-light text-primary-comfy-canvas"
>
{{ t(plan.priceKey, locale) }}
</span>
<span class="text-primary-comfy-canvas text-sm">
<span class="text-sm text-primary-comfy-canvas">
{{ t('pricing.plan.period', locale) }}
</span>
</div>
@@ -192,7 +193,7 @@ const enterprisePlan = plans.find((p) => p.isEnterprise)!
<!-- Credits -->
<p
v-if="plan.creditsKey"
class="text-primary-comfy-canvas px-6 text-sm"
class="px-6 text-sm text-primary-comfy-canvas"
>
{{ t(plan.creditsKey, locale) }}
</p>
@@ -201,7 +202,7 @@ const enterprisePlan = plans.find((p) => p.isEnterprise)!
<!-- Estimate -->
<p
v-if="plan.estimateKey"
class="text-primary-comfy-canvas/80 px-6 text-xs"
class="px-6 text-xs text-primary-comfy-canvas/80"
>
{{ t(plan.estimateKey, locale) }}
</p>
@@ -211,13 +212,13 @@ const enterprisePlan = plans.find((p) => p.isEnterprise)!
<div v-if="plan.features.length" class="px-6 py-3">
<p
v-if="plan.featureIntroKey"
class="text-primary-comfy-canvas mb-2 text-sm font-semibold"
class="mb-2 text-sm font-semibold text-primary-comfy-canvas"
>
{{ t(plan.featureIntroKey, locale) }}
</p>
<p
v-else
class="text-primary-comfy-canvas mb-2 text-sm font-semibold"
class="mb-2 text-sm font-semibold text-primary-comfy-canvas"
aria-hidden="true"
>
&nbsp;
@@ -229,7 +230,7 @@ const enterprisePlan = plans.find((p) => p.isEnterprise)!
class="flex items-start gap-2"
>
<span class="text-primary-comfy-yellow mt-0.5 text-sm"></span>
<span class="text-primary-comfy-canvas text-sm">
<span class="text-sm text-primary-comfy-canvas">
{{ t(feature.text, locale) }}
</span>
</li>
@@ -269,7 +270,7 @@ const enterprisePlan = plans.find((p) => p.isEnterprise)!
aria-hidden="true"
/>
<span
class="bg-primary-comfy-yellow text-primary-comfy-ink flex items-center px-2 text-[10px] font-bold tracking-wider"
class="bg-primary-comfy-yellow flex items-center px-2 text-[10px] font-bold tracking-wider text-primary-comfy-ink"
>
<span class="ppformula-text-center">
{{ t('pricing.badge.popular', locale) }}
@@ -287,13 +288,13 @@ const enterprisePlan = plans.find((p) => p.isEnterprise)!
<!-- Enterprise heading -->
<h2
v-if="plan.isEnterprise"
class="text-primary-comfy-canvas mt-3 text-2xl font-light"
class="mt-3 text-2xl font-light text-primary-comfy-canvas"
>
{{ t('pricing.enterprise.heading', locale) }}
</h2>
<!-- Summary -->
<p class="text-primary-comfy-canvas mt-2 text-sm">
<p class="mt-2 text-sm text-primary-comfy-canvas">
{{ t(plan.summaryKey, locale) }}
</p>
@@ -301,25 +302,25 @@ const enterprisePlan = plans.find((p) => p.isEnterprise)!
<template v-if="plan.priceKey">
<div class="mt-6 flex items-baseline gap-1">
<span
class="text-primary-comfy-canvas font-formula text-5xl font-light"
class="font-formula text-5xl font-light text-primary-comfy-canvas"
>
{{ t(plan.priceKey, locale) }}
</span>
<span class="text-primary-comfy-canvas/55 text-sm">
<span class="text-sm text-primary-comfy-canvas/55">
{{ t('pricing.plan.period', locale) }}
</span>
</div>
<p
v-if="plan.creditsKey"
class="text-primary-comfy-canvas mt-4 text-xs font-medium"
class="mt-4 text-xs font-medium text-primary-comfy-canvas"
>
{{ t(plan.creditsKey, locale) }}
</p>
<p
v-if="plan.estimateKey"
class="text-primary-comfy-canvas mt-2 text-xs"
class="mt-2 text-xs text-primary-comfy-canvas"
>
{{ t(plan.estimateKey, locale) }}
</p>
@@ -368,7 +369,7 @@ const enterprisePlan = plans.find((p) => p.isEnterprise)!
>
<!-- Left side -->
<div
class="bg-primary-comfy-ink rounded-4.5xl flex w-full flex-col items-start justify-between gap-8 p-8"
class="rounded-4.5xl flex w-full flex-col items-start justify-between gap-8 bg-primary-comfy-ink p-8"
>
<div>
<span
@@ -377,11 +378,11 @@ const enterprisePlan = plans.find((p) => p.isEnterprise)!
{{ t(enterprisePlan.labelKey, locale) }}
</span>
<h2
class="text-primary-comfy-canvas mt-3 text-2xl font-light lg:text-3xl"
class="mt-3 text-2xl font-light text-primary-comfy-canvas lg:text-3xl"
>
{{ t('pricing.enterprise.heading', locale) }}
</h2>
<p class="text-primary-comfy-canvas mt-3 text-sm">
<p class="mt-3 text-sm text-primary-comfy-canvas">
{{ t(enterprisePlan.summaryKey, locale) }}
</p>
</div>
@@ -392,7 +393,7 @@ const enterprisePlan = plans.find((p) => p.isEnterprise)!
</div>
<!-- Footnote -->
<p class="text-primary-comfy-canvas/70 mt-12 text-xs">
<p class="mt-12 text-xs text-primary-comfy-canvas/70">
{{ t('pricing.footnote', locale) }}
</p>
</section>

View File

@@ -54,7 +54,11 @@ const features: IncludedFeature[] = [
},
{
titleKey: 'pricing.included.feature11.title',
descriptionKey: 'pricing.included.feature11.description',
descriptionKey: 'pricing.included.feature11.description'
},
{
titleKey: 'pricing.included.feature12.title',
descriptionKey: 'pricing.included.feature12.description',
isComingSoon: true
}
]
@@ -65,10 +69,10 @@ const features: IncludedFeature[] = [
<div class="mx-auto w-full lg:grid lg:grid-cols-[280px_1fr] lg:gap-x-16">
<!-- Heading -->
<div
class="bg-primary-comfy-ink sticky top-20 mb-10 py-2 lg:top-28 lg:mb-0 lg:self-start"
class="sticky top-20 mb-10 bg-primary-comfy-ink py-2 lg:top-28 lg:mb-0 lg:self-start"
>
<h2
class="text-primary-comfy-canvas text-3xl/tight font-light whitespace-pre-line"
class="text-3xl/tight font-light whitespace-pre-line text-primary-comfy-canvas"
>
{{ t('pricing.included.heading', locale) }}
</h2>
@@ -81,7 +85,7 @@ const features: IncludedFeature[] = [
:key="feature.titleKey"
:class="
index < features.length - 1
? 'border-primary-comfy-canvas/15 border-b border-solid'
? 'border-b border-solid border-primary-comfy-canvas/15'
: ''
"
class="py-8 first:pt-0 lg:grid lg:grid-cols-[200px_1fr] lg:gap-x-10"
@@ -99,14 +103,14 @@ const features: IncludedFeature[] = [
v-else
class="text-primary-comfy-yellow mt-0.5 size-4 shrink-0"
/>
<p class="text-primary-comfy-canvas text-sm font-medium">
<p class="text-sm font-medium text-primary-comfy-canvas">
{{ t(feature.titleKey, locale) }}
</p>
</div>
<!-- Description -->
<p
class="text-primary-comfy-canvas/55 mt-3 text-sm/relaxed lg:mt-0"
class="mt-3 text-sm/relaxed text-primary-comfy-canvas/55 lg:mt-0"
v-html="t(feature.descriptionKey, locale)"
/>
</div>

View File

@@ -1,3 +1,4 @@
import type { VideoTrack } from '../components/common/VideoPlayer.vue'
import type { LocalizedText, TranslationKey } from '../i18n/translations'
export interface LearningTutorial {
@@ -7,6 +8,7 @@ export interface LearningTutorial {
videoSrc: string
href?: string
poster?: string
caption?: readonly VideoTrack[]
posterTime?: number
}
@@ -28,6 +30,14 @@ export const learningTutorials: readonly LearningTutorial[] = [
'https://media.comfy.org/website/learning/cleanplate_walkthrough_v03.mp4',
poster:
'https://media.comfy.org/website/learning/cleanplate_walkthrough_v03_thumbnail.jpg',
caption: [
{
src: 'https://media.comfy.org/website/learning/cleanplate_walkthrough_v03_vtt.en.vtt',
kind: 'captions',
srclang: 'en',
label: 'English'
}
],
// href: '#',
tags: [partnerNodesTag, imageToVideoTag]
},
@@ -38,7 +48,15 @@ export const learningTutorials: readonly LearningTutorial[] = [
'https://media.comfy.org/website/learning/deaging_workflow_v03.mp4',
poster:
'https://media.comfy.org/website/learning/deaging_workflow_v03_thumbnail.jpg',
href: 'https://cloud.comfy.org/?share=93f286fbc2c8',
href: 'https://comfy.org/workflows/93f286fbc2c8-93f286fbc2c8/',
caption: [
{
src: 'https://media.comfy.org/website/learning/deaging_workflow_v03_vtt.en.vtt',
kind: 'captions',
srclang: 'en',
label: 'English'
}
],
tags: [partnerNodesTag, imageToVideoTag]
},
{
@@ -49,6 +67,14 @@ export const learningTutorials: readonly LearningTutorial[] = [
poster:
'https://media.comfy.org/website/learning/frame_adjustments_demo_v03_thumbnail.jpg',
href: 'https://cloud.comfy.org/?share=7dca0438edf4',
caption: [
{
src: 'https://media.comfy.org/website/learning/frame_adjustments_demo_v03_vtt.en.vtt',
kind: 'captions',
srclang: 'en',
label: 'English'
}
],
tags: [partnerNodesTag, imageToVideoTag]
},
{
@@ -59,6 +85,14 @@ export const learningTutorials: readonly LearningTutorial[] = [
poster:
'https://media.comfy.org/website/learning/mattes_and_utilities_v03_thumbnail.jpg',
href: 'https://cloud.comfy.org/?share=be0889296f65',
caption: [
{
src: 'https://media.comfy.org/website/learning/mattes_and_utilities_v03_vtt.en.vtt',
kind: 'captions',
srclang: 'en',
label: 'English'
}
],
tags: [partnerNodesTag, imageToVideoTag]
},
{
@@ -69,6 +103,14 @@ export const learningTutorials: readonly LearningTutorial[] = [
poster:
'https://media.comfy.org/website/learning/seedance seedance_demo_comfyui_v03_thumbnail.jpg',
href: 'https://cloud.comfy.org/?share=ef543bd4a773',
caption: [
{
src: 'https://media.comfy.org/website/learning/seedance_demo_comfyui_v03_vtt.en.vtt',
kind: 'captions',
srclang: 'en',
label: 'English'
}
],
tags: [partnerNodesTag, imageToVideoTag]
},
{
@@ -79,6 +121,14 @@ export const learningTutorials: readonly LearningTutorial[] = [
poster:
'https://media.comfy.org/website/learning/skyreplacement_smaller_v06_thumbnail.jpg',
href: 'https://comfy.org/workflows/537cf7f1f745-537cf7f1f745/',
caption: [
{
src: 'https://media.comfy.org/website/learning/skyreplacement_smaller_v06_vtt.en.vtt',
kind: 'captions',
srclang: 'en',
label: 'English'
}
],
tags: [partnerNodesTag, imageToVideoTag]
}
] as const

View File

@@ -1244,6 +1244,10 @@ const translations = {
en: 'Add more credits anytime',
'zh-CN': '可随时增加积分'
},
'pricing.plan.standard.feature3': {
en: 'Run 1 workflow concurrently (via API)',
'zh-CN': '通过 API 并发运行 1 个工作流'
},
'pricing.plan.creator.label': { en: 'CREATOR', 'zh-CN': '创作者版' },
'pricing.plan.creator.summary': {
@@ -1272,8 +1276,8 @@ const translations = {
'zh-CN': '导入你自己的 LoRA'
},
'pricing.plan.creator.feature2': {
en: '3 concurrent API jobs',
'zh-CN': '3 个并发 API 任务'
en: 'Run up to 3 workflows concurrently (via API)',
'zh-CN': '通过 API 最多并发运行 3 个工作流'
},
'pricing.plan.pro.label': { en: 'PRO', 'zh-CN': '专业版' },
@@ -1300,8 +1304,8 @@ const translations = {
'zh-CN': '更长工作流运行时长(最长 1 小时)'
},
'pricing.plan.pro.feature2': {
en: '5 concurrent API jobs',
'zh-CN': '5 个并发 API 任务'
en: 'Run up to 5 workflows concurrently (via API)',
'zh-CN': '通过 API 最多并发运行 5 个工作流'
},
'pricing.enterprise.label': { en: 'ENTERPRISE', 'zh-CN': '企业版' },
@@ -1433,10 +1437,19 @@ const translations = {
'Creator 或 Pro 计划用户可从 CivitAI 或 Huggingface 导入自己的模型和 LoRA打造专属风格。'
},
'pricing.included.feature11.title': {
en: 'Run Workflows via API',
'zh-CN': '通过 API 运行工作流'
},
'pricing.included.feature11.description': {
en: 'Run Comfy workflows programmatically via API, with concurrency limits based on your plan. Perfect for integrating ComfyUI into your applications, automating batch processing, or building production pipelines. For higher rate limits, reach out to <a href="mailto:enterprise@comfy.org" class="text-primary-comfy-yellow underline">enterprise@comfy.org</a>.',
'zh-CN':
'通过 API 以编程方式运行 Comfy 工作流,并发上限由您的计划决定。非常适合将 ComfyUI 集成到您的应用、自动化批量处理或构建生产级流水线。如需更高的速率限制,请联系 <a href="mailto:enterprise@comfy.org" class="text-primary-comfy-yellow underline">enterprise@comfy.org</a>。'
},
'pricing.included.feature12.title': {
en: 'Parallel job execution',
'zh-CN': '并行任务执行'
},
'pricing.included.feature11.description': {
'pricing.included.feature12.description': {
en: 'Run multiple workflows in parallel to speed up your pipeline.',
'zh-CN': '并行运行多个工作流,加速你的流程。'
},