refactor(workflow_ui): 抽出 models 子模块
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -127,6 +127,12 @@ use node_defs::{
|
||||
inject_boosapi_workflow_template_payload, node_type_def_to_comfy, normalize_prompt_payload,
|
||||
text_to_video_workflow_graph, workflow_template_payload,
|
||||
};
|
||||
mod models;
|
||||
use models::{
|
||||
combo_fields_from_node, image_model_choices, is_video_workflow_model,
|
||||
native_folder_models_from_object_info, workflow_folder_has_route_models,
|
||||
workflow_folder_is_api_only, workflow_folder_route_model_items, workflow_route_models,
|
||||
};
|
||||
|
||||
pub fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
@@ -383,302 +389,6 @@ fn parse_boolish(input: Option<&str>) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
fn is_exact_workflow_model(value: &str) -> bool {
|
||||
let normalized = value.trim();
|
||||
!normalized.is_empty()
|
||||
&& !normalized.to_ascii_lowercase().starts_with("re:")
|
||||
&& !normalized.contains('*')
|
||||
&& !normalized.contains('?')
|
||||
}
|
||||
|
||||
fn is_image_workflow_model(value: &str) -> bool {
|
||||
if !is_exact_workflow_model(value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let normalized = value.trim().to_ascii_lowercase();
|
||||
let non_image_keywords = [
|
||||
"tts",
|
||||
"voice",
|
||||
"audio",
|
||||
"embedding",
|
||||
"embed",
|
||||
"seedance",
|
||||
"wan2",
|
||||
"t2v",
|
||||
"i2v",
|
||||
"flf2v",
|
||||
"seaweed",
|
||||
"3d",
|
||||
"hitem3d",
|
||||
"hyper3d",
|
||||
];
|
||||
if non_image_keywords
|
||||
.iter()
|
||||
.any(|keyword| normalized.contains(keyword))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let image_keywords = [
|
||||
"gpt-image",
|
||||
"dall-e",
|
||||
"dalle",
|
||||
"t2i",
|
||||
"i2i",
|
||||
"midjourney",
|
||||
"recraft",
|
||||
"kolors",
|
||||
"cogview",
|
||||
"seedream",
|
||||
"sdxl",
|
||||
"stable-diffusion",
|
||||
"stablediffusion",
|
||||
"pixart",
|
||||
"playground",
|
||||
"firefly",
|
||||
"imagen",
|
||||
"flux",
|
||||
"image",
|
||||
"img",
|
||||
"canvas",
|
||||
"painting",
|
||||
"draw",
|
||||
];
|
||||
image_keywords
|
||||
.iter()
|
||||
.any(|keyword| normalized.contains(keyword))
|
||||
}
|
||||
|
||||
fn is_video_workflow_model(value: &str) -> bool {
|
||||
let normalized = value.trim().to_ascii_lowercase();
|
||||
[
|
||||
"seedance", "wan2", "t2v", "i2v", "flf2v", "video", "seaweed",
|
||||
]
|
||||
.iter()
|
||||
.any(|keyword| normalized.contains(keyword))
|
||||
}
|
||||
|
||||
fn is_audio_workflow_model(value: &str) -> bool {
|
||||
let normalized = value.trim().to_ascii_lowercase();
|
||||
["tts", "voice", "audio", "speech"]
|
||||
.iter()
|
||||
.any(|keyword| normalized.contains(keyword))
|
||||
}
|
||||
|
||||
fn is_embedding_workflow_model(value: &str) -> bool {
|
||||
let normalized = value.trim().to_ascii_lowercase();
|
||||
normalized.contains("embedding") || normalized.contains("embed")
|
||||
}
|
||||
|
||||
fn is_3d_workflow_model(value: &str) -> bool {
|
||||
let normalized = value.trim().to_ascii_lowercase();
|
||||
["3d", "hitem3d", "hyper3d"]
|
||||
.iter()
|
||||
.any(|keyword| normalized.contains(keyword))
|
||||
}
|
||||
|
||||
fn is_vision_workflow_model(value: &str) -> bool {
|
||||
let normalized = value.trim().to_ascii_lowercase();
|
||||
["vision", "vl", "omni", "ui-tars"]
|
||||
.iter()
|
||||
.any(|keyword| normalized.contains(keyword))
|
||||
&& !is_image_workflow_model(value)
|
||||
&& !is_video_workflow_model(value)
|
||||
&& !is_embedding_workflow_model(value)
|
||||
}
|
||||
|
||||
fn is_chat_workflow_model(value: &str) -> bool {
|
||||
is_exact_workflow_model(value)
|
||||
&& !is_image_workflow_model(value)
|
||||
&& !is_video_workflow_model(value)
|
||||
&& !is_audio_workflow_model(value)
|
||||
&& !is_embedding_workflow_model(value)
|
||||
&& !is_3d_workflow_model(value)
|
||||
}
|
||||
|
||||
async fn workflow_route_models(state: &AppState) -> AppResult<Vec<String>> {
|
||||
let rows = sqlx::query_scalar::<_, String>(
|
||||
"SELECT DISTINCT model_pattern
|
||||
FROM token_routes
|
||||
WHERE COALESCE(enabled, 1) = 1
|
||||
AND model_pattern IS NOT NULL
|
||||
AND model_pattern <> ''
|
||||
ORDER BY model_pattern COLLATE NOCASE",
|
||||
)
|
||||
.fetch_all(&*state.db)
|
||||
.await?;
|
||||
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
Ok(rows
|
||||
.into_iter()
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| is_exact_workflow_model(value))
|
||||
.filter(|value| seen.insert(value.clone()))
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn image_model_choices(models: &[String]) -> Vec<String> {
|
||||
let image_models = models
|
||||
.iter()
|
||||
.filter(|model| is_image_workflow_model(model))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
if image_models.is_empty() {
|
||||
models.to_vec()
|
||||
} else {
|
||||
image_models
|
||||
}
|
||||
}
|
||||
|
||||
fn workflow_folder_models(folder: &str, models: &[String]) -> Vec<String> {
|
||||
let normalized = folder.trim().to_ascii_lowercase();
|
||||
match normalized.as_str() {
|
||||
"boosapi_models" | "boosapi" | "api_models" => models.to_vec(),
|
||||
"image_models" => image_model_choices(models),
|
||||
"chat_models" => models
|
||||
.iter()
|
||||
.filter(|model| is_chat_workflow_model(model))
|
||||
.cloned()
|
||||
.collect(),
|
||||
"clip" => models.to_vec(),
|
||||
// Comfy's video_models folder is a native Comfy API model folder. Keep it
|
||||
// separate from BoosAPI route models so video apps submit real Comfy API
|
||||
// model names instead of OpenAI-compatible route aliases.
|
||||
"video_models" => Vec::new(),
|
||||
"audio_tts_models" => models
|
||||
.iter()
|
||||
.filter(|model| is_audio_workflow_model(model))
|
||||
.cloned()
|
||||
.collect(),
|
||||
"embeddings" => models
|
||||
.iter()
|
||||
.filter(|model| is_embedding_workflow_model(model))
|
||||
.cloned()
|
||||
.collect(),
|
||||
"3d_models" => models
|
||||
.iter()
|
||||
.filter(|model| is_3d_workflow_model(model))
|
||||
.cloned()
|
||||
.collect(),
|
||||
"vision_models" => models
|
||||
.iter()
|
||||
.filter(|model| is_vision_workflow_model(model))
|
||||
.cloned()
|
||||
.collect(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn workflow_folder_is_api_only(folder: &str) -> bool {
|
||||
matches!(
|
||||
folder.trim().to_ascii_lowercase().as_str(),
|
||||
"boosapi" | "boosapi_models" | "api_models"
|
||||
)
|
||||
}
|
||||
|
||||
fn workflow_folder_has_route_models(folder: &str) -> bool {
|
||||
matches!(
|
||||
folder.trim().to_ascii_lowercase().as_str(),
|
||||
"boosapi"
|
||||
| "boosapi_models"
|
||||
| "api_models"
|
||||
| "image_models"
|
||||
| "chat_models"
|
||||
| "audio_tts_models"
|
||||
| "embeddings"
|
||||
| "3d_models"
|
||||
| "vision_models"
|
||||
)
|
||||
}
|
||||
|
||||
fn workflow_folder_route_model_items(folder: &str, route_models: &[String]) -> Vec<Value> {
|
||||
model_payload_items_from_names(workflow_folder_models(folder, route_models), 0)
|
||||
}
|
||||
|
||||
fn combo_choices_from_node_input(
|
||||
object_info: &Value,
|
||||
node_type: &str,
|
||||
input_name: &str,
|
||||
) -> Vec<String> {
|
||||
object_info
|
||||
.get(node_type)
|
||||
.and_then(|node| {
|
||||
node.pointer(&format!("/input/required/{input_name}"))
|
||||
.or_else(|| node.pointer(&format!("/input/optional/{input_name}")))
|
||||
})
|
||||
.and_then(Value::as_array)
|
||||
.and_then(|input| input.first())
|
||||
.and_then(Value::as_array)
|
||||
.map(|choices| {
|
||||
choices
|
||||
.iter()
|
||||
.filter_map(Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty() && *value != "None")
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn combo_fields_from_node(object_info: &Value, node_type: &str) -> Vec<(String, Vec<String>)> {
|
||||
let mut fields = Vec::new();
|
||||
let Some(node) = object_info.get(node_type) else {
|
||||
return fields;
|
||||
};
|
||||
for section in ["required", "optional"] {
|
||||
if let Some(inputs) = node
|
||||
.pointer(&format!("/input/{section}"))
|
||||
.and_then(Value::as_object)
|
||||
{
|
||||
for (name, value) in inputs {
|
||||
let Some(choices) = value
|
||||
.as_array()
|
||||
.and_then(|input| input.first())
|
||||
.and_then(Value::as_array)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let choices = choices
|
||||
.iter()
|
||||
.filter_map(Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty() && *value != "None")
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<Vec<_>>();
|
||||
fields.push((name.clone(), choices));
|
||||
}
|
||||
}
|
||||
}
|
||||
fields
|
||||
}
|
||||
|
||||
fn native_folder_models_from_object_info(folder: &str, object_info: &Value) -> Vec<String> {
|
||||
let normalized = folder.trim().to_ascii_lowercase();
|
||||
match normalized.as_str() {
|
||||
"checkpoints" | "image_models" => {
|
||||
combo_choices_from_node_input(object_info, "CheckpointLoaderSimple", "ckpt_name")
|
||||
}
|
||||
"loras" => combo_choices_from_node_input(object_info, "LoraLoader", "lora_name"),
|
||||
"vae" => combo_choices_from_node_input(object_info, "VAELoader", "vae_name"),
|
||||
"clip" | "clip_models" | "text_encoders" => {
|
||||
combo_choices_from_node_input(object_info, "CLIPLoader", "clip_name")
|
||||
}
|
||||
"unet" | "diffusion_models" | "diffusion_models_files" => {
|
||||
combo_choices_from_node_input(object_info, "UNETLoader", "unet_name")
|
||||
}
|
||||
"controlnet" | "controlnets" => {
|
||||
combo_choices_from_node_input(object_info, "ControlNetLoader", "control_net_name")
|
||||
}
|
||||
"upscale_models" => {
|
||||
combo_choices_from_node_input(object_info, "UpscaleModelLoader", "model_name")
|
||||
}
|
||||
"embeddings" => combo_choices_from_node_input(object_info, "EmbeddingPicker", "embedding"),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn comfy_combo_input(options: &[String], fallback: &str) -> Value {
|
||||
let choices = if options.is_empty() {
|
||||
vec![fallback.to_string()]
|
||||
@@ -850,7 +560,7 @@ fn native_model_folder_payload_or_fallback(value: Value) -> Value {
|
||||
}
|
||||
}
|
||||
|
||||
fn model_payload_items_from_names(names: Vec<String>, path_index: i64) -> Vec<Value> {
|
||||
pub(super) fn model_payload_items_from_names(names: Vec<String>, path_index: i64) -> Vec<Value> {
|
||||
names
|
||||
.into_iter()
|
||||
.map(|name| json!({ "name": name, "pathIndex": path_index }))
|
||||
@@ -5537,6 +5247,7 @@ pub(super) fn parse_workflow_cookie(raw: &str) -> Option<&str> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::models::workflow_folder_models;
|
||||
|
||||
#[test]
|
||||
fn video_model_folder_does_not_use_route_video_models() {
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
use super::model_payload_items_from_names;
|
||||
use crate::error::AppResult;
|
||||
use crate::AppState;
|
||||
use serde_json::Value;
|
||||
|
||||
pub(super) fn is_exact_workflow_model(value: &str) -> bool {
|
||||
let normalized = value.trim();
|
||||
!normalized.is_empty()
|
||||
&& !normalized.to_ascii_lowercase().starts_with("re:")
|
||||
&& !normalized.contains('*')
|
||||
&& !normalized.contains('?')
|
||||
}
|
||||
|
||||
pub(super) fn is_image_workflow_model(value: &str) -> bool {
|
||||
if !is_exact_workflow_model(value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let normalized = value.trim().to_ascii_lowercase();
|
||||
let non_image_keywords = [
|
||||
"tts",
|
||||
"voice",
|
||||
"audio",
|
||||
"embedding",
|
||||
"embed",
|
||||
"seedance",
|
||||
"wan2",
|
||||
"t2v",
|
||||
"i2v",
|
||||
"flf2v",
|
||||
"seaweed",
|
||||
"3d",
|
||||
"hitem3d",
|
||||
"hyper3d",
|
||||
];
|
||||
if non_image_keywords
|
||||
.iter()
|
||||
.any(|keyword| normalized.contains(keyword))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let image_keywords = [
|
||||
"gpt-image",
|
||||
"dall-e",
|
||||
"dalle",
|
||||
"t2i",
|
||||
"i2i",
|
||||
"midjourney",
|
||||
"recraft",
|
||||
"kolors",
|
||||
"cogview",
|
||||
"seedream",
|
||||
"sdxl",
|
||||
"stable-diffusion",
|
||||
"stablediffusion",
|
||||
"pixart",
|
||||
"playground",
|
||||
"firefly",
|
||||
"imagen",
|
||||
"flux",
|
||||
"image",
|
||||
"img",
|
||||
"canvas",
|
||||
"painting",
|
||||
"draw",
|
||||
];
|
||||
image_keywords
|
||||
.iter()
|
||||
.any(|keyword| normalized.contains(keyword))
|
||||
}
|
||||
|
||||
pub(super) fn is_video_workflow_model(value: &str) -> bool {
|
||||
let normalized = value.trim().to_ascii_lowercase();
|
||||
[
|
||||
"seedance", "wan2", "t2v", "i2v", "flf2v", "video", "seaweed",
|
||||
]
|
||||
.iter()
|
||||
.any(|keyword| normalized.contains(keyword))
|
||||
}
|
||||
|
||||
pub(super) fn is_audio_workflow_model(value: &str) -> bool {
|
||||
let normalized = value.trim().to_ascii_lowercase();
|
||||
["tts", "voice", "audio", "speech"]
|
||||
.iter()
|
||||
.any(|keyword| normalized.contains(keyword))
|
||||
}
|
||||
|
||||
pub(super) fn is_embedding_workflow_model(value: &str) -> bool {
|
||||
let normalized = value.trim().to_ascii_lowercase();
|
||||
normalized.contains("embedding") || normalized.contains("embed")
|
||||
}
|
||||
|
||||
pub(super) fn is_3d_workflow_model(value: &str) -> bool {
|
||||
let normalized = value.trim().to_ascii_lowercase();
|
||||
["3d", "hitem3d", "hyper3d"]
|
||||
.iter()
|
||||
.any(|keyword| normalized.contains(keyword))
|
||||
}
|
||||
|
||||
pub(super) fn is_vision_workflow_model(value: &str) -> bool {
|
||||
let normalized = value.trim().to_ascii_lowercase();
|
||||
["vision", "vl", "omni", "ui-tars"]
|
||||
.iter()
|
||||
.any(|keyword| normalized.contains(keyword))
|
||||
&& !is_image_workflow_model(value)
|
||||
&& !is_video_workflow_model(value)
|
||||
&& !is_embedding_workflow_model(value)
|
||||
}
|
||||
|
||||
pub(super) fn is_chat_workflow_model(value: &str) -> bool {
|
||||
is_exact_workflow_model(value)
|
||||
&& !is_image_workflow_model(value)
|
||||
&& !is_video_workflow_model(value)
|
||||
&& !is_audio_workflow_model(value)
|
||||
&& !is_embedding_workflow_model(value)
|
||||
&& !is_3d_workflow_model(value)
|
||||
}
|
||||
|
||||
pub(super) async fn workflow_route_models(state: &AppState) -> AppResult<Vec<String>> {
|
||||
let rows = sqlx::query_scalar::<_, String>(
|
||||
"SELECT DISTINCT model_pattern
|
||||
FROM token_routes
|
||||
WHERE COALESCE(enabled, 1) = 1
|
||||
AND model_pattern IS NOT NULL
|
||||
AND model_pattern <> ''
|
||||
ORDER BY model_pattern COLLATE NOCASE",
|
||||
)
|
||||
.fetch_all(&*state.db)
|
||||
.await?;
|
||||
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
Ok(rows
|
||||
.into_iter()
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| is_exact_workflow_model(value))
|
||||
.filter(|value| seen.insert(value.clone()))
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub(super) fn image_model_choices(models: &[String]) -> Vec<String> {
|
||||
let image_models = models
|
||||
.iter()
|
||||
.filter(|model| is_image_workflow_model(model))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
if image_models.is_empty() {
|
||||
models.to_vec()
|
||||
} else {
|
||||
image_models
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn workflow_folder_models(folder: &str, models: &[String]) -> Vec<String> {
|
||||
let normalized = folder.trim().to_ascii_lowercase();
|
||||
match normalized.as_str() {
|
||||
"boosapi_models" | "boosapi" | "api_models" => models.to_vec(),
|
||||
"image_models" => image_model_choices(models),
|
||||
"chat_models" => models
|
||||
.iter()
|
||||
.filter(|model| is_chat_workflow_model(model))
|
||||
.cloned()
|
||||
.collect(),
|
||||
"clip" => models.to_vec(),
|
||||
// Comfy's video_models folder is a native Comfy API model folder. Keep it
|
||||
// separate from BoosAPI route models so video apps submit real Comfy API
|
||||
// model names instead of OpenAI-compatible route aliases.
|
||||
"video_models" => Vec::new(),
|
||||
"audio_tts_models" => models
|
||||
.iter()
|
||||
.filter(|model| is_audio_workflow_model(model))
|
||||
.cloned()
|
||||
.collect(),
|
||||
"embeddings" => models
|
||||
.iter()
|
||||
.filter(|model| is_embedding_workflow_model(model))
|
||||
.cloned()
|
||||
.collect(),
|
||||
"3d_models" => models
|
||||
.iter()
|
||||
.filter(|model| is_3d_workflow_model(model))
|
||||
.cloned()
|
||||
.collect(),
|
||||
"vision_models" => models
|
||||
.iter()
|
||||
.filter(|model| is_vision_workflow_model(model))
|
||||
.cloned()
|
||||
.collect(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn workflow_folder_is_api_only(folder: &str) -> bool {
|
||||
matches!(
|
||||
folder.trim().to_ascii_lowercase().as_str(),
|
||||
"boosapi" | "boosapi_models" | "api_models"
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn workflow_folder_has_route_models(folder: &str) -> bool {
|
||||
matches!(
|
||||
folder.trim().to_ascii_lowercase().as_str(),
|
||||
"boosapi"
|
||||
| "boosapi_models"
|
||||
| "api_models"
|
||||
| "image_models"
|
||||
| "chat_models"
|
||||
| "audio_tts_models"
|
||||
| "embeddings"
|
||||
| "3d_models"
|
||||
| "vision_models"
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn workflow_folder_route_model_items(folder: &str, route_models: &[String]) -> Vec<Value> {
|
||||
model_payload_items_from_names(workflow_folder_models(folder, route_models), 0)
|
||||
}
|
||||
|
||||
pub(super) fn combo_choices_from_node_input(
|
||||
object_info: &Value,
|
||||
node_type: &str,
|
||||
input_name: &str,
|
||||
) -> Vec<String> {
|
||||
object_info
|
||||
.get(node_type)
|
||||
.and_then(|node| {
|
||||
node.pointer(&format!("/input/required/{input_name}"))
|
||||
.or_else(|| node.pointer(&format!("/input/optional/{input_name}")))
|
||||
})
|
||||
.and_then(Value::as_array)
|
||||
.and_then(|input| input.first())
|
||||
.and_then(Value::as_array)
|
||||
.map(|choices| {
|
||||
choices
|
||||
.iter()
|
||||
.filter_map(Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty() && *value != "None")
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(super) fn combo_fields_from_node(object_info: &Value, node_type: &str) -> Vec<(String, Vec<String>)> {
|
||||
let mut fields = Vec::new();
|
||||
let Some(node) = object_info.get(node_type) else {
|
||||
return fields;
|
||||
};
|
||||
for section in ["required", "optional"] {
|
||||
if let Some(inputs) = node
|
||||
.pointer(&format!("/input/{section}"))
|
||||
.and_then(Value::as_object)
|
||||
{
|
||||
for (name, value) in inputs {
|
||||
let Some(choices) = value
|
||||
.as_array()
|
||||
.and_then(|input| input.first())
|
||||
.and_then(Value::as_array)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let choices = choices
|
||||
.iter()
|
||||
.filter_map(Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty() && *value != "None")
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<Vec<_>>();
|
||||
fields.push((name.clone(), choices));
|
||||
}
|
||||
}
|
||||
}
|
||||
fields
|
||||
}
|
||||
|
||||
pub(super) fn native_folder_models_from_object_info(folder: &str, object_info: &Value) -> Vec<String> {
|
||||
let normalized = folder.trim().to_ascii_lowercase();
|
||||
match normalized.as_str() {
|
||||
"checkpoints" | "image_models" => {
|
||||
combo_choices_from_node_input(object_info, "CheckpointLoaderSimple", "ckpt_name")
|
||||
}
|
||||
"loras" => combo_choices_from_node_input(object_info, "LoraLoader", "lora_name"),
|
||||
"vae" => combo_choices_from_node_input(object_info, "VAELoader", "vae_name"),
|
||||
"clip" | "clip_models" | "text_encoders" => {
|
||||
combo_choices_from_node_input(object_info, "CLIPLoader", "clip_name")
|
||||
}
|
||||
"unet" | "diffusion_models" | "diffusion_models_files" => {
|
||||
combo_choices_from_node_input(object_info, "UNETLoader", "unet_name")
|
||||
}
|
||||
"controlnet" | "controlnets" => {
|
||||
combo_choices_from_node_input(object_info, "ControlNetLoader", "control_net_name")
|
||||
}
|
||||
"upscale_models" => {
|
||||
combo_choices_from_node_input(object_info, "UpscaleModelLoader", "model_name")
|
||||
}
|
||||
"embeddings" => combo_choices_from_node_input(object_info, "EmbeddingPicker", "embedding"),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user