V 2.0.0 - Universal api #74 - user files

This commit is contained in:
DESKTOP-TVBJISQ\Primere
2026-03-10 08:35:51 +01:00
parent a2d1822874
commit 00392a4c83
8 changed files with 118 additions and 8 deletions
+67
View File
@@ -645,6 +645,73 @@ class PrimereModelConceptSelector:
zimage_model, zimage_clip, zimage_vae
)
class PrimereControlledSamplersSteps:
CATEGORY = TREE_DASHBOARD
RETURN_TYPES = ("STRING", comfy.samplers.KSampler.SAMPLERS, comfy.samplers.KSampler.SCHEDULERS, "INT", "FLOAT")
RETURN_NAMES = ("MODEL_CONCEPT", "SAMPLER_NAME", "SCHEDULER_NAME", "STEPS", "CFG")
FUNCTION = "get_controlledsampler_step"
kolors_schedulers = ["EulerDiscreteScheduler", "EulerAncestralDiscreteScheduler", "DPMSolverMultistepScheduler", "DPMSolverMultistepScheduler_SDE_karras", "UniPCMultistepScheduler", "DEISMultistepScheduler"]
sana_schedulers = ['flow_dpm-solver']
UNETLIST = PrimereModelConceptSelector.UNETLIST
DIFFUSIONLIST = PrimereModelConceptSelector.DIFFUSIONLIST
TEXT_ENCODERS = PrimereModelConceptSelector.TEXT_ENCODERS
GGUFLIST = PrimereModelConceptSelector.GGUFLIST
VAELIST = PrimereModelConceptSelector.VAELIST
CLIPLIST = PrimereModelConceptSelector.CLIPLIST
MODELLIST = PrimereModelConceptSelector.MODELLIST
TEXT_ENCODERS_PATHS = PrimereModelConceptSelector.TEXT_ENCODERS_PATHS
CONCEPT_LIST = PrimereModelConceptSelector.CONCEPT_LIST
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model_concept": ("STRING", {"default": None, "forceInput": True}),
"concepts": (["Auto"] + cls.CONCEPT_LIST,),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
"scheduler_name": (cls.sana_schedulers + cls.kolors_schedulers + comfy.samplers.KSampler.SCHEDULERS,),
"steps": ("INT", {"default": 12, "min": 1, "max": 1000, "step": 1}),
"cfg": ("FLOAT", {"default": 7, "min": 0.1, "max": 100, "step": 0.01}),
"vae": (["None"] + cls.VAELIST,),
"encoder_1": (["None"] + cls.TEXT_ENCODERS + cls.CLIPLIST + cls.TEXT_ENCODERS_PATHS,),
"encoder_2": (["None"] + cls.TEXT_ENCODERS + cls.CLIPLIST + cls.TEXT_ENCODERS_PATHS,),
"encoder_3": (["None"] + cls.TEXT_ENCODERS + cls.CLIPLIST + cls.TEXT_ENCODERS_PATHS,),
"sampler": (["None"] + ["custom_advanced", "ksampler"], {"default": "ksampler"}),
"guidance": ('FLOAT', {"default": 3.5, "min": 0.0, "max": 100.0, "step": 0.1}),
"weight_dtype": (["None"] + ["Auto", "default", "fp16", "bf16", "fp32", "fp8_e4m3fn", "fp8_e5m2"], {"default": "default"}),
"precision": (["None"] + ['fp32', 'fp16', 'quant8', 'quant4'], {"default": "fp16"}),
"use_speed_lora": ("BOOLEAN", {"default": False, "label_on": "Seed lora ON", "label_off": "Seed lora OFF"}),
"speed_lora": (["None"],),
"speed_lora_version": ([1.0, 1.1, 2.0], {"default": 2.0}),
"speed_lora_precision": ("BOOLEAN", {"default": True, "label_on": "FP32", "label_off": "BF16"}),
"speed_lora_step": ([4, 6, 8, 10, 12, 16], {"default": 8}),
"speed_lora_strength": ("FLOAT", {"default": 1.00, "min": -20.00, "max": 20.00, "step": 0.01}),
"use_srpo_lora": ("BOOLEAN", {"default": False, "label_on": "Use SRPO Lora", "label_off": "Ignore SRPO Lora"}),
"use_srpo_svdq_lora": ("BOOLEAN", {"default": False, "label_on": "Use SRPO-NUNCHAKU Lora", "label_off": "Ignore SRPO-NUNCHAKU Lora"}),
"srpo_lora_type": (["R&Q", "RockerBOO", "oficial", "adaptive"], {"default": "oficial"}),
"srpo_lora_rank": ([8, 16, 32, 64, 128, 256], {"default": 8}),
"srpo_lora_strength": ("FLOAT", {"default": 1, "min": -20.000, "max": 20.000, "step": 0.001}),
"use_nunchaku_lora": ("BOOLEAN", {"default": False, "label_on": "Use nunchaku Lora", "label_off": "Ignore nunchaku Lora"}),
"nunchaku_lora_type": (["kontext_deblur", "kontext_face_detailer", "anything_extracted"], {"default": "anything_extracted"}),
"nunchaku_lora_rank": ([64, 256], {"default": 64}),
"nunchaku_lora_strength": ("FLOAT", {"default": 1, "min": -20.000, "max": 20.000, "step": 0.001}),
"pixart_refiner_model": (["None"] + cls.MODELLIST,),
"refiner_sampler": (comfy.samplers.KSampler.SAMPLERS, {"default": "dpmpp_2m"}),
"refiner_scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"default": "normal"}),
"refiner_cfg": ("FLOAT", {"default": 2.0, "min": 0.1, "max": 100, "step": 0.01}),
"refiner_steps": ("INT", {"default": 22, "min": 10, "max": 30, "step": 1}),
"refiner_start": ("INT", {"default": 12, "min": 1, "max": 1000, "step": 1}),
"refiner_denoise": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 1.0, "step": 0.01}),
"refiner_ignore_prompt": ("BOOLEAN", {"default": False, "label_on": "Send prompt to refiner", "label_off": "Ignore prompt"}),
}
}
def get_controlledsampler_step(self, model_concept, sampler_name, scheduler_name, steps=12, cfg=7, **kwargs):
return model_concept, sampler_name, scheduler_name, steps, round(cfg, 2)
class PrimereConceptDataTuple:
RETURN_TYPES = ("TUPLE",)
RETURN_NAMES = ("CONCEPT_DATA",)
+3 -1
View File
@@ -33,7 +33,9 @@ class PrimereApiProcessor:
FUNCTION = "process_uniapi"
API_RESULT = api_helper.get_api_config("apiconfig.json")
API_SCHEMAS_RAW = utility.json2tuple(os.path.join(PRIMERE_ROOT, 'front_end', 'api_schemas.json'))
_schema_file = os.path.join(PRIMERE_ROOT, 'front_end', 'api_schemas.json')
_schema_example = os.path.join(PRIMERE_ROOT, 'front_end', 'api_schemas.example.json')
API_SCHEMAS_RAW = utility.json2tuple(_schema_file if Path(_schema_file).is_file() else _schema_example)
API_SCHEMA_REGISTRY = api_schema_registry.normalize_registry(API_SCHEMAS_RAW)
@classmethod
+2
View File
@@ -70,6 +70,7 @@ for subdirs in valid_FElist:
NODE_CLASS_MAPPINGS = {
"PrimereSamplersSteps": Dashboard.PrimereSamplersSteps,
"PrimereControlledSamplersSteps": Dashboard.PrimereControlledSamplersSteps,
"PrimereVAE": Dashboard.PrimereVAE,
"PrimereCKPT": Dashboard.PrimereCKPT,
"PrimereVAELoader": Dashboard.PrimereVAELoader,
@@ -148,6 +149,7 @@ NODE_CLASS_MAPPINGS = {
NODE_DISPLAY_NAME_MAPPINGS = {
"PrimereSamplersSteps": "Primere Samplers & Steps & Cfg",
"PrimereControlledSamplersSteps": "Primere Controlled Sampler Setting",
"PrimereVAE": "Primere VAE Selector",
"PrimereCKPT": "Primere CKPT Selector",
"PrimereVAELoader": "Primere VAE Loader",
+3
View File
@@ -7,6 +7,9 @@ def get_api_config(name: str) -> dict:
fp = os.path.join(path, name)
config_json = utility.json2tuple(fp)
if not config_json:
return {}
for k, v in config_json.items():
match k:
case "OpenAI":
+7 -1
View File
@@ -521,4 +521,10 @@ async def primere_prompt_saver(request):
PromptServer.instance.send_sync("PromptDataSaveResponse", False)
else:
PromptServer.instance.send_sync("PromptDataSaveResponse", False)
return web.json_response({})
return web.json_response({})
routes17 = PromptServer.instance.routes
@routes17.get('/primere_apiconfig_check')
async def primere_apiconfig_check(request):
config_path = os.path.join(PRIMERE_ROOT, 'json', 'apiconfig.json')
return web.json_response({"exists": os.path.isfile(config_path)})
+1 -1
View File
@@ -82,7 +82,7 @@ export function showToast(status, message) {
const textEl = document.createElement("span");
textEl.textContent = message;
Object.assign(textEl.style, { flex: "1", lineHeight: "1.5", wordBreak: "break-all" });
Object.assign(textEl.style, { flex: "1", lineHeight: "1.5", wordBreak: "normal", overflowWrap: "break-word" });
const closeBtn = document.createElement("button");
closeBtn.textContent = "✕";
+35 -5
View File
@@ -3,9 +3,11 @@ import { applyPrimereButtonStyle, showToast } from "./frontend_helper.js";
const TARGET_NODE_NAME = "PrimereApiProcessor";
const SCHEMA_URL = new URL("/extensions/ComfyUI_Primere_Nodes/api_schemas.json", import.meta.url).href;
const SCHEMA_EXAMPLE_URL = new URL("/extensions/ComfyUI_Primere_Nodes/api_schemas.example.json", import.meta.url).href;
let schemaCache = null;
let schemaPromise = null;
let apiconfigChecked = false;
async function loadSchemas() {
if (schemaCache) {
@@ -16,7 +18,12 @@ async function loadSchemas() {
schemaPromise = fetch(SCHEMA_URL)
.then((response) => {
if (!response.ok) {
throw new Error(`Cannot load schema file (${response.status})`);
return fetch(SCHEMA_EXAMPLE_URL).then((fallback) => {
if (!fallback.ok) {
throw new Error(`Cannot load schema file (${fallback.status})`);
}
return fallback.json();
});
}
return response.json();
})
@@ -25,7 +32,7 @@ async function loadSchemas() {
return schemaCache;
})
.catch((error) => {
console.error("[Primere UniApi] Failed to load json/api_schemas.json", error);
console.error("[Primere UniApi] Failed to load api_schemas.json and api_schemas.example.json", error);
return {};
});
}
@@ -301,6 +308,15 @@ async function initializeUniApiNode(node) {
const refreshBtn = node.addWidget("button", "↺ Reload API Schema", null, async () => {
schemaCache = null;
schemaPromise = null;
let apiconfigMissing = false;
try {
const r = await fetch("/primere_apiconfig_check");
const data = r.ok ? await r.json() : null;
if (data && !data.exists) {
apiconfigMissing = true;
showToast("error", "API config file (json/apiconfig.json) not found. Create it with your API keys before using this node. See the manual for details.");
}
} catch (_) {}
try {
const freshRegistry = await loadSchemas();
if (!freshRegistry || Object.keys(freshRegistry).length === 0) {
@@ -309,9 +325,11 @@ async function initializeUniApiNode(node) {
}
updateServiceWidget(node, freshRegistry);
updateParameterWidgets(node, freshRegistry);
const providerCount = Object.keys(freshRegistry).length;
const serviceCount = Object.values(freshRegistry).reduce((sum, p) => sum + Object.keys(p).length, 0);
showToast("success", `API Schema reloaded successfully. ${providerCount} provider(s), ${serviceCount} service(s) loaded.`);
if (!apiconfigMissing) {
const providerCount = Object.keys(freshRegistry).length;
const serviceCount = Object.values(freshRegistry).reduce((sum, p) => sum + Object.keys(p).length, 0);
showToast("success", `API Schema reloaded successfully. ${providerCount} provider(s), ${serviceCount} service(s) loaded.`);
}
} catch (error) {
showToast("error", `API Schema reload failed. ${error.message}`);
}
@@ -350,6 +368,18 @@ async function initializeUniApiNode(node) {
};
}
if (!apiconfigChecked) {
apiconfigChecked = true;
fetch("/primere_apiconfig_check")
.then((r) => r.ok ? r.json() : null)
.then((data) => {
if (data && !data.exists) {
showToast("error", "API config file (json/apiconfig.json) not found. Create it with your API keys before using this node. See the manual for details.");
}
})
.catch(() => {});
}
const schemaRegistry = await loadSchemas();
updateServiceWidget(node, schemaRegistry);
updateParameterWidgets(node, schemaRegistry);