Default MoneyPrinter videos to BoosAPI route
This commit is contained in:
+23
-31
@@ -585,6 +585,20 @@ fn first_node_by_class(prompt: &serde_json::Map<String, Value>, class_type: &str
|
|||||||
.map(|(id, _)| id.clone())
|
.map(|(id, _)| id.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prompt_contains_class_type(prompt: &Value, class_type: &str) -> bool {
|
||||||
|
match prompt {
|
||||||
|
Value::Object(object) => {
|
||||||
|
object
|
||||||
|
.get("class_type")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.is_some_and(|value| value == class_type)
|
||||||
|
|| object.values().any(|value| prompt_contains_class_type(value, class_type))
|
||||||
|
}
|
||||||
|
Value::Array(items) => items.iter().any(|value| prompt_contains_class_type(value, class_type)),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn infer_comfy_mapping(prompt: &Value) -> Value {
|
fn infer_comfy_mapping(prompt: &Value) -> Value {
|
||||||
let Some(object) = prompt.as_object() else {
|
let Some(object) = prompt.as_object() else {
|
||||||
return default_comfy_mapping();
|
return default_comfy_mapping();
|
||||||
@@ -687,35 +701,6 @@ fn workflow_prompt_from_graph(graph: &Value) -> Value {
|
|||||||
.unwrap_or_else(|| graph.clone())
|
.unwrap_or_else(|| graph.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generated_comfy_prompt_from_request(body: &Value) -> Value {
|
|
||||||
let model = string_from(body, &["videoModel", "video_model", "model"])
|
|
||||||
.unwrap_or_else(|| "seedance-1-0-lite-t2v".to_string());
|
|
||||||
let ratio = string_from(body, &["ratio", "aspectRatio", "aspect_ratio"]).unwrap_or_else(|| "9:16".to_string());
|
|
||||||
let resolution = string_from(body, &["resolution", "size"]).unwrap_or_else(|| "1080p".to_string());
|
|
||||||
let duration = body
|
|
||||||
.get("duration")
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_else(|| json!(30));
|
|
||||||
json!({
|
|
||||||
"1": {
|
|
||||||
"class_type": "BoosAPITextToVideo",
|
|
||||||
"inputs": {
|
|
||||||
"model": model,
|
|
||||||
"prompt": "",
|
|
||||||
"ratio": ratio,
|
|
||||||
"resolution": resolution,
|
|
||||||
"duration": duration
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"2": {
|
|
||||||
"class_type": "output.preview_video",
|
|
||||||
"inputs": {
|
|
||||||
"video": ["1", 0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn workflow_prompt_from_body(
|
async fn workflow_prompt_from_body(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
auth: &MoneyPrinterAuth,
|
auth: &MoneyPrinterAuth,
|
||||||
@@ -740,7 +725,9 @@ async fn workflow_prompt_from_body(
|
|||||||
.await?;
|
.await?;
|
||||||
return Ok(workflow_prompt_from_graph(&workflow.graph));
|
return Ok(workflow_prompt_from_graph(&workflow.graph));
|
||||||
}
|
}
|
||||||
Ok(generated_comfy_prompt_from_request(body))
|
Err(AppError::BadRequest(
|
||||||
|
"Comfy API 模式需要选择真实 Comfy/Cloud 视频工作流模板,不能使用 BoosAPI 自定义节点".into(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_node_input(prompt: &mut Value, node_id: &str, field: &str, value: Value) -> AppResult<()> {
|
fn apply_node_input(prompt: &mut Value, node_id: &str, field: &str, value: Value) -> AppResult<()> {
|
||||||
@@ -933,6 +920,11 @@ async fn submit_comfy_video(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
let mut prompt = workflow_prompt_from_body(state, auth, body).await?;
|
let mut prompt = workflow_prompt_from_body(state, auth, body).await?;
|
||||||
|
if prompt_contains_class_type(&prompt, "BoosAPITextToVideo") {
|
||||||
|
return Err(AppError::BadRequest(
|
||||||
|
"当前 Comfy 后端不支持 BoosAPITextToVideo 自定义节点;请改用 BoosAPI /v1/videos 或选择真实 Comfy 视频模板".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
let mut mapping = comfy_mapping_from_body(body);
|
let mut mapping = comfy_mapping_from_body(body);
|
||||||
if !mapping_has_node(&mapping, &["prompt", "positive", "text", "promptNode"]) {
|
if !mapping_has_node(&mapping, &["prompt", "positive", "text", "promptNode"]) {
|
||||||
mapping = infer_comfy_mapping(&prompt);
|
mapping = infer_comfy_mapping(&prompt);
|
||||||
@@ -1138,7 +1130,7 @@ async fn create_job(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let video_backend = string_from(&body, &["videoBackend", "video_backend"])
|
let video_backend = string_from(&body, &["videoBackend", "video_backend"])
|
||||||
.unwrap_or_else(|| "comfy".to_string())
|
.unwrap_or_else(|| "boosapi".to_string())
|
||||||
.to_ascii_lowercase();
|
.to_ascii_lowercase();
|
||||||
let title = plan
|
let title = plan
|
||||||
.get("title")
|
.get("title")
|
||||||
|
|||||||
@@ -146,36 +146,6 @@ function templateLabel(workflow: WorkflowSummary) {
|
|||||||
return '私有工作流';
|
return '私有工作流';
|
||||||
}
|
}
|
||||||
|
|
||||||
function generatedComfyPromptFromForm(model: string, ratio: string, resolution: string, duration: number) {
|
|
||||||
return {
|
|
||||||
'1': {
|
|
||||||
class_type: 'BoosAPITextToVideo',
|
|
||||||
inputs: {
|
|
||||||
model,
|
|
||||||
prompt: '',
|
|
||||||
ratio,
|
|
||||||
resolution,
|
|
||||||
duration,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'2': {
|
|
||||||
class_type: 'output.preview_video',
|
|
||||||
inputs: {
|
|
||||||
video: ['1', 0],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function generatedComfyMapping() {
|
|
||||||
return {
|
|
||||||
prompt: { nodeId: '1', field: 'prompt' },
|
|
||||||
ratio: { nodeId: '1', field: 'ratio' },
|
|
||||||
resolution: { nodeId: '1', field: 'resolution' },
|
|
||||||
duration: { nodeId: '1', field: 'duration' },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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';
|
||||||
@@ -184,7 +154,7 @@ export default function MoneyPrinter() {
|
|||||||
const [topic, setTopic] = useState('帮我生成一个 30 秒的 AI 应用平台 BossAI 商品介绍短视频');
|
const [topic, setTopic] = useState('帮我生成一个 30 秒的 AI 应用平台 BossAI 商品介绍短视频');
|
||||||
const [scriptModel, setScriptModel] = useState('gpt-5.4');
|
const [scriptModel, setScriptModel] = useState('gpt-5.4');
|
||||||
const [videoModel, setVideoModel] = useState('seedance-1-0-lite-t2v');
|
const [videoModel, setVideoModel] = useState('seedance-1-0-lite-t2v');
|
||||||
const [videoBackend, setVideoBackend] = useState<'comfy' | 'boosapi'>('comfy');
|
const [videoBackend, setVideoBackend] = useState<'boosapi' | 'comfy'>('boosapi');
|
||||||
const [style, setStyle] = useState(STYLE_PRESETS[0]);
|
const [style, setStyle] = useState(STYLE_PRESETS[0]);
|
||||||
const [audienceText, setAudienceText] = useState('AI 工具用户、内容创作者、企业运营');
|
const [audienceText, setAudienceText] = useState('AI 工具用户、内容创作者、企业运营');
|
||||||
const [duration, setDuration] = useState(30);
|
const [duration, setDuration] = useState(30);
|
||||||
@@ -226,12 +196,6 @@ export default function MoneyPrinter() {
|
|||||||
setComfyNodeMapping(JSON.stringify(mapping || inferMappingFromPrompt(prompt), null, 2));
|
setComfyNodeMapping(JSON.stringify(mapping || inferMappingFromPrompt(prompt), null, 2));
|
||||||
}, [selectedWorkflow?.publicId, videoBackend]);
|
}, [selectedWorkflow?.publicId, videoBackend]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (videoBackend !== 'comfy' || useAdvancedComfy) return;
|
|
||||||
setComfyPrompt(JSON.stringify(generatedComfyPromptFromForm(videoModel.trim(), ratio, resolution, duration), null, 2));
|
|
||||||
setComfyNodeMapping(JSON.stringify(generatedComfyMapping(), null, 2));
|
|
||||||
}, [videoBackend, useAdvancedComfy, videoModel, ratio, resolution, duration]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!jobs.some((job) => !terminal(job.status))) return;
|
if (!jobs.some((job) => !terminal(job.status))) return;
|
||||||
const timer = window.setInterval(() => {
|
const timer = window.setInterval(() => {
|
||||||
@@ -260,7 +224,7 @@ export default function MoneyPrinter() {
|
|||||||
requirements,
|
requirements,
|
||||||
language: '中文',
|
language: '中文',
|
||||||
...(videoBackend === 'comfy' ? {
|
...(videoBackend === 'comfy' ? {
|
||||||
templateId: useAdvancedComfy ? templateId || undefined : undefined,
|
templateId: templateId || undefined,
|
||||||
comfyPrompt: comfyPrompt.trim() ? JSON.parse(comfyPrompt) : undefined,
|
comfyPrompt: comfyPrompt.trim() ? JSON.parse(comfyPrompt) : undefined,
|
||||||
comfyNodeMapping: comfyNodeMapping.trim() ? JSON.parse(comfyNodeMapping) : undefined,
|
comfyNodeMapping: comfyNodeMapping.trim() ? JSON.parse(comfyNodeMapping) : undefined,
|
||||||
} : {}),
|
} : {}),
|
||||||
@@ -336,10 +300,10 @@ export default function MoneyPrinter() {
|
|||||||
<input value={scriptModel} onChange={(event) => setScriptModel(event.target.value)} />
|
<input value={scriptModel} onChange={(event) => setScriptModel(event.target.value)} />
|
||||||
</label>
|
</label>
|
||||||
<label className="money-printer-field">
|
<label className="money-printer-field">
|
||||||
<span>视频后端</span>
|
<span>视频后端</span>
|
||||||
<select value={videoBackend} onChange={(event) => setVideoBackend(event.target.value as 'comfy' | 'boosapi')}>
|
<select value={videoBackend} onChange={(event) => setVideoBackend(event.target.value as 'comfy' | 'boosapi')}>
|
||||||
<option value="comfy">Comfy API</option>
|
|
||||||
<option value="boosapi">BoosAPI /v1/videos</option>
|
<option value="boosapi">BoosAPI /v1/videos</option>
|
||||||
|
<option value="comfy">Comfy API 模板</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label className="money-printer-field">
|
<label className="money-printer-field">
|
||||||
@@ -390,47 +354,34 @@ export default function MoneyPrinter() {
|
|||||||
<div className="money-printer-section-head">
|
<div className="money-printer-section-head">
|
||||||
<div>
|
<div>
|
||||||
<h2>Comfy API 工作流</h2>
|
<h2>Comfy API 工作流</h2>
|
||||||
<p>{useAdvancedComfy ? '使用保存过的工作流模板,会自动带入 Comfy API prompt 和节点映射。' : '已根据上方表单自动生成 Comfy prompt,生成时会注入 AI 改写后的视频提示词。'}</p>
|
<p>请选择真实 Comfy/Cloud 支持的视频工作流模板。系统只注入提示词、画幅、分辨率和时长,不再提交 BoosAPI 自定义节点。</p>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" className="btn btn-ghost" onClick={() => setUseAdvancedComfy((value) => !value)}>
|
|
||||||
{useAdvancedComfy ? '使用自动生成' : '高级模板'}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
{useAdvancedComfy && (
|
<label className="money-printer-field">
|
||||||
<>
|
<span>工作流模板</span>
|
||||||
<label className="money-printer-field">
|
<select value={templateId} onChange={(event) => setTemplateId(event.target.value)}>
|
||||||
<span>工作流模板</span>
|
<option value="">请选择模板</option>
|
||||||
<select value={templateId} onChange={(event) => setTemplateId(event.target.value)}>
|
{workflows.map((workflow) => (
|
||||||
<option value="">请选择模板</option>
|
<option key={workflow.publicId} value={workflow.publicId}>
|
||||||
{workflows.map((workflow) => (
|
{workflow.name} · {templateLabel(workflow)}
|
||||||
<option key={workflow.publicId} value={workflow.publicId}>
|
</option>
|
||||||
{workflow.name} · {templateLabel(workflow)}
|
))}
|
||||||
</option>
|
</select>
|
||||||
))}
|
</label>
|
||||||
</select>
|
{selectedWorkflow ? (
|
||||||
</label>
|
|
||||||
{selectedWorkflow && (
|
|
||||||
<div className="money-printer-template-meta">
|
|
||||||
<strong>{selectedWorkflow.name}</strong>
|
|
||||||
<span>{mappingFromGraph(selectedWorkflow.graph) ? '已带节点映射' : '这个模板没有内置节点映射,系统会尝试自动推断。'}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<label className="money-printer-field">
|
|
||||||
<span>Comfy prompt JSON</span>
|
|
||||||
<textarea value={comfyPrompt} onChange={(event) => setComfyPrompt(event.target.value)} rows={7} placeholder='{"12":{"class_type":"...","inputs":{...}}}' />
|
|
||||||
</label>
|
|
||||||
<label className="money-printer-field">
|
|
||||||
<span>节点映射 JSON</span>
|
|
||||||
<textarea value={comfyNodeMapping} onChange={(event) => setComfyNodeMapping(event.target.value)} rows={7} />
|
|
||||||
</label>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{!useAdvancedComfy && (
|
|
||||||
<div className="money-printer-template-meta">
|
<div className="money-printer-template-meta">
|
||||||
<strong>自动生成 Comfy prompt</strong>
|
<strong>{selectedWorkflow.name}</strong>
|
||||||
<span>模型 {videoModel || '未填写'} / {ratio} / {resolution} / {duration} 秒</span>
|
<span>{mappingFromGraph(selectedWorkflow.graph) ? '已带节点映射' : '这个模板没有内置节点映射,系统会尝试自动推断。'}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
) : null}
|
||||||
|
<label className="money-printer-field">
|
||||||
|
<span>Comfy prompt JSON</span>
|
||||||
|
<textarea value={comfyPrompt} onChange={(event) => setComfyPrompt(event.target.value)} rows={7} placeholder='{"12":{"class_type":"...","inputs":{...}}}' />
|
||||||
|
</label>
|
||||||
|
<label className="money-printer-field">
|
||||||
|
<span>节点映射 JSON</span>
|
||||||
|
<textarea value={comfyNodeMapping} onChange={(event) => setComfyNodeMapping(event.target.value)} rows={7} />
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user