@@ -1,6 +1,6 @@
import React , { useCallback , useEffect , useMemo , useState } from 'react' ;
import { useLocation } from 'react-router-dom' ;
import { api , userApi , type MoneyPrinterJob , type WorkflowSummary } from '../api.js' ;
import { api , userApi , type MoneyPrinterJob , type MoneyPrinterStatusResponse , type WorkflowSummary } from '../api.js' ;
import type { SessionAudience } from '../authSession.js' ;
const STYLE_PRESETS = [
@@ -225,6 +225,10 @@ function parseJsonInput(value: string, label: string) {
}
const BUILTIN_CLOUD_TEMPLATE_VALUE = '__builtin_cloud_seedance__' ;
const DEFAULT_BUILTIN_CLOUD_DURATION = 5 ;
const DEFAULT_BUILTIN_CLOUD_RESOLUTION = '480p' ;
const DEFAULT_CLOUD_RATIOS = [ '9:16' , '16:9' , '1:1' ] ;
const DEFAULT_CLOUD_RESOLUTIONS = [ '480p' , '720p' , '1080p' ] ;
export default function MoneyPrinter() {
const location = useLocation ( ) ;
@@ -247,14 +251,40 @@ export default function MoneyPrinter() {
const [ templateId , setTemplateId ] = useState ( '' ) ;
const [ showAdvancedComfy , setShowAdvancedComfy ] = useState ( false ) ;
const [ jobs , setJobs ] = useState < MoneyPrinterJob [ ] > ( [ ] ) ;
const [ status , setStatus ] = useState < MoneyPrinterStatusResponse | null > ( null ) ;
const [ submitting , setSubmitting ] = useState ( false ) ;
const [ error , setError ] = useState ( '' ) ;
const isCloudComfy = status ? . comfy ? . backend === 'cloud' && status ? . builtinCloudTemplate ? . enabled !== false ;
const cloudDurationMin = status ? . builtinCloudTemplate ? . duration ? . min ? ? 3 ;
const cloudDurationMax = status ? . builtinCloudTemplate ? . duration ? . max ? ? 12 ;
const cloudDefaultDuration = status ? . builtinCloudTemplate ? . duration ? . default ? ? DEFAULT_BUILTIN_CLOUD_DURATION ;
const cloudResolutions = useMemo (
( ) = > status ? . builtinCloudTemplate ? . resolutions ? . length
? status . builtinCloudTemplate . resolutions
: DEFAULT_CLOUD_RESOLUTIONS ,
[ status ? . builtinCloudTemplate ? . resolutions ] ,
) ;
const cloudRatios = useMemo (
( ) = > status ? . builtinCloudTemplate ? . ratios ? . length
? status . builtinCloudTemplate . ratios
: DEFAULT_CLOUD_RATIOS ,
[ status ? . builtinCloudTemplate ? . ratios ] ,
) ;
const loadJobs = useCallback ( async ( ) = > {
const payload = await client . getMoneyPrinterJobs ( { limit : 30 } ) ;
setJobs ( Array . isArray ( payload . jobs ) ? payload . jobs : [ ] ) ;
} , [ client ] ) ;
const loadStatus = useCallback ( async ( ) = > {
const payload = await client . getMoneyPrinterStatus ( ) ;
setStatus ( payload ) ;
} , [ client ] ) ;
const refresh = useCallback ( async ( ) = > {
await Promise . all ( [ loadJobs ( ) , loadStatus ( ) ] ) ;
} , [ loadJobs , loadStatus ] ) ;
const loadWorkflows = useCallback ( async ( ) = > {
const payload = await client . getWorkflows ( { include_public : true } ) ;
const items = Array . isArray ( payload . items ) ? payload . items : [ ] ;
@@ -262,19 +292,44 @@ export default function MoneyPrinter() {
setWorkflows ( videoItems ) ;
setTemplateId ( ( current ) = > {
if ( current && videoItems . some ( ( item ) = > item . publicId === current ) ) return current ;
if ( isCloudComfy ) return BUILTIN_CLOUD_TEMPLATE_VALUE ;
return videoItems . find ( ( item ) = > item . isTemplate || item . visibility === 'template' || item . visibility === 'public' ) ? . publicId
|| videoItems [ 0 ] ? . publicId
|| '' ;
} ) ;
} , [ client ] ) ;
} , [ client , isCloudComfy ] ) ;
useEffect ( ( ) = > {
Promise . all ( [ loadJobs ( ) , loadWorkflows ( ) ] ) . catch ( ( err ) = > setError ( err ? . message || '短视频任务加载失败' ) ) ;
} , [ loadJobs , loadWorkflows ] ) ;
Promise . all ( [ loadJobs ( ) , loadWorkflows ( ) , loadStatus ( ) ] ) . catch ( ( err ) = > setError ( err ? . message || '短视频任务加载失败' ) ) ;
} , [ loadJobs , loadWorkflows , loadStatus ] ) ;
const selectedWorkflow = workflows . find ( ( item ) = > item . publicId === templateId ) || null ;
const useBuiltinCloudTemplate = videoBackend === 'comfy' && ( ! templateId || templateId === BUILTIN_CLOUD_TEMPLATE_VALUE ) ;
const canSubmit = Boolean ( topic . trim ( ) ) && ! submitting ;
const useBuiltinCloudTemplate = videoBackend === 'comfy' && isCloudComfy && templateId === BUILTIN_CLOUD_TEMPLATE_VALUE ;
const comfyHasTemplate = useBuiltinCloudTemplate || Boolean ( templateId && selectedWorkflow ) ;
const canSubmit = Boolean ( topic . trim ( ) ) && ! submitting && ( videoBackend !== 'comfy' || comfyHasTemplate ) ;
const ratioOptions = useBuiltinCloudTemplate ? cloudRatios : DEFAULT_CLOUD_RATIOS ;
const resolutionOptions = useBuiltinCloudTemplate ? cloudResolutions : DEFAULT_CLOUD_RESOLUTIONS ;
const durationMin = useBuiltinCloudTemplate ? cloudDurationMin : 5 ;
const durationMax = useBuiltinCloudTemplate ? cloudDurationMax : 120 ;
useEffect ( ( ) = > {
if ( videoBackend !== 'comfy' ) return ;
if ( isCloudComfy ) {
setTemplateId ( ( current ) = > current || BUILTIN_CLOUD_TEMPLATE_VALUE ) ;
return ;
}
setTemplateId ( ( current ) = > current === BUILTIN_CLOUD_TEMPLATE_VALUE ? '' : current ) ;
} , [ isCloudComfy , videoBackend ] ) ;
useEffect ( ( ) = > {
if ( ! useBuiltinCloudTemplate ) return ;
setDuration ( ( current ) = > {
const numeric = Number . isFinite ( current ) ? current : cloudDefaultDuration ;
return Math . max ( cloudDurationMin , Math . min ( cloudDurationMax , numeric ) ) ;
} ) ;
setResolution ( ( current ) = > cloudResolutions . includes ( current ) ? current : DEFAULT_BUILTIN_CLOUD_RESOLUTION ) ;
setRatio ( ( current ) = > cloudRatios . includes ( current ) ? current : '9:16' ) ;
} , [ cloudDefaultDuration , cloudDurationMax , cloudDurationMin , cloudRatios , cloudResolutions , useBuiltinCloudTemplate ] ) ;
useEffect ( ( ) = > {
if ( useBuiltinCloudTemplate ) {
@@ -304,6 +359,16 @@ export default function MoneyPrinter() {
setSubmitting ( true ) ;
setError ( '' ) ;
try {
if ( videoBackend === 'comfy' && ! comfyHasTemplate ) {
throw new Error ( isCloudComfy ? '请选择 Cloud 内置模板或一个真实视频工作流模板' : '当前 Comfy 后端需要选择一个真实视频工作流模板' ) ;
}
const nextDuration = useBuiltinCloudTemplate
? Math . max ( cloudDurationMin , Math . min ( cloudDurationMax , Number ( duration ) || cloudDefaultDuration ) )
: duration ;
const nextResolution = useBuiltinCloudTemplate && ! cloudResolutions . includes ( resolution )
? DEFAULT_BUILTIN_CLOUD_RESOLUTION
: resolution ;
const nextRatio = useBuiltinCloudTemplate && ! cloudRatios . includes ( ratio ) ? '9:16' : ratio ;
const payload = await client . createMoneyPrinterJob ( {
topic : topic.trim ( ) ,
scriptModel : scriptModel.trim ( ) ,
@@ -311,9 +376,9 @@ export default function MoneyPrinter() {
videoModel : videoModel.trim ( ) ,
style ,
audience : audienceText ,
duration ,
ratio ,
resolution ,
duration : nextDuration ,
ratio : nextRatio ,
resolution : nextResolution ,
requirements ,
language : '中文' ,
. . . ( videoBackend === 'comfy' ? {
@@ -366,7 +431,7 @@ export default function MoneyPrinter() {
< p className = "page-subtitle" > 内 置 MoneyPrinterTurbo 思 路 : 主 题 生 成 脚 本 和 分 镜 , 再 用 BoosAPI 视 频 模 型 生 产 成 片 。 < / p >
< / div >
< div className = "page-actions" >
< button type = "button" className = "btn btn-ghost" onClick = { ( ) = > void loadJobs ( ) } > 刷 新 < / button >
< button type = "button" className = "btn btn-ghost" onClick = { ( ) = > void refresh ( ) } > 刷 新 < / button >
< button type = "button" className = "btn btn-primary" onClick = { submit } disabled = { ! canSubmit } >
{ submitting ? '提交中...' : '生成短视频' }
< / button >
@@ -410,22 +475,18 @@ export default function MoneyPrinter() {
< / label >
< label className = "money-printer-field" >
< span > 时 长 < / span >
< input type = "number" min = { 5 } max = { 120 } value = { duration } onChange = { ( event ) = > setDuration ( Number ( event . target . value || 30 ) ) } / >
< input type = "number" min = { durationMin } max = { durationMax } value = { duration } onChange = { ( event ) = > setDuration ( Number ( event . target . value || durationMin ) ) } / >
< / label >
< label className = "money-printer-field" >
< span > 画 幅 < / span >
< select value = { ratio } onChange = { ( event ) = > setRatio ( event . target . value ) } >
< option value = "9:16" > 9 :16 竖 屏 < / option >
< option value = "16:9" > 16 :9 横 屏 < / option >
< option value = "1:1" > 1 :1 方 屏 < / option >
{ ratioOptions . map ( ( item ) = > < option key = { item } value = { item } > { item } < / option > ) }
< / select >
< / label >
< label className = "money-printer-field" >
< span > 分 辨 率 < / span >
< select value = { resolution } onChange = { ( event ) = > setResolution ( event . target . value ) } >
< option value = "1080p" > 1080 p < / option >
< option value = "720p" > 720 p < / option >
< option value = "480p" > 480 p < / option >
{ resolutionOptions . map ( ( item ) = > < option key = { item } value = { item } > { item } < / option > ) }
< / select >
< / label >
< / div >
@@ -458,7 +519,8 @@ export default function MoneyPrinter() {
< label className = "money-printer-field" >
< span > 工 作 流 模 板 < / span >
< select value = { templateId } onChange = { ( event ) = > setTemplateId ( event . target . value ) } >
< option value = { BUILTIN_CLOUD_TEMPLATE_VALUE } > 内 置 Cloud Seedance 文 生 视 频 < / option >
{ isCloudComfy && < option value = { BUILTIN_CLOUD_TEMPLATE_VALUE } > 内 置 Cloud Seedance 文 生 视 频 < / option > }
{ ! isCloudComfy && < option value = "" > 选 择 真 实 Comfy 视 频 模 板 < / option > }
{ workflows . map ( ( workflow ) = > (
< option key = { workflow . publicId } value = { workflow . publicId } >
{ workflowLooksVideoCapable ( workflow ) ? '视频 · ' : '模板 · ' } { workflowOptionLabel ( workflow ) }
@@ -469,7 +531,10 @@ export default function MoneyPrinter() {
{ useBuiltinCloudTemplate ? (
< div className = "money-printer-template-meta" >
< strong > 内 置 Cloud Seedance 文 生 视 频 < / strong >
< span > 无 需 导 入 模 板 , 使 用 Comfy Cloud 官 方 ByteDanceTextToVideoNode 。 若 账 号 无 余 额 , 会 直 接 显 示 余 额 不 足 。 < / span >
< span >
当 前 后 端 为 Cloud , 使 用 官 方 { status ? . builtinCloudTemplate ? . nodeType || 'ByteDanceTextToVideoNode' }
{ status ? . builtinCloudTemplate ? . model ? ` / ${ status . builtinCloudTemplate . model } ` : '' } 。 时 长 { cloudDurationMin } - { cloudDurationMax } 秒 , 若 账 号 无 余 额 会 直 接 显 示 余 额 不 足 。
< / span >
< / div >
) : selectedWorkflow ? (
< div className = "money-printer-template-meta" >
@@ -480,7 +545,12 @@ export default function MoneyPrinter() {
{ mappingFromGraph ( selectedWorkflow . graph ) ? '已带节点映射' : '系统会按节点结构自动推断提示词和参数映射' }
< / span >
< / div >
) : null }
) : (
< div className = "money-printer-template-meta is-warning" >
< strong > 需 要 视 频 工 作 流 模 板 < / strong >
< span > { isCloudComfy ? '可以使用内置 Cloud 模板,也可以选择模板库里的真实视频工作流。' : '当前不是 Cloud 后端,内置 Seedance 模板不可用,请选择 external/runninghub 可执行的视频模板。' } < / span >
< / div >
) }
< button
type = "button"
className = "btn btn-ghost money-printer-advanced-toggle"