Add built-in Cloud video template fallback
This commit is contained in:
@@ -723,6 +723,25 @@ fn workflow_prompt_from_graph(graph: &Value) -> Value {
|
|||||||
.unwrap_or_else(|| graph.clone())
|
.unwrap_or_else(|| graph.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_cloud_seedance_prompt() -> Value {
|
||||||
|
json!({
|
||||||
|
"1": {
|
||||||
|
"class_type": "ByteDanceTextToVideoNode",
|
||||||
|
"inputs": {
|
||||||
|
"model": "seedance-1-0-lite-t2v-250428",
|
||||||
|
"prompt": "A clean vertical product demo video, minimal futuristic interface, smooth camera movement, bright studio lighting",
|
||||||
|
"resolution": "480p",
|
||||||
|
"aspect_ratio": "9:16",
|
||||||
|
"duration": 5,
|
||||||
|
"seed": 0,
|
||||||
|
"camera_fixed": false,
|
||||||
|
"watermark": false,
|
||||||
|
"generate_audio": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async fn workflow_prompt_from_body(
|
async fn workflow_prompt_from_body(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
auth: &MoneyPrinterAuth,
|
auth: &MoneyPrinterAuth,
|
||||||
@@ -747,8 +766,11 @@ async fn workflow_prompt_from_body(
|
|||||||
.await?;
|
.await?;
|
||||||
return Ok(workflow_prompt_from_graph(&workflow.graph));
|
return Ok(workflow_prompt_from_graph(&workflow.graph));
|
||||||
}
|
}
|
||||||
|
if state.config.comfyui.uses_cloud_backend() {
|
||||||
|
return Ok(default_cloud_seedance_prompt());
|
||||||
|
}
|
||||||
Err(AppError::BadRequest(
|
Err(AppError::BadRequest(
|
||||||
"Comfy API 模式需要选择真实 Comfy/Cloud 视频工作流模板,不能使用 BoosAPI 自定义节点".into(),
|
"Comfy API 模式需要选择真实 Comfy/Cloud 视频工作流模板;Cloud 模式可使用内置 Seedance 兜底模板".into(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -856,11 +878,45 @@ fn inject_comfy_video_inputs(prompt: &mut Value, mapping: &Value, body: &Value,
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn repair_money_printer_comfy_prompt(prompt: &mut Value) {
|
fn repair_money_printer_comfy_prompt(prompt: &mut Value) {
|
||||||
|
repair_bytedance_video_nodes(prompt);
|
||||||
repair_resize_image_mask_nodes(prompt);
|
repair_resize_image_mask_nodes(prompt);
|
||||||
repair_text_generate_ltx2_prompt_nodes(prompt);
|
repair_text_generate_ltx2_prompt_nodes(prompt);
|
||||||
repair_wan2_api_nodes(prompt);
|
repair_wan2_api_nodes(prompt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn repair_bytedance_video_nodes(prompt: &mut Value) {
|
||||||
|
let Some(nodes) = prompt.as_object_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
for (_node_id, node) in nodes.iter_mut() {
|
||||||
|
let class_type = node.get("class_type").and_then(Value::as_str).unwrap_or_default();
|
||||||
|
if !matches!(class_type, "ByteDanceTextToVideoNode" | "ByteDance2TextToVideoNode") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let Some(inputs) = node.get_mut("inputs").and_then(Value::as_object_mut) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if let Some(duration) = inputs.get("duration").and_then(value_to_duration_seconds) {
|
||||||
|
inputs.insert("duration".to_string(), json!(duration.clamp(3, 12)));
|
||||||
|
}
|
||||||
|
if let Some(duration) = inputs.get("model.duration").and_then(value_to_duration_seconds) {
|
||||||
|
inputs.insert("model.duration".to_string(), json!(duration.clamp(4, 15)));
|
||||||
|
}
|
||||||
|
if inputs
|
||||||
|
.get("resolution")
|
||||||
|
.is_some_and(|value| !matches!(value.as_str(), Some("480p" | "720p" | "1080p")))
|
||||||
|
{
|
||||||
|
inputs.insert("resolution".to_string(), json!("480p"));
|
||||||
|
}
|
||||||
|
if inputs
|
||||||
|
.get("aspect_ratio")
|
||||||
|
.is_some_and(|value| !matches!(value.as_str(), Some("16:9" | "4:3" | "1:1" | "3:4" | "9:16" | "21:9")))
|
||||||
|
{
|
||||||
|
inputs.insert("aspect_ratio".to_string(), json!("9:16"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn repair_resize_image_mask_nodes(prompt: &mut Value) {
|
fn repair_resize_image_mask_nodes(prompt: &mut Value) {
|
||||||
let Some(nodes) = prompt.as_object_mut() else {
|
let Some(nodes) = prompt.as_object_mut() else {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -224,6 +224,8 @@ function parseJsonInput(value: string, label: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const BUILTIN_CLOUD_TEMPLATE_VALUE = '__builtin_cloud_seedance__';
|
||||||
|
|
||||||
export default function MoneyPrinter() {
|
export default function MoneyPrinter() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const audience: SessionAudience = location.pathname.startsWith('/user/') ? 'user' : 'admin';
|
const audience: SessionAudience = location.pathname.startsWith('/user/') ? 'user' : 'admin';
|
||||||
@@ -271,15 +273,21 @@ export default function MoneyPrinter() {
|
|||||||
}, [loadJobs, loadWorkflows]);
|
}, [loadJobs, loadWorkflows]);
|
||||||
|
|
||||||
const selectedWorkflow = workflows.find((item) => item.publicId === templateId) || null;
|
const selectedWorkflow = workflows.find((item) => item.publicId === templateId) || null;
|
||||||
const canSubmit = Boolean(topic.trim()) && !submitting && (videoBackend !== 'comfy' || Boolean(templateId));
|
const useBuiltinCloudTemplate = videoBackend === 'comfy' && (!templateId || templateId === BUILTIN_CLOUD_TEMPLATE_VALUE);
|
||||||
|
const canSubmit = Boolean(topic.trim()) && !submitting;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (useBuiltinCloudTemplate) {
|
||||||
|
setComfyPrompt('');
|
||||||
|
setComfyNodeMapping('{\n "prompt": { "nodeId": "1", "field": "prompt" },\n "ratio": { "nodeId": "1", "field": "aspect_ratio" },\n "resolution": { "nodeId": "1", "field": "resolution" },\n "duration": { "nodeId": "1", "field": "duration" }\n}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!selectedWorkflow || videoBackend !== 'comfy') return;
|
if (!selectedWorkflow || videoBackend !== 'comfy') return;
|
||||||
const prompt = workflowPromptFromGraph(selectedWorkflow.graph);
|
const prompt = workflowPromptFromGraph(selectedWorkflow.graph);
|
||||||
if (prompt) setComfyPrompt(JSON.stringify(prompt, null, 2));
|
if (prompt) setComfyPrompt(JSON.stringify(prompt, null, 2));
|
||||||
const mapping = mappingFromGraph(selectedWorkflow.graph);
|
const mapping = mappingFromGraph(selectedWorkflow.graph);
|
||||||
setComfyNodeMapping(JSON.stringify(mapping || inferMappingFromPrompt(prompt), null, 2));
|
setComfyNodeMapping(JSON.stringify(mapping || inferMappingFromPrompt(prompt), null, 2));
|
||||||
}, [selectedWorkflow?.publicId, videoBackend]);
|
}, [selectedWorkflow?.publicId, videoBackend, useBuiltinCloudTemplate]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!jobs.some((job) => !terminal(job.status))) return;
|
if (!jobs.some((job) => !terminal(job.status))) return;
|
||||||
@@ -309,8 +317,8 @@ export default function MoneyPrinter() {
|
|||||||
requirements,
|
requirements,
|
||||||
language: '中文',
|
language: '中文',
|
||||||
...(videoBackend === 'comfy' ? {
|
...(videoBackend === 'comfy' ? {
|
||||||
templateId: templateId || undefined,
|
templateId: useBuiltinCloudTemplate ? undefined : templateId,
|
||||||
comfyPrompt: parseJsonInput(comfyPrompt, 'Comfy prompt JSON'),
|
comfyPrompt: useBuiltinCloudTemplate ? undefined : parseJsonInput(comfyPrompt, 'Comfy prompt JSON'),
|
||||||
comfyNodeMapping: parseJsonInput(comfyNodeMapping, '节点映射 JSON'),
|
comfyNodeMapping: parseJsonInput(comfyNodeMapping, '节点映射 JSON'),
|
||||||
} : {}),
|
} : {}),
|
||||||
});
|
});
|
||||||
@@ -450,7 +458,7 @@ export default function MoneyPrinter() {
|
|||||||
<label className="money-printer-field">
|
<label className="money-printer-field">
|
||||||
<span>工作流模板</span>
|
<span>工作流模板</span>
|
||||||
<select value={templateId} onChange={(event) => setTemplateId(event.target.value)}>
|
<select value={templateId} onChange={(event) => setTemplateId(event.target.value)}>
|
||||||
<option value="">请选择模板</option>
|
<option value={BUILTIN_CLOUD_TEMPLATE_VALUE}>内置 Cloud Seedance 文生视频</option>
|
||||||
{workflows.map((workflow) => (
|
{workflows.map((workflow) => (
|
||||||
<option key={workflow.publicId} value={workflow.publicId}>
|
<option key={workflow.publicId} value={workflow.publicId}>
|
||||||
{workflowLooksVideoCapable(workflow) ? '视频 · ' : '模板 · '}{workflowOptionLabel(workflow)}
|
{workflowLooksVideoCapable(workflow) ? '视频 · ' : '模板 · '}{workflowOptionLabel(workflow)}
|
||||||
@@ -458,7 +466,12 @@ export default function MoneyPrinter() {
|
|||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
{selectedWorkflow ? (
|
{useBuiltinCloudTemplate ? (
|
||||||
|
<div className="money-printer-template-meta">
|
||||||
|
<strong>内置 Cloud Seedance 文生视频</strong>
|
||||||
|
<span>无需导入模板,使用 Comfy Cloud 官方 ByteDanceTextToVideoNode。若账号无余额,会直接显示余额不足。</span>
|
||||||
|
</div>
|
||||||
|
) : selectedWorkflow ? (
|
||||||
<div className="money-printer-template-meta">
|
<div className="money-printer-template-meta">
|
||||||
<strong>{selectedWorkflow.name}</strong>
|
<strong>{selectedWorkflow.name}</strong>
|
||||||
<span>
|
<span>
|
||||||
|
|||||||
Reference in New Issue
Block a user