Stable diffusion.cpp server support (#5074) * feat: Add stable-diffusion.cpp server endpoints * feat: Add stable-diffusion.cpp server settings * feat: Add stable-diffusion.cpp server * Add debug log for sdcpp payload * feat: add loadSdcppSamplers and loadSdcppSchedulers for sdcpp backend * feat: add sdcpp to sampler and scheduler source lists --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

12452491cbf8b64f8f40ac93e2dd0dfb9054c2b4

Jay4242 <96741732+Jay4242@users.noreply.github.com>

Signed
3 files changed, +197 -4Showing whitespace changes
public/scripts/extensions/stable-diffusion/index.js+111 -0
@@ -79,6 +79,7 @@ const sources = {
79 extras: 'extras',79 extras: 'extras',
80 horde: 'horde',80 horde: 'horde',
81 auto: 'auto',81 auto: 'auto',
82 sdcpp: 'sdcpp',
82 novel: 'novel',83 novel: 'novel',
83 vlad: 'vlad',84 vlad: 'vlad',
84 openai: 'openai',85 openai: 'openai',
@@ -290,6 +291,9 @@ const defaultSettings = {
290 auto_url: 'http://localhost:7860',291 auto_url: 'http://localhost:7860',
291 auto_auth: '',292 auto_auth: '',
292293
294 // stable-diffusion.cpp settings
295 sdcpp_url: 'http://127.0.0.1:1234',
296
293 vlad_url: 'http://localhost:7860',297 vlad_url: 'http://localhost:7860',
294 vlad_auth: '',298 vlad_auth: '',
295299
@@ -528,6 +532,7 @@ async function loadSettings() {
528 $('#sd_multimodal_captioning').prop('checked', extension_settings.sd.multimodal_captioning);532 $('#sd_multimodal_captioning').prop('checked', extension_settings.sd.multimodal_captioning);
529 $('#sd_auto_url').val(extension_settings.sd.auto_url);533 $('#sd_auto_url').val(extension_settings.sd.auto_url);
530 $('#sd_auto_auth').val(extension_settings.sd.auto_auth);534 $('#sd_auto_auth').val(extension_settings.sd.auto_auth);
535 $('#sd_sdcpp_url').val(extension_settings.sd.sdcpp_url);
531 $('#sd_vlad_url').val(extension_settings.sd.vlad_url);536 $('#sd_vlad_url').val(extension_settings.sd.vlad_url);
532 $('#sd_vlad_auth').val(extension_settings.sd.vlad_auth);537 $('#sd_vlad_auth').val(extension_settings.sd.vlad_auth);
533 $('#sd_drawthings_url').val(extension_settings.sd.drawthings_url);538 $('#sd_drawthings_url').val(extension_settings.sd.drawthings_url);
@@ -1156,6 +1161,11 @@ function onAutoAuthInput() {
1156 saveSettingsDebounced();1161 saveSettingsDebounced();
1157}1162}
11581163
1164function onSdcppUrlInput() {
1165 extension_settings.sd.sdcpp_url = $('#sd_sdcpp_url').val();
1166 saveSettingsDebounced();
1167}
1168
1159function onVladUrlInput() {1169function onVladUrlInput() {
1160 extension_settings.sd.vlad_url = $('#sd_vlad_url').val();1170 extension_settings.sd.vlad_url = $('#sd_vlad_url').val();
1161 saveSettingsDebounced();1171 saveSettingsDebounced();
@@ -1264,6 +1274,29 @@ async function validateAutoUrl() {
1264 }1274 }
1265}1275}
12661276
1277async function validateSdcppUrl() {
1278 try {
1279 if (!extension_settings.sd.sdcpp_url) {
1280 throw new Error('URL is not set.');
1281 }
1282
1283 const result = await fetch('/api/sd/sdcpp/ping', {
1284 method: 'POST',
1285 headers: getRequestHeaders(),
1286 body: JSON.stringify({ url: extension_settings.sd.sdcpp_url }),
1287 });
1288
1289 if (!result.ok) {
1290 throw new Error('stable-diffusion.cpp server returned an error.');
1291 }
1292
1293 await loadSettingOptions();
1294 toastr.success('stable-diffusion.cpp server connected.');
1295 } catch (error) {
1296 toastr.error(`Could not validate stable-diffusion.cpp server: ${error.message}`);
1297 }
1298}
1299
1267async function validateDrawthingsUrl() {1300async function validateDrawthingsUrl() {
1268 try {1301 try {
1269 if (!extension_settings.sd.drawthings_url) {1302 if (!extension_settings.sd.drawthings_url) {
@@ -1557,6 +1590,9 @@ async function loadSamplers() {
1557 case sources.auto:1590 case sources.auto:
1558 samplers = await loadAutoSamplers();1591 samplers = await loadAutoSamplers();
1559 break;1592 break;
1593 case sources.sdcpp:
1594 samplers = await loadSdcppSamplers();
1595 break;
1560 case sources.drawthings:1596 case sources.drawthings:
1561 samplers = await loadDrawthingsSamplers();1597 samplers = await loadDrawthingsSamplers();
1562 break;1598 break;
@@ -1682,6 +1718,11 @@ async function loadAutoSamplers() {
1682 }1718 }
1683}1719}
16841720
1721async function loadSdcppSamplers() {
1722 // The sdcpp server does not provide an API for samplers, so we return the known list.
1723 return ['euler', 'euler_a', 'heun', 'dpm2', 'dpm++2s_a', 'dpm++2m', 'dpm++2mv2', 'ipndm', 'ipndm_v', 'lcm', 'ddim_trailing', 'tcd'];
1724}
1725
1685async function loadDrawthingsSamplers() {1726async function loadDrawthingsSamplers() {
1686 // The app developer doesn't provide an API to get these yet1727 // The app developer doesn't provide an API to get these yet
1687 return [1728 return [
@@ -1771,6 +1812,9 @@ async function loadModels() {
1771 case sources.auto:1812 case sources.auto:
1772 models = await loadAutoModels();1813 models = await loadAutoModels();
1773 break;1814 break;
1815 case sources.sdcpp:
1816 models = [{ value: '', text: 'N/A' }];
1817 break;
1774 case sources.drawthings:1818 case sources.drawthings:
1775 models = await loadDrawthingsModels();1819 models = await loadDrawthingsModels();
1776 break;1820 break;
@@ -2379,6 +2423,9 @@ async function loadSchedulers() {
2379 case sources.auto:2423 case sources.auto:
2380 schedulers = await getAutoRemoteSchedulers();2424 schedulers = await getAutoRemoteSchedulers();
2381 break;2425 break;
2426 case sources.sdcpp:
2427 schedulers = await loadSdcppSchedulers();
2428 break;
2382 case sources.novel:2429 case sources.novel:
2383 schedulers = loadNovelSchedulers();2430 schedulers = loadNovelSchedulers();
2384 break;2431 break;
@@ -2477,6 +2524,11 @@ async function loadComfySchedulers() {
2477 }2524 }
2478}2525}
24792526
2527async function loadSdcppSchedulers() {
2528 // The sdcpp server does not provide an API for schedulers, so we return the known list.
2529 return ['discrete', 'karras', 'exponential', 'ays', 'gits', 'smoothstep', 'sgm_uniform', 'simple', 'kl_optimal', 'lcm'];
2530}
2531
2480async function loadVaes() {2532async function loadVaes() {
2481 $('#sd_vae').empty();2533 $('#sd_vae').empty();
2482 let vaes = [];2534 let vaes = [];
@@ -2491,6 +2543,9 @@ async function loadVaes() {
2491 case sources.auto:2543 case sources.auto:
2492 vaes = await loadAutoVaes();2544 vaes = await loadAutoVaes();
2493 break;2545 break;
2546 case sources.sdcpp:
2547 vaes = ['N/A'];
2548 break;
2494 case sources.novel:2549 case sources.novel:
2495 vaes = ['N/A'];2550 vaes = ['N/A'];
2496 break;2551 break;
@@ -3166,6 +3221,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
3166 case sources.auto:3221 case sources.auto:
3167 result = await generateAutoImage(prefixedPrompt, negativePrompt, signal);3222 result = await generateAutoImage(prefixedPrompt, negativePrompt, signal);
3168 break;3223 break;
3224 case sources.sdcpp:
3225 result = await generateSdcppImage(prefixedPrompt, negativePrompt, signal);
3226 break;
3169 case sources.novel:3227 case sources.novel:
3170 result = await generateNovelImage(prefixedPrompt, negativePrompt, signal);3228 result = await generateNovelImage(prefixedPrompt, negativePrompt, signal);
3171 break;3229 break;
@@ -3654,6 +3712,55 @@ async function generateAutoImage(prompt, negativePrompt, signal) {
3654}3712}
36553713
3656/**3714/**
3715 * Generates an image using stable-diffusion.cpp server API.
3716 *
3717 * @param {string} prompt - The main instruction used to guide the image generation.
3718 * @param {string} negativePrompt - The instruction used to restrict the image generation.
3719 * @param {AbortSignal} signal - An AbortSignal object that can be used to cancel the request.
3720 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
3721 */
3722async function generateSdcppImage(prompt, negativePrompt, signal) {
3723 const payload = {
3724 url: extension_settings.sd.sdcpp_url,
3725 prompt: prompt,
3726 negative_prompt: negativePrompt,
3727 steps: extension_settings.sd.steps,
3728 cfg_scale: extension_settings.sd.scale,
3729 width: extension_settings.sd.width,
3730 height: extension_settings.sd.height,
3731 batch_size: 1,
3732 seed: extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : undefined,
3733 };
3734
3735 if (extension_settings.sd.sampler && extension_settings.sd.sampler !== 'N/A') {
3736 payload.sampler_name = extension_settings.sd.sampler;
3737 }
3738
3739 if (extension_settings.sd.scheduler && extension_settings.sd.scheduler !== 'N/A') {
3740 payload.scheduler = extension_settings.sd.scheduler;
3741 }
3742
3743 if (Number.isFinite(extension_settings.sd.clip_skip)) {
3744 payload.clip_skip = extension_settings.sd.clip_skip;
3745 }
3746
3747 const result = await fetch('/api/sd/sdcpp/generate', {
3748 method: 'POST',
3749 headers: getRequestHeaders(),
3750 signal: signal,
3751 body: JSON.stringify(payload),
3752 });
3753
3754 if (result.ok) {
3755 const data = await result.json();
3756 return { format: 'png', data: data.images?.[0] };
3757 } else {
3758 const text = await result.text();
3759 throw new Error(text);
3760 }
3761}
3762
3763/**
3657 * Generates an image in Drawthings API using the provided prompt and configuration settings.3764 * Generates an image in Drawthings API using the provided prompt and configuration settings.
3658 *3765 *
3659 * @param {string} prompt - The main instruction used to guide the image generation.3766 * @param {string} prompt - The main instruction used to guide the image generation.
@@ -4736,6 +4843,8 @@ function isValidState() {
4736 return true;4843 return true;
4737 case sources.auto:4844 case sources.auto:
4738 return !!extension_settings.sd.auto_url;4845 return !!extension_settings.sd.auto_url;
4846 case sources.sdcpp:
4847 return !!extension_settings.sd.sdcpp_url;
4739 case sources.drawthings:4848 case sources.drawthings:
4740 return !!extension_settings.sd.drawthings_url;4849 return !!extension_settings.sd.drawthings_url;
4741 case sources.vlad:4850 case sources.vlad:
@@ -5418,6 +5527,8 @@ jQuery(async () => {
5418 $('#sd_auto_validate').on('click', validateAutoUrl);5527 $('#sd_auto_validate').on('click', validateAutoUrl);
5419 $('#sd_auto_url').on('input', onAutoUrlInput);5528 $('#sd_auto_url').on('input', onAutoUrlInput);
5420 $('#sd_auto_auth').on('input', onAutoAuthInput);5529 $('#sd_auto_auth').on('input', onAutoAuthInput);
5530 $('#sd_sdcpp_validate').on('click', validateSdcppUrl);
5531 $('#sd_sdcpp_url').on('input', onSdcppUrlInput);
5421 $('#sd_drawthings_validate').on('click', validateDrawthingsUrl);5532 $('#sd_drawthings_validate').on('click', validateDrawthingsUrl);
5422 $('#sd_drawthings_url').on('input', onDrawthingsUrlInput);5533 $('#sd_drawthings_url').on('input', onDrawthingsUrlInput);
5423 $('#sd_drawthings_auth').on('input', onDrawthingsAuthInput);5534 $('#sd_drawthings_auth').on('input', onDrawthingsAuthInput);
public/scripts/extensions/stable-diffusion/settings.html+18 -4
@@ -55,6 +55,7 @@
55 <option value="vlad">SD.Next (vladmandic)</option>55 <option value="vlad">SD.Next (vladmandic)</option>
56 <option value="stability">Stability AI</option>56 <option value="stability">Stability AI</option>
57 <option value="auto">Stable Diffusion Web UI (AUTOMATIC1111)</option>57 <option value="auto">Stable Diffusion Web UI (AUTOMATIC1111)</option>
58 <option value="sdcpp">stable-diffusion.cpp server</option>
58 <option value="horde">Stable Horde</option>59 <option value="horde">Stable Horde</option>
59 <option value="togetherai">TogetherAI</option>60 <option value="togetherai">TogetherAI</option>
60 <option value="xai">xAI (Grok)</option>61 <option value="xai">xAI (Grok)</option>
@@ -76,6 +77,19 @@
76 <!-- (Original Text)<b>Important:</b> run SD Web UI with the <tt>--api</tt> flag! The server must be accessible from the SillyTavern host machine. -->77 <!-- (Original Text)<b>Important:</b> run SD Web UI with the <tt>--api</tt> flag! The server must be accessible from the SillyTavern host machine. -->
77 <i><b data-i18n="Important:">Important:</b></i><i data-i18n="sd_auto_auth_warning_1"> run SD Web UI with the </i><i><tt>--api</tt></i><i data-i18n="sd_auto_auth_warning_2"> flag! The server must be accessible from the SillyTavern host machine.</i>78 <i><b data-i18n="Important:">Important:</b></i><i data-i18n="sd_auto_auth_warning_1"> run SD Web UI with the </i><i><tt>--api</tt></i><i data-i18n="sd_auto_auth_warning_2"> flag! The server must be accessible from the SillyTavern host machine.</i>
78 </div>79 </div>
80 <div data-sd-source="sdcpp">
81 <label for="sd_sdcpp_url">stable-diffusion.cpp URL</label>
82 <div class="flex-container flexnowrap">
83 <input id="sd_sdcpp_url" type="text" class="text_pole" placeholder="Example: {{sdcpp_url}}" value="{{sdcpp_url}}" />
84 <div id="sd_sdcpp_validate" class="menu_button menu_button_icon">
85 <i class="fa-solid fa-check"></i>
86 <span data-i18n="Connect">
87 Connect
88 </span>
89 </div>
90 </div>
91 <i data-i18n="The server must be accessible from the SillyTavern host machine.">The server must be accessible from the SillyTavern host machine.</i>
92 </div>
79 <div data-sd-source="drawthings">93 <div data-sd-source="drawthings">
80 <label for="sd_drawthings_url">DrawThings API URL</label>94 <label for="sd_drawthings_url">DrawThings API URL</label>
81 <div class="flex-container flexnowrap">95 <div class="flex-container flexnowrap">
@@ -395,12 +409,12 @@
395 </div>409 </div>
396410
397 <div class="flex-container">411 <div class="flex-container">
398 <div class="flex1" data-sd-source="extras,horde,auto,drawthings,novel,vlad,comfy">412 <div class="flex1" data-sd-source="extras,horde,auto,drawthings,novel,vlad,comfy,sdcpp">
399 <label for="sd_sampler" data-i18n="Sampling method">Sampling method</label>413 <label for="sd_sampler" data-i18n="Sampling method">Sampling method</label>
400 <select id="sd_sampler"></select>414 <select id="sd_sampler"></select>
401 </div>415 </div>
402416
403 <div class="flex1" data-sd-source="comfy,auto,novel">417 <div class="flex1" data-sd-source="comfy,auto,novel,sdcpp">
404 <label for="sd_scheduler" data-i18n="Scheduler">Scheduler</label>418 <label for="sd_scheduler" data-i18n="Scheduler">Scheduler</label>
405 <select id="sd_scheduler"></select>419 <select id="sd_scheduler"></select>
406 </div>420 </div>
@@ -483,7 +497,7 @@
483 <input class="neo-range-input" type="number" id="sd_hr_second_pass_steps_value" data-for="sd_hr_second_pass_steps" max="{{hr_second_pass_steps_max}}" step="{{hr_second_pass_steps_step}}" value="{{hr_second_pass_steps}}" >497 <input class="neo-range-input" type="number" id="sd_hr_second_pass_steps_value" data-for="sd_hr_second_pass_steps" max="{{hr_second_pass_steps_max}}" step="{{hr_second_pass_steps_step}}" value="{{hr_second_pass_steps}}" >
484 </div>498 </div>
485499
486 <div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="auto,vlad,comfy,horde,drawthings,extras">500 <div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="auto,vlad,comfy,horde,drawthings,extras,sdcpp">
487 <small>501 <small>
488 <span data-i18n="CLIP Skip">CLIP Skip</span>502 <span data-i18n="CLIP Skip">CLIP Skip</span>
489 </small>503 </small>
@@ -537,7 +551,7 @@
537 </label>551 </label>
538 </div>552 </div>
539553
540 <div data-sd-source="novel,togetherai,pollinations,comfy,drawthings,vlad,auto,horde,extras,stability,bfl" class="marginTop5">554 <div data-sd-source="novel,togetherai,pollinations,comfy,drawthings,vlad,auto,horde,extras,stability,bfl,sdcpp" class="marginTop5">
541 <label for="sd_seed">555 <label for="sd_seed">
542 <span data-i18n="Seed">Seed</span>556 <span data-i18n="Seed">Seed</span>
543 <small data-i18n="(-1 for random)">(-1 for random)</small>557 <small data-i18n="(-1 for random)">(-1 for random)</small>
src/endpoints/stable-diffusion.js+68 -0
@@ -801,6 +801,73 @@ together.post('/generate', async (request, response) => {
801 }801 }
802});802});
803803
804const sdcpp = express.Router();
805
806sdcpp.post('/ping', async (request, response) => {
807 try {
808 const url = new URL(request.body.url);
809 url.pathname = '/v1/images/generations';
810
811 const result = await fetch(url, { method: 'OPTIONS' });
812 if (!result.ok) {
813 throw new Error('stable-diffusion.cpp server returned an error.');
814 }
815
816 return response.sendStatus(200);
817 } catch (error) {
818 console.error(error);
819 return response.sendStatus(500);
820 }
821});
822
823sdcpp.post('/generate', async (request, response) => {
824 try {
825 const url = new URL(request.body.url);
826 url.pathname = '/sdapi/v1/txt2img';
827
828 const payload = {
829 prompt: request.body.prompt,
830 negative_prompt: request.body.negative_prompt,
831 width: request.body.width,
832 height: request.body.height,
833 steps: request.body.steps,
834 cfg_scale: request.body.cfg_scale,
835 seed: request.body.seed,
836 batch_size: request.body.batch_size,
837 sampler_name: request.body.sampler_name,
838 scheduler: request.body.scheduler,
839 clip_skip: request.body.clip_skip,
840 };
841
842 for (const [key, value] of Object.entries(payload)) {
843 if (value === undefined || value === null || value === '') {
844 delete payload[key];
845 }
846 }
847
848 console.debug('stable-diffusion.cpp request:', payload);
849
850 const result = await fetch(url, {
851 method: 'POST',
852 body: JSON.stringify(payload),
853 headers: {
854 'Content-Type': 'application/json',
855 },
856 });
857
858 if (!result.ok) {
859 const text = await result.text();
860 throw new Error('stable-diffusion.cpp server returned an error.', { cause: text });
861 }
862
863 const data = await result.json();
864 return response.send(data);
865 } catch (error) {
866 console.error(error);
867 return response.sendStatus(500);
868 }
869});
870
804const drawthings = express.Router();871const drawthings = express.Router();
805872
806drawthings.post('/ping', async (request, response) => {873drawthings.post('/ping', async (request, response) => {
@@ -1917,6 +1984,7 @@ zai.post('/generate-video', async (request, response) => {
1917router.use('/comfy', comfy);1984router.use('/comfy', comfy);
1918router.use('/comfyrunpod', comfyRunPod);1985router.use('/comfyrunpod', comfyRunPod);
1919router.use('/together', together);1986router.use('/together', together);
1987router.use('/sdcpp', sdcpp);
1920router.use('/drawthings', drawthings);1988router.use('/drawthings', drawthings);
1921router.use('/pollinations', pollinations);1989router.use('/pollinations', pollinations);
1922router.use('/stability', stability);1990router.use('/stability', stability);