|
const general = require('../general') |
|
const psapi = require('../../psapi') |
|
const html_manip = require('../html_manip') |
|
const layer_util = require('../layer') |
|
const dummy = require('../dummy') |
|
const io = require('../io') |
|
class HordeSettings { |
|
static {} |
|
static async saveSettings() { |
|
try { |
|
const settings = await getSettings() |
|
|
|
let native_horde_settings = await mapPluginSettingsToHorde(settings) |
|
const horde_api_key = html_manip.getHordeApiKey() |
|
native_horde_settings['api_key'] = html_manip.getHordeApiKey() |
|
await io.IOJson.saveHordeSettingsToFile(native_horde_settings) |
|
} catch (e) { |
|
console.warn(e) |
|
} |
|
} |
|
static async loadSettings() { |
|
try { |
|
let native_horde_settings = |
|
await io.IOJson.loadHordeSettingsFromFile() |
|
html_manip.setHordeApiKey(native_horde_settings['api_key']) |
|
} catch (e) { |
|
console.warn(e) |
|
} |
|
} |
|
} |
|
class hordeGenerator { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor() { |
|
this.horde_settings |
|
this.plugin_settings |
|
this.currentGenerationResult = null |
|
this.requestStatus = null |
|
this.isProcessHordeResultCalled = false |
|
this.maxWaitTime = 0 |
|
this.waiting = 0 |
|
this.isCanceled = false |
|
this.horde_id = null |
|
this.last_horde_id = null |
|
} |
|
|
|
async getSettings() { |
|
try { |
|
const workers = await getWorkers() |
|
|
|
const workers_ids = getWorkerID(workers) |
|
const settings = await getSettings() |
|
this.plugin_settings = settings |
|
let payload = await mapPluginSettingsToHorde(settings) |
|
|
|
payload['workers'] = [] |
|
|
|
this.horde_settings = payload |
|
return this.horde_settings |
|
} catch (e) { |
|
console.warn('getSettings: ', e) |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
async generateRequest(settings) { |
|
try { |
|
this.horde_id = null |
|
this.requestStatus = await requestHorde(settings) |
|
if (this.requestStatus?.message) { |
|
await app.showAlert(this.requestStatus?.message) |
|
} |
|
this.horde_id = this.requestStatus.id |
|
console.log( |
|
'generateRequest this.requestStatus: ', |
|
this.requestStatus |
|
) |
|
|
|
const images_info = await this.startCheckingProgress() |
|
const result = await this.toGenerationFormat(images_info) |
|
console.warn('generateRequest() images_info: ', images_info) |
|
console.warn('generateRequest() result: ', result) |
|
|
|
html_manip.updateProgressBarsHtml(0) |
|
return result |
|
} catch (e) { |
|
this.horde_id = null |
|
console.warn(e) |
|
} |
|
} |
|
async generate() { |
|
|
|
this.horde_settings = await this.getSettings() |
|
|
|
this.isCanceled = false |
|
const result = await this.generateRequest(this.horde_settings) |
|
|
|
return result |
|
|
|
|
|
|
|
} |
|
|
|
isValidGeneration() { |
|
if (this.currentGenerationResult) { |
|
return true |
|
} else { |
|
return false |
|
} |
|
} |
|
preGenerate() {} |
|
|
|
|
|
|
|
async layerToBase64Webp(layer, document_name, image_name) { |
|
const width = html_manip.getWidth() |
|
const height = html_manip.getHeight() |
|
const image_buffer = await psapi.newExportPng( |
|
layer, |
|
image_name, |
|
width, |
|
height |
|
) |
|
|
|
const base64_image = _arrayBufferToBase64(image_buffer) |
|
|
|
|
|
await saveFileInSubFolder(base64_image, document_name, image_name) |
|
return base64_image |
|
} |
|
|
|
async layerToBase64ToFile(layer, document_name, image_name) { |
|
const width = html_manip.getWidth() |
|
const height = html_manip.getHeight() |
|
const image_buffer = await psapi.newExportPng( |
|
layer, |
|
image_name, |
|
width, |
|
height |
|
) |
|
|
|
const base64_image = _arrayBufferToBase64(image_buffer) |
|
|
|
|
|
await saveFileInSubFolder(base64_image, document_name, image_name) |
|
return base64_image |
|
} |
|
|
|
async toGenerationFormat(images_info) { |
|
|
|
try { |
|
|
|
|
|
let last_images_paths = {} |
|
for (const image_info of images_info) { |
|
const path = image_info['path'] |
|
|
|
const layer = image_info['layer'] |
|
const [document_name, image_name] = path.split('/') |
|
|
|
|
|
image_info['base64'] = await this.layerToBase64ToFile( |
|
layer, |
|
document_name, |
|
image_name |
|
) |
|
|
|
|
|
await layer_util.deleteLayers([layer]) |
|
|
|
|
|
|
|
this.plugin_settings['auto_metadata'] = |
|
image_info?.auto_metadata |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const dir_name = 'temp_dir_name' |
|
return { |
|
|
|
dir_name: dir_name, |
|
images_info: images_info, |
|
metadata: this.plugin_settings, |
|
} |
|
} catch (e) { |
|
console.warn(e) |
|
} |
|
} |
|
|
|
async toSession(images_info) { |
|
try { |
|
|
|
|
|
let last_images_paths = {} |
|
for (const image_info of images_info) { |
|
const path = image_info['path'] |
|
|
|
const layer = image_info['layer'] |
|
const [document_name, image_name] = path.split('/') |
|
|
|
|
|
image_info['base64'] = await this.layerToBase64ToFile( |
|
layer, |
|
document_name, |
|
image_name |
|
) |
|
const json_file_name = `${image_name.split('.')[0]}.json` |
|
this.plugin_settings['auto_metadata'] = |
|
image_info?.auto_metadata |
|
|
|
g_generation_session.base64OutputImages[path] = |
|
image_info['base64'] |
|
await saveJsonFileInSubFolder( |
|
this.plugin_settings, |
|
document_name, |
|
json_file_name |
|
) |
|
last_images_paths[path] = image_info['layer'] |
|
} |
|
|
|
if (g_generation_session.isFirstGeneration) { |
|
|
|
g_generation_session.image_paths_to_layers = last_images_paths |
|
} else { |
|
g_generation_session.image_paths_to_layers = { |
|
...g_generation_session.image_paths_to_layers, |
|
...last_images_paths, |
|
} |
|
|
|
} |
|
} catch (e) { |
|
console.warn(e) |
|
} |
|
} |
|
|
|
async interruptRequest(horde_id) { |
|
try { |
|
console.log('interruptRquest():') |
|
|
|
const full_url = `https://stablehorde.net/api/v2/generate/status/${horde_id}` |
|
|
|
console.log(full_url) |
|
|
|
let response = await fetch(full_url, { |
|
method: 'DELETE', |
|
headers: { |
|
Accept: 'application/json', |
|
'Content-Type': 'application/json', |
|
|
|
'Client-Agent': 'unknown:0:unknown', |
|
}, |
|
}) |
|
|
|
let result = await response.json() |
|
console.log('interruptReqquest result:', result) |
|
|
|
return result |
|
} catch (e) { |
|
console.warn(e) |
|
return |
|
} |
|
} |
|
async interrupt() { |
|
try { |
|
html_manip.updateProgressBarsHtml(0) |
|
|
|
this.last_horde_id = this.horde_id |
|
this.horde_id = null |
|
this.isCanceled = true |
|
|
|
await this.interruptRequest(this.last_horde_id) |
|
} catch (e) { |
|
console.warn(e) |
|
} |
|
} |
|
async postGeneration() { |
|
toggleTwoButtonsByClass(false, 'btnGenerateClass', 'btnInterruptClass') |
|
} |
|
async processHordeResult() { |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
if (this.isProcessHordeResultCalled) { |
|
return |
|
} |
|
this.isProcessHordeResultCalled = true |
|
console.log('horde request is done') |
|
|
|
const temp_id = this.horde_id |
|
|
|
g_horde_generation_result = await requestHordeStatus(temp_id) |
|
|
|
const generations = g_horde_generation_result.generations |
|
const writeable_entry = await getCurrentDocFolder() |
|
const images_info = [] |
|
for (const image_horde_container of generations) { |
|
try { |
|
const url = image_horde_container.img |
|
const image_file_name = general.newOutputImageName('webp') |
|
|
|
const image_layer = await downloadItExe( |
|
url, |
|
writeable_entry, |
|
image_file_name |
|
) |
|
const image_png_file_name = |
|
general.convertImageNameToPng(image_file_name) |
|
|
|
const uuid = await getUniqueDocumentId() |
|
const image_path = `${uuid}/${image_png_file_name}` |
|
images_info.push({ |
|
path: image_path, |
|
base64: dummy.getDummyBase64(), |
|
layer: image_layer, |
|
}) |
|
await psapi.layerToSelection( |
|
g_generation_session.selectionInfo |
|
) |
|
} catch (e) { |
|
console.warn(e) |
|
} |
|
} |
|
this.isProcessHordeResultCalled = false |
|
return images_info |
|
} catch (e) { |
|
console.warn(e) |
|
} |
|
} |
|
updateHordeProgressBar(check_horde_status) { |
|
|
|
console.log('this.maxWaitTime: ', this.maxWaitTime) |
|
console.log( |
|
"check_horde_status['wait_time']: ", |
|
check_horde_status['wait_time'] |
|
) |
|
console.log( |
|
"check_horde_status['waiting']: ", |
|
check_horde_status['waiting'] |
|
) |
|
|
|
this.maxWaitTime = Math.max( |
|
check_horde_status['wait_time'], |
|
this.maxWaitTime |
|
) |
|
const delta_time = this.maxWaitTime - check_horde_status['wait_time'] |
|
|
|
if (isNaN(this.maxWaitTime) || parseInt(this.maxWaitTime) === 0) { |
|
this.maxWaitTime = 0 |
|
} else { |
|
console.log('delta_time:', delta_time) |
|
console.log('this.maxWaitTime:', this.maxWaitTime) |
|
|
|
const completion_percentage = (delta_time / this.maxWaitTime) * 100 |
|
console.log('completion_percentage:', completion_percentage) |
|
|
|
html_manip.updateProgressBarsHtml(completion_percentage) |
|
} |
|
} |
|
async startCheckingProgress() { |
|
console.log('startCheckingProgress is called') |
|
return await new Promise((resolve, reject) => { |
|
if (this.horde_id && !this.isCanceled) { |
|
this.interval_id = setTimeout(async () => { |
|
try { |
|
console.warn( |
|
'startCheckingProgress(): horde_id and isCanceled', |
|
this.horde_id, |
|
this.isCanceled |
|
) |
|
|
|
const check_json = await requestHordeCheck( |
|
this.horde_id |
|
) |
|
|
|
this.updateHordeProgressBar(check_json) |
|
|
|
if (check_json['done']) { |
|
|
|
|
|
const images_info = await this.processHordeResult() |
|
if (this.horde_id) { |
|
this.last_horde_id = this.horde_id |
|
this.horde_id = null |
|
} |
|
return resolve(images_info) |
|
} else { |
|
|
|
console.warn( |
|
'startCheckingProgress(): reqursive startCheckingProgress call', |
|
this.horde_id, |
|
this.isCanceled |
|
) |
|
const horde_result = |
|
await this.startCheckingProgress() |
|
return resolve(horde_result) |
|
} |
|
} catch (e) { |
|
console.warn(e) |
|
const result = await this.startCheckingProgress() |
|
return resolve(result) |
|
} |
|
}, 3000) |
|
} else { |
|
console.warn( |
|
'startCheckingProgress: else block', |
|
this.horde_id, |
|
this.isCanceled |
|
) |
|
return resolve() |
|
} |
|
}) |
|
} |
|
} |
|
const webui_to_horde_samplers = { |
|
'Euler a': 'k_euler_a', |
|
Euler: 'k_euler', |
|
LMS: 'k_lms', |
|
Heun: 'k_heun', |
|
DPM2: 'k_dpm_2', |
|
'DPM2 a': 'k_dpm_2_a', |
|
'DPM++ 2S a': 'k_dpmpp_2s_a', |
|
'DPM++ 2M': 'k_dpmpp_2m', |
|
'DPM++ SDE': 'k_dpmpp_sde', |
|
'DPM fast': 'k_dpm_fast', |
|
'DPM adaptive': 'k_dpm_adaptive', |
|
'LMS Karras': 'k_lms', |
|
'DPM2 Karras': 'k_dpm_2', |
|
'DPM2 a Karras': 'k_dpm_2_a', |
|
'DPM++ 2S a Karras': 'k_dpmpp_2s_a', |
|
'DPM++ 2M Karras': 'k_dpmpp_2m', |
|
'DPM++ SDE Karras': 'k_dpmpp_sde', |
|
DDIM: 'ddim', |
|
PLMS: 'plms', |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function mapPluginSettingsToHorde(plugin_settings) { |
|
const { getModelHorde } = require('../sd_scripts/horde') |
|
const ps = plugin_settings |
|
const sampler = webui_to_horde_samplers[ps['sampler_index']] |
|
const model = getModelHorde() |
|
let horde_prompt |
|
if (ps['negative_prompt'].length > 0) { |
|
horde_prompt = `${ps['prompt']} ### ${ps['negative_prompt']}` |
|
} else { |
|
horde_prompt = ps['prompt'] |
|
} |
|
const extra_payload = {} |
|
if (ps['mode'] === 'img2img') { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const init_image_base64_webp = await io.IO.base64PngToBase64Webp( |
|
ps['init_images'][0] |
|
) |
|
extra_payload['source_image'] = init_image_base64_webp |
|
extra_payload['source_processing'] = 'img2img' |
|
} else if (ps['mode'] === 'inpaint' || ps['mode'] === 'outpaint') { |
|
const init_image_base64_webp = await io.IO.base64PngToBase64Webp( |
|
ps['init_images'][0] |
|
) |
|
const mask_base64_webp = await io.IO.base64PngToBase64Webp(ps['mask']) |
|
extra_payload['source_processing'] = 'inpainting' |
|
extra_payload['source_image'] = init_image_base64_webp |
|
extra_payload['source_mask'] = mask_base64_webp |
|
|
|
} |
|
|
|
let seed = ps['seed'] |
|
if (parseInt(ps['seed']) === -1) { |
|
const random_seed = Math.floor(Math.random() * 100000000000 + 1) |
|
seed = random_seed.toString() |
|
} |
|
const width = general.nearestMultiple(ps['width'], 64) |
|
const height = general.nearestMultiple(ps['height'], 64) |
|
const nsfw = html_manip.getUseNsfw() |
|
let horde_payload = { |
|
prompt: horde_prompt, |
|
params: { |
|
sampler_name: sampler, |
|
toggles: [1, 4], |
|
cfg_scale: ps['cfg_scale'], |
|
denoising_strength: ps['denoising_strength'], |
|
seed: seed, |
|
height: height, |
|
width: width, |
|
seed_variation: 1, |
|
post_processing: ['GFPGAN'], |
|
karras: false, |
|
tiling: false, |
|
steps: parseInt(ps['steps']), |
|
n: 1, |
|
}, |
|
nsfw: nsfw, |
|
trusted_workers: true, |
|
censor_nsfw: false, |
|
|
|
|
|
|
|
models: [model], |
|
|
|
|
|
|
|
...extra_payload, |
|
r2: true, |
|
shared: false, |
|
} |
|
return horde_payload |
|
} |
|
|
|
function getWorkerID(workers_json) { |
|
let workers_ids = [] |
|
for (worker of workers_json) { |
|
workers_ids.push(worker?.id) |
|
} |
|
console.log('workers_ids:', workers_ids) |
|
|
|
return workers_ids |
|
} |
|
async function getWorkers() { |
|
const full_url = 'https://stablehorde.net/api/v2/workers' |
|
|
|
console.log(full_url) |
|
|
|
let request = await fetch(full_url, { |
|
method: 'GET', |
|
headers: { |
|
Accept: 'application/json', |
|
}, |
|
}) |
|
|
|
let workers = await request.json() |
|
|
|
console.log('requestHorde workers:', workers) |
|
return workers |
|
} |
|
async function requestHorde(payload) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
console.log('requestHorde():') |
|
|
|
const full_url = 'https://stablehorde.net/api/v2/generate/async' |
|
|
|
console.log(full_url) |
|
|
|
const horde_api_key = html_manip.getHordeApiKey() |
|
let request = await fetch(full_url, { |
|
method: 'POST', |
|
headers: { |
|
Accept: 'application/json', |
|
'Content-Type': 'application/json', |
|
apikey: horde_api_key, |
|
|
|
'Client-Agent': 'unknown:0:unknown', |
|
}, |
|
body: JSON.stringify(payload), |
|
}) |
|
|
|
let json = await request.json() |
|
console.log('requestHorde json:', json) |
|
|
|
return json |
|
} catch (e) { |
|
console.warn(e) |
|
return {} |
|
} |
|
} |
|
async function requestHordeCheck(id) { |
|
try { |
|
console.log('requestHordeCheck():') |
|
const base_url = 'https://stablehorde.net/api/v2/generate/check' |
|
|
|
const full_url = `${base_url}/${id}` |
|
|
|
console.log(full_url) |
|
const payload = {} |
|
let request = await fetch(full_url, { |
|
method: 'GET', |
|
headers: { |
|
Accept: 'application/json', |
|
'Content-Type': 'application/json', |
|
|
|
'Client-Agent': 'unknown:0:unknown', |
|
}, |
|
}) |
|
|
|
let json = await request.json() |
|
console.log('requestHordeCheck json:', json) |
|
|
|
return json |
|
} catch (e) { |
|
console.warn(e) |
|
return {} |
|
} |
|
} |
|
|
|
async function requestHordeStatus(id) { |
|
try { |
|
console.log('requestHordeStatus():') |
|
const base_url = 'https://stablehorde.net/api/v2/generate/status' |
|
|
|
const full_url = `${base_url}/${id}` |
|
|
|
console.log(full_url) |
|
const payload = {} |
|
let request = await fetch(full_url, { |
|
method: 'GET', |
|
headers: { |
|
Accept: 'application/json', |
|
'Content-Type': 'application/json', |
|
|
|
'Client-Agent': 'unknown:0:unknown', |
|
}, |
|
}) |
|
|
|
let json = await request.json() |
|
console.log('requestHordeStatus json:', json) |
|
|
|
return json |
|
} catch (e) { |
|
console.warn(e) |
|
} |
|
} |
|
|
|
let g_horde_generation_result |
|
let g_b_request_result = false |
|
function cancelRequestClientSide() { |
|
this.interval_id = clearTimeout(this.interval_id) |
|
|
|
g_b_request_result = false |
|
} |
|
|
|
module.exports = { |
|
hordeGenerator, |
|
HordeSettings, |
|
} |
|
|