|
@@ -2,8 +2,8 @@
|
|
|
* @node sdcpp-model
|
|
* @node sdcpp-model
|
|
|
* @name SD.cpp Model
|
|
* @name SD.cpp Model
|
|
|
* @category sdcpp
|
|
* @category sdcpp
|
|
|
- * @version 1.0.0
|
|
|
|
|
- * @description List, load or unload models on an sdcpp-restapi server, and read its health
|
|
|
|
|
|
|
+ * @version 2.0.0
|
|
|
|
|
+ * @description Pick a model from what the server has, and hand it to whichever node needs one
|
|
|
* @icon layers
|
|
* @icon layers
|
|
|
*/
|
|
*/
|
|
|
|
|
|
|
@@ -11,101 +11,94 @@ const configSchema = {
|
|
|
type: 'object',
|
|
type: 'object',
|
|
|
properties: {
|
|
properties: {
|
|
|
serverUrl: {
|
|
serverUrl: {
|
|
|
- type: 'string',
|
|
|
|
|
- title: 'Server URL',
|
|
|
|
|
- description: 'Base address of the sdcpp-restapi server',
|
|
|
|
|
|
|
+ type: 'string', title: 'Server URL',
|
|
|
|
|
+ description: 'Base address of the sdcpp-restapi server. The list of models is read from here',
|
|
|
default: 'http://localhost:8077'
|
|
default: 'http://localhost:8077'
|
|
|
},
|
|
},
|
|
|
credentialId: {
|
|
credentialId: {
|
|
|
- type: 'string',
|
|
|
|
|
- title: 'Credential',
|
|
|
|
|
- description: 'A basic credential holding the sdcpp-restapi username and password. Not needed for the health operation, which the server leaves open',
|
|
|
|
|
- dynamicOptions: {
|
|
|
|
|
- source: 'credentials',
|
|
|
|
|
- filter: { type: ['basic'] }
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- operation: {
|
|
|
|
|
- type: 'string',
|
|
|
|
|
- title: 'Operation',
|
|
|
|
|
- enum: ['health', 'list', 'load', 'unload', 'loadUpscaler', 'unloadUpscaler', 'refresh'],
|
|
|
|
|
- default: 'health',
|
|
|
|
|
- description: 'health reports what is loaded now, list shows what is available on disk, load swaps the main model slot, loadUpscaler fills the separate upscaler slot, refresh rescans the model directories'
|
|
|
|
|
- },
|
|
|
|
|
- modelName: {
|
|
|
|
|
- type: 'string',
|
|
|
|
|
- title: 'Model Name',
|
|
|
|
|
- description: 'File name of the model to load, relative to its type directory. Required for load and loadUpscaler'
|
|
|
|
|
|
|
+ type: 'string', title: 'Credential',
|
|
|
|
|
+ description: 'A basic credential holding the sdcpp-restapi username and password. Listing models needs one',
|
|
|
|
|
+ dynamicOptions: { source: 'credentials', filter: { type: ['basic'] } }
|
|
|
},
|
|
},
|
|
|
modelType: {
|
|
modelType: {
|
|
|
- type: 'string',
|
|
|
|
|
- title: 'Model Type',
|
|
|
|
|
- enum: ['', 'checkpoint', 'diffusion'],
|
|
|
|
|
- default: '',
|
|
|
|
|
- description: 'checkpoint bundles the U-Net, CLIP and VAE together and suits SD1, SD2 and SDXL. diffusion holds only the U-Net or DiT weights and needs its components named separately, which is how Flux, SD3, Qwen, Wan and Z-Image load'
|
|
|
|
|
- },
|
|
|
|
|
- vae: { type: 'string', title: 'VAE', description: 'Component file name, for a diffusion model' },
|
|
|
|
|
- clipL: { type: 'string', title: 'CLIP-L', description: 'Component file name' },
|
|
|
|
|
- clipG: { type: 'string', title: 'CLIP-G', description: 'Component file name' },
|
|
|
|
|
- t5xxl: { type: 'string', title: 'T5-XXL', description: 'Component file name' },
|
|
|
|
|
- llm: { type: 'string', title: 'LLM', description: 'Component file name, used by Z-Image, Qwen, Anima and Flux2' },
|
|
|
|
|
- taesd: { type: 'string', title: 'TAESD', description: 'Tiny autoencoder used to render progress previews, not final output' },
|
|
|
|
|
- controlnet: { type: 'string', title: 'ControlNet', description: 'Component file name' },
|
|
|
|
|
- options: {
|
|
|
|
|
- type: 'object',
|
|
|
|
|
- title: 'Load Options',
|
|
|
|
|
- description: 'Extra load options passed straight through, such as flash_attn, enable_mmap, weight_type, stream_layers or max_vram. See /options/descriptions on the server for the full list'
|
|
|
|
|
|
|
+ type: 'string', title: 'Kind',
|
|
|
|
|
+ enum: ['checkpoint', 'diffusion', 'vae', 'lora', 'clip', 't5', 'embedding',
|
|
|
|
|
+ 'controlnet', 'llm', 'esrgan', 'taesd', 'motion_module', 'adetailer'],
|
|
|
|
|
+ default: 'checkpoint',
|
|
|
|
|
+ description: 'Which kind of model to choose from. esrgan is the upscaler kind'
|
|
|
},
|
|
},
|
|
|
- listType: {
|
|
|
|
|
- type: 'string',
|
|
|
|
|
- title: 'List Type',
|
|
|
|
|
- enum: ['', 'checkpoint', 'diffusion', 'vae', 'lora', 'clip', 't5', 'embedding',
|
|
|
|
|
- 'controlnet', 'llm', 'esrgan', 'taesd', 'motion_module', 'adetailer'],
|
|
|
|
|
- default: '',
|
|
|
|
|
- description: 'For the list operation, restrict the result to one kind of model. Empty lists all of them'
|
|
|
|
|
|
|
+ modelName: {
|
|
|
|
|
+ type: 'string', title: 'Model',
|
|
|
|
|
+ description: 'Chosen from what the server has. Change the Kind above to list a different sort',
|
|
|
|
|
+ dynamicOptions: {
|
|
|
|
|
+ source: 'node',
|
|
|
|
|
+ node: 'sdcpp-model',
|
|
|
|
|
+ config: { listOnly: true },
|
|
|
|
|
+ itemsPath: 'models',
|
|
|
|
|
+ valueKey: 'name',
|
|
|
|
|
+ labelKey: 'name',
|
|
|
|
|
+ needs: ['serverUrl', 'credentialId', 'modelType']
|
|
|
|
|
+ }
|
|
|
},
|
|
},
|
|
|
- search: {
|
|
|
|
|
- type: 'string',
|
|
|
|
|
- title: 'Search',
|
|
|
|
|
- description: 'For the list operation, keep only models whose name contains this text'
|
|
|
|
|
|
|
+ setting: {
|
|
|
|
|
+ type: 'string', title: 'Supplies',
|
|
|
|
|
+ description: 'The setting this fills in on the node it is connected to. Leave empty to use the ordinary name for the kind chosen - modelName for a checkpoint or diffusion model, vae, taesd, controlnet, llm and so on'
|
|
|
},
|
|
},
|
|
|
- timeout: {
|
|
|
|
|
- type: 'number',
|
|
|
|
|
- title: 'Timeout (ms)',
|
|
|
|
|
- description: 'Loading a large model reads many gigabytes from disk and can take minutes, so this defaults high',
|
|
|
|
|
- default: 300000
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ timeout: { type: 'number', title: 'Timeout (ms)', default: 60000 }
|
|
|
},
|
|
},
|
|
|
required: []
|
|
required: []
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-const inputSchema = {
|
|
|
|
|
- type: 'object',
|
|
|
|
|
- properties: {
|
|
|
|
|
- data: { type: 'any' }
|
|
|
|
|
- }
|
|
|
|
|
-};
|
|
|
|
|
|
|
+const inputSchema = { type: 'object', properties: { data: { type: 'any' } } };
|
|
|
|
|
|
|
|
const outputSchema = {
|
|
const outputSchema = {
|
|
|
type: 'object',
|
|
type: 'object',
|
|
|
properties: {
|
|
properties: {
|
|
|
- operation: { type: 'string' },
|
|
|
|
|
- success: { type: 'boolean' },
|
|
|
|
|
- modelLoaded: { type: 'boolean', description: 'For health, whether a model occupies the main slot' },
|
|
|
|
|
- modelName: { type: 'string' },
|
|
|
|
|
|
|
+ _config: { type: 'object', description: 'The setting this supplies, as the engine consumes it' },
|
|
|
|
|
+ modelName: { type: 'string', description: 'The chosen model' },
|
|
|
modelType: { type: 'string' },
|
|
modelType: { type: 'string' },
|
|
|
- architecture: { type: 'string', description: 'Architecture the server detected, which decides the generation defaults' },
|
|
|
|
|
- upscalerLoaded: { type: 'boolean' },
|
|
|
|
|
- upscalerName: { type: 'string' },
|
|
|
|
|
- loadedComponents: { type: 'object', description: 'Which component files are resident' },
|
|
|
|
|
- models: { type: 'array', description: 'For list, a flat array of {type, name} entries' },
|
|
|
|
|
- modelsByType: { type: 'object', description: 'For list, the raw grouping the server returned' },
|
|
|
|
|
- count: { type: 'number', description: 'For list, how many models matched' },
|
|
|
|
|
- message: { type: 'string' },
|
|
|
|
|
- health: { type: 'object', description: 'For health, the full response including memory and feature flags' }
|
|
|
|
|
|
|
+ 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' }
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+// Which setting a kind of model ordinarily fills in. A checkpoint or a
|
|
|
|
|
+// diffusion model is the model itself; the rest are components named after
|
|
|
|
|
+// themselves. An upscaler is also "modelName", because SD.cpp Load Upscaler
|
|
|
|
|
+// takes it as its own model.
|
|
|
|
|
+const SETTING_FOR_KIND = {
|
|
|
|
|
+ checkpoint: 'modelName',
|
|
|
|
|
+ diffusion: 'modelName',
|
|
|
|
|
+ esrgan: 'modelName',
|
|
|
|
|
+ vae: 'vae',
|
|
|
|
|
+ clip: 'clipL',
|
|
|
|
|
+ t5: 't5xxl',
|
|
|
|
|
+ llm: 'llm',
|
|
|
|
|
+ taesd: 'taesd',
|
|
|
|
|
+ controlnet: 'controlnet',
|
|
|
|
|
+ motion_module: 'motionModule',
|
|
|
|
|
+ lora: 'lora',
|
|
|
|
|
+ embedding: 'embedding',
|
|
|
|
|
+ adetailer: 'adetailer'
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// The server groups models by kind under its own key names.
|
|
|
|
|
+const GROUP_FOR_KIND = {
|
|
|
|
|
+ checkpoint: 'checkpoints',
|
|
|
|
|
+ diffusion: 'diffusion_models',
|
|
|
|
|
+ vae: 'vae',
|
|
|
|
|
+ lora: 'loras',
|
|
|
|
|
+ clip: 'clip',
|
|
|
|
|
+ t5: 't5',
|
|
|
|
|
+ embedding: 'embeddings',
|
|
|
|
|
+ controlnet: 'controlnets',
|
|
|
|
|
+ llm: 'llm',
|
|
|
|
|
+ esrgan: 'esrgan',
|
|
|
|
|
+ taesd: 'taesd',
|
|
|
|
|
+ motion_module: 'motion_modules',
|
|
|
|
|
+ adetailer: 'adetailers'
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
function normalizeServer(url) {
|
|
function normalizeServer(url) {
|
|
|
const value = String(url || '').trim();
|
|
const value = String(url || '').trim();
|
|
|
if (!value) {
|
|
if (!value) {
|
|
@@ -180,6 +173,17 @@ function login(server, credential, timeout) {
|
|
|
return session.token;
|
|
return session.token;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// /health is unauthenticated, and it is the only way to find out what is
|
|
|
|
|
+// already loaded without asking for a token first.
|
|
|
|
|
+function readHealth(server, timeout) {
|
|
|
|
|
+ return call({
|
|
|
|
|
+ method: 'GET',
|
|
|
|
|
+ url: server + '/health',
|
|
|
|
|
+ timeout: timeout,
|
|
|
|
|
+ what: 'reading server health'
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function putIfSet(target, key, value) {
|
|
function putIfSet(target, key, value) {
|
|
|
if (value === undefined || value === null || value === '') {
|
|
if (value === undefined || value === null || value === '') {
|
|
|
return;
|
|
return;
|
|
@@ -187,277 +191,77 @@ function putIfSet(target, key, value) {
|
|
|
target[key] = value;
|
|
target[key] = value;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// The list response groups models under one key per kind. Flattening gives a
|
|
|
|
|
-// single array a Loop node can walk without knowing the key names.
|
|
|
|
|
-const LIST_GROUPS = {
|
|
|
|
|
- checkpoints: 'checkpoint',
|
|
|
|
|
- diffusion_models: 'diffusion',
|
|
|
|
|
- vae: 'vae',
|
|
|
|
|
- loras: 'lora',
|
|
|
|
|
- clip: 'clip',
|
|
|
|
|
- t5: 't5',
|
|
|
|
|
- embeddings: 'embedding',
|
|
|
|
|
- controlnets: 'controlnet',
|
|
|
|
|
- llm: 'llm',
|
|
|
|
|
- esrgan: 'esrgan',
|
|
|
|
|
- taesd: 'taesd',
|
|
|
|
|
- motion_modules: 'motion_module',
|
|
|
|
|
- adetailers: 'adetailer'
|
|
|
|
|
-};
|
|
|
|
|
|
|
+function listModels(server, credentialId, modelType, timeout) {
|
|
|
|
|
+ const credential = readCredential(credentialId);
|
|
|
|
|
+ const token = login(server, credential, Math.min(timeout, 30000));
|
|
|
|
|
|
|
|
-function flattenModels(listed) {
|
|
|
|
|
- const flat = [];
|
|
|
|
|
- const groupNames = Object.keys(LIST_GROUPS);
|
|
|
|
|
|
|
+ const listed = call({
|
|
|
|
|
+ method: 'GET',
|
|
|
|
|
+ url: server + '/models?type=' + encodeURIComponent(modelType),
|
|
|
|
|
+ headers: { 'Authorization': 'Bearer ' + token },
|
|
|
|
|
+ timeout: timeout,
|
|
|
|
|
+ what: 'listing ' + modelType + ' models'
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- for (let i = 0; i < groupNames.length; i++) {
|
|
|
|
|
- const group = groupNames[i];
|
|
|
|
|
- const entries = listed[group];
|
|
|
|
|
- if (!Array.isArray(entries)) {
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- for (let j = 0; j < entries.length; j++) {
|
|
|
|
|
- const entry = entries[j];
|
|
|
|
|
- // Entries are sometimes plain file names and sometimes objects
|
|
|
|
|
- // carrying a name plus size and hash, so handle both rather than
|
|
|
|
|
- // assuming one and producing a column of undefined.
|
|
|
|
|
- if (entry && typeof entry === 'object') {
|
|
|
|
|
- flat.push({
|
|
|
|
|
- type: LIST_GROUPS[group],
|
|
|
|
|
- name: entry.name || entry.filename || '',
|
|
|
|
|
- details: entry
|
|
|
|
|
- });
|
|
|
|
|
- } else {
|
|
|
|
|
- flat.push({ type: LIST_GROUPS[group], name: String(entry), details: null });
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const group = GROUP_FOR_KIND[modelType] || modelType;
|
|
|
|
|
+ const entries = Array.isArray(listed[group]) ? listed[group] : [];
|
|
|
|
|
+
|
|
|
|
|
+ const models = [];
|
|
|
|
|
+ for (let i = 0; i < entries.length; i++) {
|
|
|
|
|
+ const entry = entries[i];
|
|
|
|
|
+ // Entries are sometimes plain file names and sometimes objects with a
|
|
|
|
|
+ // name plus size and hash, so handle both rather than assuming one and
|
|
|
|
|
+ // producing a list of undefined.
|
|
|
|
|
+ if (entry && typeof entry === 'object') {
|
|
|
|
|
+ models.push({ name: entry.name || entry.filename || '', details: entry });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ models.push({ name: String(entry), details: null });
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- return flat;
|
|
|
|
|
|
|
+ return { models: models, loadedModel: listed.loaded_model || '' };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function execute(config, input, context) {
|
|
async function execute(config, input, context) {
|
|
|
const server = normalizeServer(config.serverUrl);
|
|
const server = normalizeServer(config.serverUrl);
|
|
|
- const operation = config.operation || 'health';
|
|
|
|
|
- const timeout = config.timeout || 300000;
|
|
|
|
|
-
|
|
|
|
|
- // /health is the one endpoint the server leaves unauthenticated, which makes
|
|
|
|
|
- // it usable as a reachability check before any credential exists.
|
|
|
|
|
- if (operation === 'health') {
|
|
|
|
|
- const health = call({
|
|
|
|
|
- method: 'GET',
|
|
|
|
|
- url: server + '/health',
|
|
|
|
|
- timeout: timeout,
|
|
|
|
|
- what: 'reading server health'
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- return {
|
|
|
|
|
- operation: operation,
|
|
|
|
|
- success: true,
|
|
|
|
|
- modelLoaded: health.model_loaded === true,
|
|
|
|
|
- modelName: health.model_name || '',
|
|
|
|
|
- modelType: health.model_type || '',
|
|
|
|
|
- architecture: health.model_architecture || '',
|
|
|
|
|
- upscalerLoaded: health.upscaler_loaded === true,
|
|
|
|
|
- upscalerName: health.upscaler_name || '',
|
|
|
|
|
- loadedComponents: health.loaded_components || {},
|
|
|
|
|
- models: [],
|
|
|
|
|
- modelsByType: {},
|
|
|
|
|
- count: 0,
|
|
|
|
|
- message: health.status || '',
|
|
|
|
|
- health: health
|
|
|
|
|
- };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (!config.credentialId) {
|
|
|
|
|
- throw new Error('SD.cpp: the ' + operation + ' operation needs a credential. ' +
|
|
|
|
|
- 'Only health works without one');
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const credential = readCredential(config.credentialId);
|
|
|
|
|
- const token = login(server, credential, timeout);
|
|
|
|
|
- const authHeaders = { 'Authorization': 'Bearer ' + token };
|
|
|
|
|
- const jsonHeaders = {
|
|
|
|
|
- 'Authorization': 'Bearer ' + token,
|
|
|
|
|
- 'Content-Type': 'application/json'
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- if (operation === 'list') {
|
|
|
|
|
- const query = [];
|
|
|
|
|
- if (config.listType) {
|
|
|
|
|
- query.push('type=' + encodeURIComponent(config.listType));
|
|
|
|
|
- }
|
|
|
|
|
- if (config.search) {
|
|
|
|
|
- query.push('search=' + encodeURIComponent(config.search));
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const listed = call({
|
|
|
|
|
- method: 'GET',
|
|
|
|
|
- url: server + '/models' + (query.length ? '?' + query.join('&') : ''),
|
|
|
|
|
- headers: authHeaders,
|
|
|
|
|
- timeout: timeout,
|
|
|
|
|
- what: 'listing models'
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- const flat = flattenModels(listed);
|
|
|
|
|
-
|
|
|
|
|
- return {
|
|
|
|
|
- operation: operation,
|
|
|
|
|
- success: true,
|
|
|
|
|
- modelLoaded: !!listed.loaded_model,
|
|
|
|
|
- modelName: listed.loaded_model || '',
|
|
|
|
|
- modelType: listed.loaded_model_type || '',
|
|
|
|
|
- architecture: '',
|
|
|
|
|
- upscalerLoaded: false,
|
|
|
|
|
- upscalerName: '',
|
|
|
|
|
- loadedComponents: {},
|
|
|
|
|
- models: flat,
|
|
|
|
|
- modelsByType: listed,
|
|
|
|
|
- count: flat.length,
|
|
|
|
|
- message: '',
|
|
|
|
|
- health: {}
|
|
|
|
|
- };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (operation === 'refresh') {
|
|
|
|
|
- const refreshed = call({
|
|
|
|
|
- method: 'POST',
|
|
|
|
|
- url: server + '/models/refresh',
|
|
|
|
|
- headers: jsonHeaders,
|
|
|
|
|
- body: '{}',
|
|
|
|
|
- timeout: timeout,
|
|
|
|
|
- what: 'rescanning the model directories'
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- return {
|
|
|
|
|
- operation: operation,
|
|
|
|
|
- success: true,
|
|
|
|
|
- modelLoaded: false,
|
|
|
|
|
- modelName: '',
|
|
|
|
|
- modelType: '',
|
|
|
|
|
- architecture: '',
|
|
|
|
|
- upscalerLoaded: false,
|
|
|
|
|
- upscalerName: '',
|
|
|
|
|
- loadedComponents: {},
|
|
|
|
|
- models: [],
|
|
|
|
|
- modelsByType: {},
|
|
|
|
|
- count: 0,
|
|
|
|
|
- message: refreshed.message || 'model directories rescanned',
|
|
|
|
|
- health: {}
|
|
|
|
|
- };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (operation === 'unload' || operation === 'unloadUpscaler') {
|
|
|
|
|
- const path = operation === 'unload' ? '/models/unload' : '/upscaler/unload';
|
|
|
|
|
- const unloaded = call({
|
|
|
|
|
- method: 'POST',
|
|
|
|
|
- url: server + path,
|
|
|
|
|
- headers: jsonHeaders,
|
|
|
|
|
- body: '{}',
|
|
|
|
|
- timeout: timeout,
|
|
|
|
|
- what: operation === 'unload' ? 'unloading the model' : 'unloading the upscaler'
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
|
|
+ const timeout = config.timeout > 0 ? config.timeout : 60000;
|
|
|
|
|
+ const modelType = config.modelType || 'checkpoint';
|
|
|
|
|
+
|
|
|
|
|
+ // The editor asks for the list while a field is being filled in. It is not
|
|
|
|
|
+ // what this node does during a run - a workflow that listed 358 models on
|
|
|
|
|
+ // every execution would be paying for nothing.
|
|
|
|
|
+ if (config.listOnly === true) {
|
|
|
|
|
+ const listed = listModels(server, config.credentialId, modelType, timeout);
|
|
|
return {
|
|
return {
|
|
|
- operation: operation,
|
|
|
|
|
- success: true,
|
|
|
|
|
- modelLoaded: false,
|
|
|
|
|
modelName: '',
|
|
modelName: '',
|
|
|
- modelType: '',
|
|
|
|
|
- architecture: '',
|
|
|
|
|
- upscalerLoaded: false,
|
|
|
|
|
- upscalerName: '',
|
|
|
|
|
- loadedComponents: {},
|
|
|
|
|
- models: [],
|
|
|
|
|
- modelsByType: {},
|
|
|
|
|
- count: 0,
|
|
|
|
|
- message: unloaded.message || 'unloaded',
|
|
|
|
|
- health: {}
|
|
|
|
|
|
|
+ modelType: modelType,
|
|
|
|
|
+ setting: '',
|
|
|
|
|
+ models: listed.models,
|
|
|
|
|
+ loadedModel: listed.loadedModel
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const modelName = String(config.modelName || '').trim();
|
|
const modelName = String(config.modelName || '').trim();
|
|
|
if (!modelName) {
|
|
if (!modelName) {
|
|
|
- throw new Error('SD.cpp: the ' + operation + ' operation needs a model name');
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (operation === 'loadUpscaler') {
|
|
|
|
|
- const body = { model_name: modelName };
|
|
|
|
|
- const loaded = call({
|
|
|
|
|
- method: 'POST',
|
|
|
|
|
- url: server + '/upscaler/load',
|
|
|
|
|
- headers: jsonHeaders,
|
|
|
|
|
- body: JSON.stringify(body),
|
|
|
|
|
- timeout: timeout,
|
|
|
|
|
- what: 'loading upscaler ' + modelName
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- smartbotic.log.info('SD.cpp: loaded upscaler ' + modelName);
|
|
|
|
|
-
|
|
|
|
|
- return {
|
|
|
|
|
- operation: operation,
|
|
|
|
|
- success: loaded.success !== false,
|
|
|
|
|
- modelLoaded: false,
|
|
|
|
|
- modelName: modelName,
|
|
|
|
|
- modelType: 'esrgan',
|
|
|
|
|
- architecture: '',
|
|
|
|
|
- upscalerLoaded: true,
|
|
|
|
|
- upscalerName: modelName,
|
|
|
|
|
- loadedComponents: {},
|
|
|
|
|
- models: [],
|
|
|
|
|
- modelsByType: {},
|
|
|
|
|
- count: 0,
|
|
|
|
|
- message: loaded.message || '',
|
|
|
|
|
- health: {}
|
|
|
|
|
- };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (operation !== 'load') {
|
|
|
|
|
- throw new Error('SD.cpp: unknown operation "' + operation + '"');
|
|
|
|
|
|
|
+ throw new Error('SD.cpp Model: choose a model. Pick one from the list, which is read ' +
|
|
|
|
|
+ 'from the server named above');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const body = { model_name: modelName };
|
|
|
|
|
- putIfSet(body, 'model_type', config.modelType);
|
|
|
|
|
- putIfSet(body, 'vae', config.vae);
|
|
|
|
|
- putIfSet(body, 'clip_l', config.clipL);
|
|
|
|
|
- putIfSet(body, 'clip_g', config.clipG);
|
|
|
|
|
- putIfSet(body, 't5xxl', config.t5xxl);
|
|
|
|
|
- putIfSet(body, 'llm', config.llm);
|
|
|
|
|
- putIfSet(body, 'taesd', config.taesd);
|
|
|
|
|
- putIfSet(body, 'controlnet', config.controlnet);
|
|
|
|
|
|
|
+ const setting = String(config.setting || '').trim() ||
|
|
|
|
|
+ SETTING_FOR_KIND[modelType] || 'modelName';
|
|
|
|
|
|
|
|
- if (config.options && typeof config.options === 'object') {
|
|
|
|
|
- body.options = config.options;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Loading a model unloads whatever was in the slot first, and the server
|
|
|
|
|
- // holds a mutex for the duration, so this call blocks until the weights are
|
|
|
|
|
- // resident. That is why the timeout defaults to five minutes.
|
|
|
|
|
- const loaded = call({
|
|
|
|
|
- method: 'POST',
|
|
|
|
|
- url: server + '/models/load',
|
|
|
|
|
- headers: jsonHeaders,
|
|
|
|
|
- body: JSON.stringify(body),
|
|
|
|
|
- timeout: timeout,
|
|
|
|
|
- what: 'loading model ' + modelName
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ const values = {};
|
|
|
|
|
+ values[setting] = modelName;
|
|
|
|
|
|
|
|
- smartbotic.log.info('SD.cpp: loaded model ' + modelName +
|
|
|
|
|
- (loaded.model_type ? ' as ' + loaded.model_type : ''));
|
|
|
|
|
|
|
+ smartbotic.log.info('SD.cpp Model: supplying ' + setting + ' = ' + modelName);
|
|
|
|
|
|
|
|
|
|
+ // Same marker the Configurator uses, so this reaches a node through its
|
|
|
|
|
+ // config input and replaces that node's own setting.
|
|
|
return {
|
|
return {
|
|
|
- operation: operation,
|
|
|
|
|
- success: loaded.success !== false,
|
|
|
|
|
- modelLoaded: true,
|
|
|
|
|
- modelName: loaded.model_name || modelName,
|
|
|
|
|
- modelType: loaded.model_type || config.modelType || '',
|
|
|
|
|
- architecture: '',
|
|
|
|
|
- upscalerLoaded: false,
|
|
|
|
|
- upscalerName: '',
|
|
|
|
|
- loadedComponents: loaded.loaded_components || {},
|
|
|
|
|
- models: [],
|
|
|
|
|
- modelsByType: {},
|
|
|
|
|
- count: 0,
|
|
|
|
|
- message: loaded.message || '',
|
|
|
|
|
- health: {}
|
|
|
|
|
|
|
+ _config: values,
|
|
|
|
|
+ modelName: modelName,
|
|
|
|
|
+ modelType: modelType,
|
|
|
|
|
+ setting: setting,
|
|
|
|
|
+ models: []
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|