Add cancelling of SD gens

1effb66fd6ee33072cc2a3fc7b9994220eedba6a

Cohee <18619528+Cohee1207@users.noreply.github.com>

2 files changed, +74 -25Showing whitespace changes
public/scripts/extensions/stable-diffusion/index.js+63 -25
@@ -2289,24 +2289,34 @@ async function generatePicture(initiator, args, trigger, message, callback) {
22892289 }
22902290
22912291 const dimensions = setTypeSpecificDimensions(generationType);
2292+ const abortController = new AbortController();
22922293 let negativePromptPrefix = args?.negative || '';
22932294 let imagePath = '';
22942295
2296+ const stopListener = () => abortController.abort('Aborted by user');
2297+ const mesStop = document.getElementById('mes_stop');
2298+
22952299 try {
22962300 const combineNegatives = (prefix) => { negativePromptPrefix = combinePrefixes(negativePromptPrefix, prefix); };
22972301 const prompt = await getPrompt(generationType, message, trigger, quietPrompt, combineNegatives);
22982302 console.log('Processed image prompt:', prompt);
22992303
2304+ mesStop?.addEventListener('click', stopListener);
23002305 context.deactivateSendButtons();
23012306 hideSwipeButtons();
23022307
2303- imagePath = await sendGenerationRequest(generationType, prompt, negativePromptPrefix, characterName, callback, initiator);
2308+ if (typeof args?._abortController?.addEventListener === 'function') {
2309+ args._abortController.addEventListener('abort', stopListener);
2310+ }
2311+
2312+ imagePath = await sendGenerationRequest(generationType, prompt, negativePromptPrefix, characterName, callback, initiator, abortController.signal);
23042313 } catch (err) {
23052314 console.trace(err);
23062315 throw new Error('SD prompt text generation failed.');
23072316 }
23082317 finally {
23092318 restoreOriginalDimensions(dimensions);
2319+ mesStop?.removeEventListener('click', stopListener);
23102320 context.activateSendButtons();
23112321 showSwipeButtons();
23122322 }
@@ -2521,9 +2531,10 @@ async function generatePrompt(quietPrompt) {
25212531 * @param {string} characterName Name of the character
25222532 * @param {function} callback Callback function to be called after image generation
25232533 * @param {string} initiator The initiator of the image generation
2534+ * @param {AbortSignal} signal Abort signal to cancel the request
25242535 * @returns
25252536 */
25262537async function sendGenerationRequest(generationType, prompt, additionalNegativePrefix, characterName, callback, initiator, signal) {
25272538 const noCharPrefix = [generationMode.FREE, generationMode.BACKGROUND, generationMode.USER, generationMode.USER_MULTIMODAL, generationMode.FREE_EXTENDED];
25282539 const prefix = noCharPrefix.includes(generationType)
25292540 ? extension_settings.sd.prompt_prefix
@@ -2541,37 +2552,37 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
25412552 try {
25422553 switch (extension_settings.sd.source) {
25432554 case sources.extras:
25442555 result = await generateExtrasImage(prefixedPrompt, negativePrompt, signal);
25452556 break;
25462557 case sources.horde:
25472558 result = await generateHordeImage(prefixedPrompt, negativePrompt, signal);
25482559 break;
25492560 case sources.vlad:
25502561 result = await generateAutoImage(prefixedPrompt, negativePrompt, signal);
25512562 break;
25522563 case sources.drawthings:
25532564 result = await generateDrawthingsImage(prefixedPrompt, negativePrompt, signal);
25542565 break;
25552566 case sources.auto:
25562567 result = await generateAutoImage(prefixedPrompt, negativePrompt, signal);
25572568 break;
25582569 case sources.novel:
25592570 result = await generateNovelImage(prefixedPrompt, negativePrompt, signal);
25602571 break;
25612572 case sources.openai:
25622573 result = await generateOpenAiImage(prefixedPrompt, signal);
25632574 break;
25642575 case sources.comfy:
25652576 result = await generateComfyImage(prefixedPrompt, negativePrompt, signal);
25662577 break;
25672578 case sources.togetherai:
25682579 result = await generateTogetherAIImage(prefixedPrompt, negativePrompt, signal);
25692580 break;
25702581 case sources.pollinations:
25712582 result = await generatePollinationsImage(prefixedPrompt, negativePrompt, signal);
25722583 break;
25732584 case sources.stability:
25742585 result = await generateStabilityImage(prefixedPrompt, negativePrompt, signal);
25752586 break;
25762587 }
25772588
@@ -2600,12 +2611,14 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
26002611 * Generates an image using the TogetherAI API.
26012612 * @param {string} prompt - The main instruction used to guide the image generation.
26022613 * @param {string} negativePrompt - The instruction used to restrict the image generation.
2614+ * @param {AbortSignal} signal - An AbortSignal object that can be used to cancel the request.
26032615 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
26042616 */
26052617async function generateTogetherAIImage(prompt, negativePrompt, signal) {
26062618 const result = await fetch('/api/sd/together/generate', {
26072619 method: 'POST',
26082620 headers: getRequestHeaders(),
2621+ signal: signal,
26092622 body: JSON.stringify({
26102623 prompt: prompt,
26112624 negative_prompt: negativePrompt,
@@ -2630,12 +2643,14 @@ async function generateTogetherAIImage(prompt, negativePrompt) {
26302643 * Generates an image using the Pollinations API.
26312644 * @param {string} prompt - The main instruction used to guide the image generation.
26322645 * @param {string} negativePrompt - The instruction used to restrict the image generation.
2646+ * @param {AbortSignal} signal - An AbortSignal object that can be used to cancel the request.
26332647 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
26342648 */
26352649async function generatePollinationsImage(prompt, negativePrompt, signal) {
26362650 const result = await fetch('/api/sd/pollinations/generate', {
26372651 method: 'POST',
26382652 headers: getRequestHeaders(),
2653+ signal: signal,
26392654 body: JSON.stringify({
26402655 prompt: prompt,
26412656 negative_prompt: negativePrompt,
@@ -2662,9 +2677,10 @@ async function generatePollinationsImage(prompt, negativePrompt) {
26622677 *
26632678 * @param {string} prompt - The main instruction used to guide the image generation.
26642679 * @param {string} negativePrompt - The instruction used to restrict the image generation.
2680+ * @param {AbortSignal} signal - An AbortSignal object that can be used to cancel the request.
26652681 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
26662682 */
26672683async function generateExtrasImage(prompt, negativePrompt, signal) {
26682684 const url = new URL(getApiUrl());
26692685 url.pathname = '/api/image';
26702686 const result = await doExtrasFetch(url, {
@@ -2672,6 +2688,7 @@ async function generateExtrasImage(prompt, negativePrompt) {
26722688 headers: {
26732689 'Content-Type': 'application/json',
26742690 },
2691+ signal: signal,
26752692 body: JSON.stringify({
26762693 prompt: prompt,
26772694 sampler: extension_settings.sd.sampler,
@@ -2739,9 +2756,10 @@ function getClosestAspectRatio(width, height) {
27392756 * Generates an image using Stability AI.
27402757 * @param {string} prompt - The main instruction used to guide the image generation.
27412758 * @param {string} negativePrompt - The instruction used to restrict the image generation.
2759+ * @param {AbortSignal} signal - An AbortSignal object that can be used to cancel the request.
27422760 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
27432761 */
27442762async function generateStabilityImage(prompt, negativePrompt, signal) {
27452763 const IMAGE_FORMAT = 'png';
27462764 const PROMPT_LIMIT = 10000;
27472765
@@ -2749,6 +2767,7 @@ async function generateStabilityImage(prompt, negativePrompt) {
27492767 const response = await fetch('/api/sd/stability/generate', {
27502768 method: 'POST',
27512769 headers: getRequestHeaders(),
2770+ signal: signal,
27522771 body: JSON.stringify({
27532772 model: extension_settings.sd.model,
27542773 payload: {
@@ -2783,12 +2802,14 @@ async function generateStabilityImage(prompt, negativePrompt) {
27832802 *
27842803 * @param {string} prompt - The main instruction used to guide the image generation.
27852804 * @param {string} negativePrompt - The instruction used to restrict the image generation.
2805+ * @param {AbortSignal} signal - An AbortSignal object that can be used to cancel the request.
27862806 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
27872807 */
27882808async function generateHordeImage(prompt, negativePrompt, signal) {
27892809 const result = await fetch('/api/horde/generate-image', {
27902810 method: 'POST',
27912811 headers: getRequestHeaders(),
2812+ signal: signal,
27922813 body: JSON.stringify({
27932814 prompt: prompt,
27942815 sampler: extension_settings.sd.sampler,
@@ -2821,13 +2842,15 @@ async function generateHordeImage(prompt, negativePrompt) {
28212842 *
28222843 * @param {string} prompt - The main instruction used to guide the image generation.
28232844 * @param {string} negativePrompt - The instruction used to restrict the image generation.
2845+ * @param {AbortSignal} signal - An AbortSignal object that can be used to cancel the request.
28242846 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
28252847 */
28262848async function generateAutoImage(prompt, negativePrompt, signal) {
28272849 const isValidVae = extension_settings.sd.vae && !['N/A', placeholderVae].includes(extension_settings.sd.vae);
28282850 const result = await fetch('/api/sd/generate', {
28292851 method: 'POST',
28302852 headers: getRequestHeaders(),
2853+ signal: signal,
28312854 body: JSON.stringify({
28322855 ...getSdRequestBody(),
28332856 prompt: prompt,
@@ -2875,12 +2898,14 @@ async function generateAutoImage(prompt, negativePrompt) {
28752898 *
28762899 * @param {string} prompt - The main instruction used to guide the image generation.
28772900 * @param {string} negativePrompt - The instruction used to restrict the image generation.
2901+ * @param {AbortSignal} signal - An AbortSignal object that can be used to cancel the request.
28782902 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
28792903 */
28802904async function generateDrawthingsImage(prompt, negativePrompt, signal) {
28812905 const result = await fetch('/api/sd/drawthings/generate', {
28822906 method: 'POST',
28832907 headers: getRequestHeaders(),
2908+ signal: signal,
28842909 body: JSON.stringify({
28852910 ...getSdRequestBody(),
28862911 prompt: prompt,
@@ -2914,14 +2939,16 @@ async function generateDrawthingsImage(prompt, negativePrompt) {
29142939 *
29152940 * @param {string} prompt - The main instruction used to guide the image generation.
29162941 * @param {string} negativePrompt - The instruction used to restrict the image generation.
2942+ * @param {AbortSignal} signal - An AbortSignal object that can be used to cancel the request.
29172943 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
29182944 */
29192945async function generateNovelImage(prompt, negativePrompt, signal) {
29202946 const { steps, width, height, sm, sm_dyn } = getNovelParams();
29212947
29222948 const result = await fetch('/api/novelai/generate-image', {
29232949 method: 'POST',
29242950 headers: getRequestHeaders(),
2951+ signal: signal,
29252952 body: JSON.stringify({
29262953 prompt: prompt,
29272954 model: extension_settings.sd.model,
@@ -3010,7 +3037,13 @@ function getNovelParams() {
30103037 return { steps, width, height, sm, sm_dyn };
30113038}
30123039
3013-async function generateOpenAiImage(prompt) {
3040+/**
3041+ * Generates an image in OpenAI API using the provided prompt and configuration settings.
3042+ * @param {string} prompt - The main instruction used to guide the image generation.
3043+ * @param {AbortSignal} signal - An AbortSignal object that can be used to cancel the request.
3044+ * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
3045+ */
3046+async function generateOpenAiImage(prompt, signal) {
30143047 const dalle2PromptLimit = 1000;
30153048 const dalle3PromptLimit = 4000;
30163049
@@ -3045,6 +3078,7 @@ async function generateOpenAiImage(prompt) {
30453078 const result = await fetch('/api/openai/generate-image', {
30463079 method: 'POST',
30473080 headers: getRequestHeaders(),
3081+ signal: signal,
30483082 body: JSON.stringify({
30493083 prompt: prompt,
30503084 model: extension_settings.sd.model,
@@ -3070,9 +3104,10 @@ async function generateOpenAiImage(prompt) {
30703104 *
30713105 * @param {string} prompt - The main instruction used to guide the image generation.
30723106 * @param {string} negativePrompt - The instruction used to restrict the image generation.
3107+ * @param {AbortSignal} signal - An AbortSignal object that can be used to cancel the request.
30733108 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
30743109 */
30753110async function generateComfyImage(prompt, negativePrompt, signal) {
30763111 const placeholders = [
30773112 'model',
30783113 'vae',
@@ -3133,6 +3168,7 @@ async function generateComfyImage(prompt, negativePrompt) {
31333168 const promptResult = await fetch('/api/sd/comfy/generate', {
31343169 method: 'POST',
31353170 headers: getRequestHeaders(),
3171+ signal: signal,
31363172 body: JSON.stringify({
31373173 url: extension_settings.sd.comfy_url,
31383174 prompt: `{
@@ -3245,7 +3281,7 @@ async function onComfyNewWorkflowClick() {
32453281 if (!name) {
32463282 return;
32473283 }
32483284 if (!String(name).toLowerCase().endsWith('.json')) {
32493285 name += '.json';
32503286 }
32513287 extension_settings.sd.comfy_workflow = name;
@@ -3448,8 +3484,10 @@ async function sdMessageButton(e) {
34483484 const messageText = message?.mes;
34493485 const hasSavedImage = message?.extra?.image && message?.extra?.title;
34503486 const hasSavedNegative = message?.extra?.negative;
3487+ const abortController = new AbortController();
34513488
34523489 if ($icon.hasClass(busyClass)) {
3490+ abortController.abort();
34533491 console.log('Previous image is still being generated...');
34543492 return;
34553493 }
@@ -3466,7 +3504,7 @@ async function sdMessageButton(e) {
34663504 const generationType = message?.extra?.generationType ?? generationMode.FREE;
34673505 console.log('Regenerating an image, using existing prompt:', prompt);
34683506 dimensions = setTypeSpecificDimensions(generationType);
34693507 await sendGenerationRequest(generationType, prompt, negative, characterFileName, saveGeneratedImage, initiators.action, abortController.signal);
34703508 }
34713509 else {
34723510 console.log('doing /sd raw last');
src/endpoints/horde.js+11 -0
@@ -339,7 +339,18 @@ router.post('/generate-image', jsonParser, async (request, response) => {
339339 return response.sendStatus(400);
340340 }
341341
342+ console.log('Horde image generation request:', generation);
343+
344+ const controller = new AbortController();
345+ request.socket.removeAllListeners('close');
346+ request.socket.on('close', function () {
347+ console.log('Horde image generation request aborted.');
348+ controller.abort();
349+ if (generation.id) ai_horde.deleteImageGenerationRequest(generation.id);
350+ });
351+
342352 for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
353+ controller.signal.throwIfAborted();
343354 await delay(CHECK_INTERVAL);
344355 const check = await ai_horde.getImageGenerationCheck(generation.id);
345356 console.log(check);