|
@@ -36,15 +36,30 @@ const configSchema = {
|
|
|
description: 'A basic credential holding the sdcpp-restapi username and password. Listing models needs one',
|
|
description: 'A basic credential holding the sdcpp-restapi username and password. Listing models needs one',
|
|
|
dynamicOptions: { source: 'credentials', filter: { type: ['sdcpp', 'basic'] } }
|
|
dynamicOptions: { source: 'credentials', filter: { type: ['sdcpp', 'basic'] } }
|
|
|
},
|
|
},
|
|
|
|
|
+ source: {
|
|
|
|
|
+ type: 'string', title: 'Model',
|
|
|
|
|
+ enum: ['choose', 'loaded'],
|
|
|
|
|
+ enumLabels: ['Pick one from the server', 'Whatever is loaded right now'],
|
|
|
|
|
+ default: 'choose',
|
|
|
|
|
+ description: 'Pick one, or take the model the server currently has loaded. Taking the loaded one keeps a workflow portable between servers, and means it never spends minutes swapping a model just to match what a node was set to'
|
|
|
|
|
+ },
|
|
|
|
|
+ includeComponents: {
|
|
|
|
|
+ type: 'boolean', title: 'Include Components',
|
|
|
|
|
+ default: false,
|
|
|
|
|
+ showWhen: { field: 'source', value: 'loaded' },
|
|
|
|
|
+ description: 'Also supply the VAE, CLIP, T5, LLM and TAESD the server has loaded, not only the model name. Only useful for a diffusion model, which loads its parts separately'
|
|
|
|
|
+ },
|
|
|
modelType: {
|
|
modelType: {
|
|
|
type: 'string', title: 'Kind',
|
|
type: 'string', title: 'Kind',
|
|
|
|
|
+ showWhen: { field: 'source', value: 'choose' },
|
|
|
enum: ['checkpoint', 'diffusion', 'vae', 'lora', 'clip', 't5', 'embedding',
|
|
enum: ['checkpoint', 'diffusion', 'vae', 'lora', 'clip', 't5', 'embedding',
|
|
|
'controlnet', 'llm', 'esrgan', 'taesd', 'motion_module', 'adetailer'],
|
|
'controlnet', 'llm', 'esrgan', 'taesd', 'motion_module', 'adetailer'],
|
|
|
default: 'checkpoint',
|
|
default: 'checkpoint',
|
|
|
description: 'Which kind of model to choose from. esrgan is the upscaler kind'
|
|
description: 'Which kind of model to choose from. esrgan is the upscaler kind'
|
|
|
},
|
|
},
|
|
|
modelName: {
|
|
modelName: {
|
|
|
- type: 'string', title: 'Model',
|
|
|
|
|
|
|
+ type: 'string', title: 'Choose',
|
|
|
|
|
+ showWhen: { field: 'source', value: 'choose' },
|
|
|
description: 'Chosen from what the server has. Change the Kind above to list a different sort',
|
|
description: 'Chosen from what the server has. Change the Kind above to list a different sort',
|
|
|
dynamicOptions: {
|
|
dynamicOptions: {
|
|
|
source: 'node',
|
|
source: 'node',
|
|
@@ -73,6 +88,8 @@ const outputSchema = {
|
|
|
_config: { type: 'object', description: 'The setting this supplies, as the engine consumes it' },
|
|
_config: { type: 'object', description: 'The setting this supplies, as the engine consumes it' },
|
|
|
modelName: { type: 'string', description: 'The chosen model' },
|
|
modelName: { type: 'string', description: 'The chosen model' },
|
|
|
modelType: { type: 'string' },
|
|
modelType: { type: 'string' },
|
|
|
|
|
+ architecture: { type: 'string', description: 'What the server detected, when following the loaded model' },
|
|
|
|
|
+ loadedComponents: { type: 'object', description: 'The components the server has loaded, when following it' },
|
|
|
setting: { type: 'string', description: 'Which setting it was supplied as' },
|
|
setting: { type: 'string', description: 'Which setting it was supplied as' },
|
|
|
models: { type: 'array', description: 'Everything of this kind the server has. Only filled when listing' }
|
|
models: { type: 'array', description: 'Everything of this kind the server has. Only filled when listing' }
|
|
|
}
|
|
}
|
|
@@ -98,6 +115,19 @@ const SETTING_FOR_KIND = {
|
|
|
adetailer: 'adetailer'
|
|
adetailer: 'adetailer'
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+// What the server calls each loaded component, and the setting it fills in on a
|
|
|
|
|
+// node that loads one. Reported under several spellings across versions, so each
|
|
|
|
|
+// setting lists the ones seen.
|
|
|
|
|
+const COMPONENT_SETTINGS = [
|
|
|
|
|
+ { setting: 'vae', keys: ['vae', 'vae_model'] },
|
|
|
|
|
+ { setting: 'clipL', keys: ['clip_l', 'clipL'] },
|
|
|
|
|
+ { setting: 'clipG', keys: ['clip_g', 'clipG'] },
|
|
|
|
|
+ { setting: 't5xxl', keys: ['t5xxl', 't5'] },
|
|
|
|
|
+ { setting: 'llm', keys: ['llm'] },
|
|
|
|
|
+ { setting: 'taesd', keys: ['taesd'] },
|
|
|
|
|
+ { setting: 'controlnet', keys: ['controlnet', 'controlnet_model'] }
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
// The server groups models by kind under its own key names.
|
|
// The server groups models by kind under its own key names.
|
|
|
const GROUP_FOR_KIND = {
|
|
const GROUP_FOR_KIND = {
|
|
|
checkpoint: 'checkpoints',
|
|
checkpoint: 'checkpoints',
|
|
@@ -256,16 +286,48 @@ async function execute(config, input, context) {
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const modelName = String(config.modelName || '').trim();
|
|
|
|
|
- if (!modelName) {
|
|
|
|
|
- throw new Error('SD.cpp Model: choose a model. Pick one from the list, which is read ' +
|
|
|
|
|
- 'from the server named above');
|
|
|
|
|
|
|
+ let modelName;
|
|
|
|
|
+ let architecture = '';
|
|
|
|
|
+ let loadedComponents = {};
|
|
|
|
|
+ const values = {};
|
|
|
|
|
+
|
|
|
|
|
+ if ((config.source || 'choose') === 'loaded') {
|
|
|
|
|
+ // /health is unauthenticated, so this works before any credential is
|
|
|
|
|
+ // set up - useful when the point is to follow whatever the server has.
|
|
|
|
|
+ const health = readHealth(server, Math.min(timeout, 15000));
|
|
|
|
|
+ if (health.model_loaded !== true || !health.model_name) {
|
|
|
|
|
+ throw new Error('SD.cpp Model: this node is set to use the model the server has ' +
|
|
|
|
|
+ 'loaded, but ' + server + ' has none. Load one first, or choose a model here');
|
|
|
|
|
+ }
|
|
|
|
|
+ modelName = health.model_name;
|
|
|
|
|
+ architecture = health.model_architecture || '';
|
|
|
|
|
+ loadedComponents = health.loaded_components || {};
|
|
|
|
|
+
|
|
|
|
|
+ if (config.includeComponents === true) {
|
|
|
|
|
+ for (let i = 0; i < COMPONENT_SETTINGS.length; i++) {
|
|
|
|
|
+ const entry = COMPONENT_SETTINGS[i];
|
|
|
|
|
+ for (let k = 0; k < entry.keys.length; k++) {
|
|
|
|
|
+ const found = loadedComponents[entry.keys[k]];
|
|
|
|
|
+ // A component the server does not have comes back empty or
|
|
|
|
|
+ // absent; supplying that would blank the target's own value.
|
|
|
|
|
+ if (found) {
|
|
|
|
|
+ values[entry.setting] = found;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ modelName = String(config.modelName || '').trim();
|
|
|
|
|
+ if (!modelName) {
|
|
|
|
|
+ throw new Error('SD.cpp Model: choose a model. Pick one from the list, which is read ' +
|
|
|
|
|
+ 'from the server named above');
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const setting = String(config.setting || '').trim() ||
|
|
const setting = String(config.setting || '').trim() ||
|
|
|
SETTING_FOR_KIND[modelType] || 'modelName';
|
|
SETTING_FOR_KIND[modelType] || 'modelName';
|
|
|
|
|
|
|
|
- const values = {};
|
|
|
|
|
values[setting] = modelName;
|
|
values[setting] = modelName;
|
|
|
|
|
|
|
|
smartbotic.log.info('SD.cpp Model: supplying ' + setting + ' = ' + modelName);
|
|
smartbotic.log.info('SD.cpp Model: supplying ' + setting + ' = ' + modelName);
|
|
@@ -276,6 +338,8 @@ async function execute(config, input, context) {
|
|
|
_config: values,
|
|
_config: values,
|
|
|
modelName: modelName,
|
|
modelName: modelName,
|
|
|
modelType: modelType,
|
|
modelType: modelType,
|
|
|
|
|
+ architecture: architecture,
|
|
|
|
|
+ loadedComponents: loadedComponents,
|
|
|
setting: setting,
|
|
setting: setting,
|
|
|
models: []
|
|
models: []
|
|
|
};
|
|
};
|