diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js index bb12cb416..7fd51ddc5 100644 --- a/public/scripts/extensions/stable-diffusion/index.js +++ b/public/scripts/extensions/stable-diffusion/index.js @@ -47,6 +47,7 @@ const sources = { openai: 'openai', comfy: 'comfy', togetherai: 'togetherai', + drawthings: 'drawthings', }; const generationMode = { @@ -217,6 +218,9 @@ const defaultSettings = { vlad_url: 'http://localhost:7860', vlad_auth: '', + drawthings_url: 'http://localhost:7860', + drawthings_auth: '', + hr_upscaler: 'Latent', hr_scale: 2.0, hr_scale_min: 1.0, @@ -312,6 +316,8 @@ function getSdRequestBody() { return { url: extension_settings.sd.vlad_url, auth: extension_settings.sd.vlad_auth }; case sources.auto: return { url: extension_settings.sd.auto_url, auth: extension_settings.sd.auto_auth }; + case sources.drawthings: + return { url: extension_settings.sd.drawthings_url }; default: throw new Error('Invalid SD source.'); } @@ -385,6 +391,8 @@ async function loadSettings() { $('#sd_auto_auth').val(extension_settings.sd.auto_auth); $('#sd_vlad_url').val(extension_settings.sd.vlad_url); $('#sd_vlad_auth').val(extension_settings.sd.vlad_auth); + $('#sd_drawthings_url').val(extension_settings.sd.drawthings_url); + $('#sd_drawthings_auth').val(extension_settings.sd.drawthings_auth); $('#sd_interactive_mode').prop('checked', extension_settings.sd.interactive_mode); $('#sd_openai_style').val(extension_settings.sd.openai_style); $('#sd_openai_quality').val(extension_settings.sd.openai_quality); @@ -844,6 +852,16 @@ function onVladAuthInput() { saveSettingsDebounced(); } +function onDrawthingsUrlInput() { + extension_settings.sd.drawthings_url = $('#sd_drawthings_url').val(); + saveSettingsDebounced(); +} + +function onDrawthingsAuthInput() { + extension_settings.sd.drawthings_auth = $('#sd_drawthings_auth').val(); + saveSettingsDebounced(); +} + function onHrUpscalerChange() { extension_settings.sd.hr_upscaler = $('#sd_hr_upscaler').find(':selected').val(); saveSettingsDebounced(); @@ -910,6 +928,29 @@ async function validateAutoUrl() { } } +async function validateDrawthingsUrl() { + try { + if (!extension_settings.sd.drawthings_url) { + throw new Error('URL is not set.'); + } + + const result = await fetch('/api/sd/drawthings/ping', { + method: 'POST', + headers: getRequestHeaders(), + body: JSON.stringify(getSdRequestBody()), + }); + + if (!result.ok) { + throw new Error('SD Drawthings returned an error.'); + } + + await loadSettingOptions(); + toastr.success('SD Drawthings API connected.'); + } catch (error) { + toastr.error(`Could not validate SD Drawthings API: ${error.message}`); + } +} + async function validateVladUrl() { try { if (!extension_settings.sd.vlad_url) { @@ -997,6 +1038,27 @@ async function getAutoRemoteModel() { } } +async function getDrawthingsRemoteModel() { + try { + const result = await fetch('/api/sd/drawthings/get-model', { + method: 'POST', + headers: getRequestHeaders(), + body: JSON.stringify(getSdRequestBody()), + }); + + if (!result.ok) { + throw new Error('SD DrawThings API returned an error.'); + } + + const data = await result.text(); + + return data; + } catch (error) { + console.error(error); + return null; + } +} + async function onVaeChange() { extension_settings.sd.vae = $('#sd_vae').find(':selected').val(); } @@ -1087,6 +1149,9 @@ async function loadSamplers() { case sources.auto: samplers = await loadAutoSamplers(); break; + case sources.drawthings: + samplers = await loadDrawthingsSamplers(); + break; case sources.novel: samplers = await loadNovelSamplers(); break; @@ -1172,6 +1237,11 @@ async function loadAutoSamplers() { } } +async function loadDrawthingsSamplers() { + // The app developer doesn't provide an API to get these yet + return ["UniPC", "DPM++ 2M Karras", "Euler a", "DPM++ SDE Karras", "PLMS", "DDIM", "LCM", "Euler A Substep", "DPM++ SDE Substep", "TCD"]; +} + async function loadVladSamplers() { if (!extension_settings.sd.vlad_url) { return []; @@ -1248,6 +1318,9 @@ async function loadModels() { case sources.auto: models = await loadAutoModels(); break; + case sources.drawthings: + models = await loadDrawthingsModels(); + break; case sources.novel: models = await loadNovelModels(); break; @@ -1384,6 +1457,27 @@ async function loadAutoModels() { } } +async function loadDrawthingsModels() { + if (!extension_settings.sd.drawthings_url) { + return []; + } + + try { + const currentModel = await getDrawthingsRemoteModel(); + + if (currentModel) { + extension_settings.sd.model = currentModel; + } + + const data = [{value: currentModel, text: currentModel}]; + + return data; + } catch (error) { + console.log("Error loading DrawThings API models:", error); + return []; + } +} + async function loadOpenAiModels() { return [ { value: 'dall-e-3', text: 'DALL-E 3' }, @@ -1506,6 +1600,9 @@ async function loadSchedulers() { case sources.vlad: schedulers = ['N/A']; break; + case sources.drawthings: + schedulers = ['N/A']; + break; case sources.openai: schedulers = ['N/A']; break; @@ -1568,6 +1665,9 @@ async function loadVaes() { case sources.vlad: vaes = ['N/A']; break; + case sources.drawthings: + vaes = ['N/A']; + break; case sources.openai: vaes = ['N/A']; break; @@ -1975,6 +2075,9 @@ async function sendGenerationRequest(generationType, prompt, characterName = nul case sources.vlad: result = await generateAutoImage(prefixedPrompt, negativePrompt); break; + case sources.drawthings: + result = await generateDrawthingsImage(prefixedPrompt, negativePrompt); + break; case sources.auto: result = await generateAutoImage(prefixedPrompt, negativePrompt); break; @@ -2157,6 +2260,42 @@ async function generateAutoImage(prompt, negativePrompt) { } } +/** + * Generates an image in Drawthings API using the provided prompt and configuration settings. + * + * @param {string} prompt - The main instruction used to guide the image generation. + * @param {string} negativePrompt - The instruction used to restrict the image generation. + * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete. + */ +async function generateDrawthingsImage(prompt, negativePrompt) { + const result = await fetch('/api/sd/drawthings/generate', { + method: 'POST', + headers: getRequestHeaders(), + body: JSON.stringify({ + ...getSdRequestBody(), + prompt: prompt, + negative_prompt: negativePrompt, + sampler_name: extension_settings.sd.sampler, + steps: extension_settings.sd.steps, + cfg_scale: extension_settings.sd.scale, + width: extension_settings.sd.width, + height: extension_settings.sd.height, + restore_faces: !!extension_settings.sd.restore_faces, + enable_hr: !!extension_settings.sd.enable_hr, + denoising_strength: extension_settings.sd.denoising_strength, + // TODO: advanced API parameters: hr, upscaler + }), + }); + + if (result.ok) { + const data = await result.json(); + return { format: 'png', data: data.images[0] }; + } else { + const text = await result.text(); + throw new Error(text); + } +} + /** * Generates an image in NovelAI API using the provided prompt and configuration settings. * @@ -2573,6 +2712,8 @@ function isValidState() { return true; case sources.auto: return !!extension_settings.sd.auto_url; + case sources.drawthings: + return !!extension_settings.sd.drawthings_url; case sources.vlad: return !!extension_settings.sd.vlad_url; case sources.novel: @@ -2715,6 +2856,9 @@ jQuery(async () => { $('#sd_auto_validate').on('click', validateAutoUrl); $('#sd_auto_url').on('input', onAutoUrlInput); $('#sd_auto_auth').on('input', onAutoAuthInput); + $('#sd_drawthings_validate').on('click', validateDrawthingsUrl); + $('#sd_drawthings_url').on('input', onDrawthingsUrlInput); + $('#sd_drawthings_auth').on('input', onDrawthingsAuthInput); $('#sd_vlad_validate').on('click', validateVladUrl); $('#sd_vlad_url').on('input', onVladUrlInput); $('#sd_vlad_auth').on('input', onVladAuthInput); diff --git a/public/scripts/extensions/stable-diffusion/settings.html b/public/scripts/extensions/stable-diffusion/settings.html index 9fcefe3bc..3cb34d3aa 100644 --- a/public/scripts/extensions/stable-diffusion/settings.html +++ b/public/scripts/extensions/stable-diffusion/settings.html @@ -36,6 +36,7 @@ + @@ -56,6 +57,21 @@ Important: run SD Web UI with the --api flag! The server must be accessible from the SillyTavern host machine. +