Add cancelling of SD gens

1effb66fd6ee33072cc2a3fc7b9994220eedba6a

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

2 files changed, +74 -25Ignore whitespace
public/scripts/extensions/stable-diffusion/index.js+63 -25
@@ -2289,24 +2289,34 @@ async function generatePicture(initiator, args, trigger, message, callback) {
2289 }2289 }
22902290
2291 const dimensions = setTypeSpecificDimensions(generationType);2291 const dimensions = setTypeSpecificDimensions(generationType);
2292 const abortController = new AbortController();
2292 let negativePromptPrefix = args?.negative || '';2293 let negativePromptPrefix = args?.negative || '';
2293 let imagePath = '';2294 let imagePath = '';
22942295
2296 const stopListener = () => abortController.abort('Aborted by user');
2297 const mesStop = document.getElementById('mes_stop');
2298
2295 try {2299 try {
2296 const combineNegatives = (prefix) => { negativePromptPrefix = combinePrefixes(negativePromptPrefix, prefix); };2300 const combineNegatives = (prefix) => { negativePromptPrefix = combinePrefixes(negativePromptPrefix, prefix); };
2297 const prompt = await getPrompt(generationType, message, trigger, quietPrompt, combineNegatives);2301 const prompt = await getPrompt(generationType, message, trigger, quietPrompt, combineNegatives);
2298 console.log('Processed image prompt:', prompt);2302 console.log('Processed image prompt:', prompt);
22992303
2304 mesStop?.addEventListener('click', stopListener);
2300 context.deactivateSendButtons();2305 context.deactivateSendButtons();
2301 hideSwipeButtons();2306 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);
2304 } catch (err) {2313 } catch (err) {
2305 console.trace(err);2314 console.trace(err);
2306 throw new Error('SD prompt text generation failed.');2315 throw new Error('SD prompt text generation failed.');
2307 }2316 }
2308 finally {2317 finally {
2309 restoreOriginalDimensions(dimensions);2318 restoreOriginalDimensions(dimensions);
2319 mesStop?.removeEventListener('click', stopListener);
2310 context.activateSendButtons();2320 context.activateSendButtons();
2311 showSwipeButtons();2321 showSwipeButtons();
2312 }2322 }
@@ -2521,9 +2531,10 @@ async function generatePrompt(quietPrompt) {
2521 * @param {string} characterName Name of the character2531 * @param {string} characterName Name of the character
2522 * @param {function} callback Callback function to be called after image generation2532 * @param {function} callback Callback function to be called after image generation
2523 * @param {string} initiator The initiator of the image generation2533 * @param {string} initiator The initiator of the image generation
2534 * @param {AbortSignal} signal Abort signal to cancel the request
2524 * @returns2535 * @returns
2525 */2536 */
2526async function sendGenerationRequest(generationType, prompt, additionalNegativePrefix, characterName, callback, initiator) {2537async function sendGenerationRequest(generationType, prompt, additionalNegativePrefix, characterName, callback, initiator, signal) {
2527 const noCharPrefix = [generationMode.FREE, generationMode.BACKGROUND, generationMode.USER, generationMode.USER_MULTIMODAL, generationMode.FREE_EXTENDED];2538 const noCharPrefix = [generationMode.FREE, generationMode.BACKGROUND, generationMode.USER, generationMode.USER_MULTIMODAL, generationMode.FREE_EXTENDED];
2528 const prefix = noCharPrefix.includes(generationType)2539 const prefix = noCharPrefix.includes(generationType)
2529 ? extension_settings.sd.prompt_prefix2540 ? extension_settings.sd.prompt_prefix
@@ -2541,37 +2552,37 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
2541 try {2552 try {
2542 switch (extension_settings.sd.source) {2553 switch (extension_settings.sd.source) {
2543 case sources.extras:2554 case sources.extras:
2544 result = await generateExtrasImage(prefixedPrompt, negativePrompt);2555 result = await generateExtrasImage(prefixedPrompt, negativePrompt, signal);
2545 break;2556 break;
2546 case sources.horde:2557 case sources.horde:
2547 result = await generateHordeImage(prefixedPrompt, negativePrompt);2558 result = await generateHordeImage(prefixedPrompt, negativePrompt, signal);
2548 break;2559 break;
2549 case sources.vlad:2560 case sources.vlad:
2550 result = await generateAutoImage(prefixedPrompt, negativePrompt);2561 result = await generateAutoImage(prefixedPrompt, negativePrompt, signal);
2551 break;2562 break;
2552 case sources.drawthings:2563 case sources.drawthings:
2553 result = await generateDrawthingsImage(prefixedPrompt, negativePrompt);2564 result = await generateDrawthingsImage(prefixedPrompt, negativePrompt, signal);
2554 break;2565 break;
2555 case sources.auto:2566 case sources.auto:
2556 result = await generateAutoImage(prefixedPrompt, negativePrompt);2567 result = await generateAutoImage(prefixedPrompt, negativePrompt, signal);
2557 break;2568 break;
2558 case sources.novel:2569 case sources.novel:
2559 result = await generateNovelImage(prefixedPrompt, negativePrompt);2570 result = await generateNovelImage(prefixedPrompt, negativePrompt, signal);
2560 break;2571 break;
2561 case sources.openai:2572 case sources.openai:
2562 result = await generateOpenAiImage(prefixedPrompt);2573 result = await generateOpenAiImage(prefixedPrompt, signal);
2563 break;2574 break;
2564 case sources.comfy:2575 case sources.comfy:
2565 result = await generateComfyImage(prefixedPrompt, negativePrompt);2576 result = await generateComfyImage(prefixedPrompt, negativePrompt, signal);
2566 break;2577 break;
2567 case sources.togetherai:2578 case sources.togetherai:
2568 result = await generateTogetherAIImage(prefixedPrompt, negativePrompt);2579 result = await generateTogetherAIImage(prefixedPrompt, negativePrompt, signal);
2569 break;2580 break;
2570 case sources.pollinations:2581 case sources.pollinations:
2571 result = await generatePollinationsImage(prefixedPrompt, negativePrompt);2582 result = await generatePollinationsImage(prefixedPrompt, negativePrompt, signal);
2572 break;2583 break;
2573 case sources.stability:2584 case sources.stability:
2574 result = await generateStabilityImage(prefixedPrompt, negativePrompt);2585 result = await generateStabilityImage(prefixedPrompt, negativePrompt, signal);
2575 break;2586 break;
2576 }2587 }
25772588
@@ -2600,12 +2611,14 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
2600 * Generates an image using the TogetherAI API.2611 * Generates an image using the TogetherAI API.
2601 * @param {string} prompt - The main instruction used to guide the image generation.2612 * @param {string} prompt - The main instruction used to guide the image generation.
2602 * @param {string} negativePrompt - The instruction used to restrict the image generation.2613 * @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.
2603 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.2615 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
2604 */2616 */
2605async function generateTogetherAIImage(prompt, negativePrompt) {2617async function generateTogetherAIImage(prompt, negativePrompt, signal) {
2606 const result = await fetch('/api/sd/together/generate', {2618 const result = await fetch('/api/sd/together/generate', {
2607 method: 'POST',2619 method: 'POST',
2608 headers: getRequestHeaders(),2620 headers: getRequestHeaders(),
2621 signal: signal,
2609 body: JSON.stringify({2622 body: JSON.stringify({
2610 prompt: prompt,2623 prompt: prompt,
2611 negative_prompt: negativePrompt,2624 negative_prompt: negativePrompt,
@@ -2630,12 +2643,14 @@ async function generateTogetherAIImage(prompt, negativePrompt) {
2630 * Generates an image using the Pollinations API.2643 * Generates an image using the Pollinations API.
2631 * @param {string} prompt - The main instruction used to guide the image generation.2644 * @param {string} prompt - The main instruction used to guide the image generation.
2632 * @param {string} negativePrompt - The instruction used to restrict the image generation.2645 * @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.
2633 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.2647 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
2634 */2648 */
2635async function generatePollinationsImage(prompt, negativePrompt) {2649async function generatePollinationsImage(prompt, negativePrompt, signal) {
2636 const result = await fetch('/api/sd/pollinations/generate', {2650 const result = await fetch('/api/sd/pollinations/generate', {
2637 method: 'POST',2651 method: 'POST',
2638 headers: getRequestHeaders(),2652 headers: getRequestHeaders(),
2653 signal: signal,
2639 body: JSON.stringify({2654 body: JSON.stringify({
2640 prompt: prompt,2655 prompt: prompt,
2641 negative_prompt: negativePrompt,2656 negative_prompt: negativePrompt,
@@ -2662,9 +2677,10 @@ async function generatePollinationsImage(prompt, negativePrompt) {
2662 *2677 *
2663 * @param {string} prompt - The main instruction used to guide the image generation.2678 * @param {string} prompt - The main instruction used to guide the image generation.
2664 * @param {string} negativePrompt - The instruction used to restrict the image generation.2679 * @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.
2665 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.2681 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
2666 */2682 */
2667async function generateExtrasImage(prompt, negativePrompt) {2683async function generateExtrasImage(prompt, negativePrompt, signal) {
2668 const url = new URL(getApiUrl());2684 const url = new URL(getApiUrl());
2669 url.pathname = '/api/image';2685 url.pathname = '/api/image';
2670 const result = await doExtrasFetch(url, {2686 const result = await doExtrasFetch(url, {
@@ -2672,6 +2688,7 @@ async function generateExtrasImage(prompt, negativePrompt) {
2672 headers: {2688 headers: {
2673 'Content-Type': 'application/json',2689 'Content-Type': 'application/json',
2674 },2690 },
2691 signal: signal,
2675 body: JSON.stringify({2692 body: JSON.stringify({
2676 prompt: prompt,2693 prompt: prompt,
2677 sampler: extension_settings.sd.sampler,2694 sampler: extension_settings.sd.sampler,
@@ -2739,9 +2756,10 @@ function getClosestAspectRatio(width, height) {
2739 * Generates an image using Stability AI.2756 * Generates an image using Stability AI.
2740 * @param {string} prompt - The main instruction used to guide the image generation.2757 * @param {string} prompt - The main instruction used to guide the image generation.
2741 * @param {string} negativePrompt - The instruction used to restrict the image generation.2758 * @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.
2742 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.2760 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
2743 */2761 */
2744async function generateStabilityImage(prompt, negativePrompt) {2762async function generateStabilityImage(prompt, negativePrompt, signal) {
2745 const IMAGE_FORMAT = 'png';2763 const IMAGE_FORMAT = 'png';
2746 const PROMPT_LIMIT = 10000;2764 const PROMPT_LIMIT = 10000;
27472765
@@ -2749,6 +2767,7 @@ async function generateStabilityImage(prompt, negativePrompt) {
2749 const response = await fetch('/api/sd/stability/generate', {2767 const response = await fetch('/api/sd/stability/generate', {
2750 method: 'POST',2768 method: 'POST',
2751 headers: getRequestHeaders(),2769 headers: getRequestHeaders(),
2770 signal: signal,
2752 body: JSON.stringify({2771 body: JSON.stringify({
2753 model: extension_settings.sd.model,2772 model: extension_settings.sd.model,
2754 payload: {2773 payload: {
@@ -2783,12 +2802,14 @@ async function generateStabilityImage(prompt, negativePrompt) {
2783 *2802 *
2784 * @param {string} prompt - The main instruction used to guide the image generation.2803 * @param {string} prompt - The main instruction used to guide the image generation.
2785 * @param {string} negativePrompt - The instruction used to restrict the image generation.2804 * @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.
2786 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.2806 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
2787 */2807 */
2788async function generateHordeImage(prompt, negativePrompt) {2808async function generateHordeImage(prompt, negativePrompt, signal) {
2789 const result = await fetch('/api/horde/generate-image', {2809 const result = await fetch('/api/horde/generate-image', {
2790 method: 'POST',2810 method: 'POST',
2791 headers: getRequestHeaders(),2811 headers: getRequestHeaders(),
2812 signal: signal,
2792 body: JSON.stringify({2813 body: JSON.stringify({
2793 prompt: prompt,2814 prompt: prompt,
2794 sampler: extension_settings.sd.sampler,2815 sampler: extension_settings.sd.sampler,
@@ -2821,13 +2842,15 @@ async function generateHordeImage(prompt, negativePrompt) {
2821 *2842 *
2822 * @param {string} prompt - The main instruction used to guide the image generation.2843 * @param {string} prompt - The main instruction used to guide the image generation.
2823 * @param {string} negativePrompt - The instruction used to restrict the image generation.2844 * @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.
2824 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.2846 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
2825 */2847 */
2826async function generateAutoImage(prompt, negativePrompt) {2848async function generateAutoImage(prompt, negativePrompt, signal) {
2827 const isValidVae = extension_settings.sd.vae && !['N/A', placeholderVae].includes(extension_settings.sd.vae);2849 const isValidVae = extension_settings.sd.vae && !['N/A', placeholderVae].includes(extension_settings.sd.vae);
2828 const result = await fetch('/api/sd/generate', {2850 const result = await fetch('/api/sd/generate', {
2829 method: 'POST',2851 method: 'POST',
2830 headers: getRequestHeaders(),2852 headers: getRequestHeaders(),
2853 signal: signal,
2831 body: JSON.stringify({2854 body: JSON.stringify({
2832 ...getSdRequestBody(),2855 ...getSdRequestBody(),
2833 prompt: prompt,2856 prompt: prompt,
@@ -2875,12 +2898,14 @@ async function generateAutoImage(prompt, negativePrompt) {
2875 *2898 *
2876 * @param {string} prompt - The main instruction used to guide the image generation.2899 * @param {string} prompt - The main instruction used to guide the image generation.
2877 * @param {string} negativePrompt - The instruction used to restrict the image generation.2900 * @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.
2878 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.2902 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
2879 */2903 */
2880async function generateDrawthingsImage(prompt, negativePrompt) {2904async function generateDrawthingsImage(prompt, negativePrompt, signal) {
2881 const result = await fetch('/api/sd/drawthings/generate', {2905 const result = await fetch('/api/sd/drawthings/generate', {
2882 method: 'POST',2906 method: 'POST',
2883 headers: getRequestHeaders(),2907 headers: getRequestHeaders(),
2908 signal: signal,
2884 body: JSON.stringify({2909 body: JSON.stringify({
2885 ...getSdRequestBody(),2910 ...getSdRequestBody(),
2886 prompt: prompt,2911 prompt: prompt,
@@ -2914,14 +2939,16 @@ async function generateDrawthingsImage(prompt, negativePrompt) {
2914 *2939 *
2915 * @param {string} prompt - The main instruction used to guide the image generation.2940 * @param {string} prompt - The main instruction used to guide the image generation.
2916 * @param {string} negativePrompt - The instruction used to restrict the image generation.2941 * @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.
2917 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.2943 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
2918 */2944 */
2919async function generateNovelImage(prompt, negativePrompt) {2945async function generateNovelImage(prompt, negativePrompt, signal) {
2920 const { steps, width, height, sm, sm_dyn } = getNovelParams();2946 const { steps, width, height, sm, sm_dyn } = getNovelParams();
29212947
2922 const result = await fetch('/api/novelai/generate-image', {2948 const result = await fetch('/api/novelai/generate-image', {
2923 method: 'POST',2949 method: 'POST',
2924 headers: getRequestHeaders(),2950 headers: getRequestHeaders(),
2951 signal: signal,
2925 body: JSON.stringify({2952 body: JSON.stringify({
2926 prompt: prompt,2953 prompt: prompt,
2927 model: extension_settings.sd.model,2954 model: extension_settings.sd.model,
@@ -3010,7 +3037,13 @@ function getNovelParams() {
3010 return { steps, width, height, sm, sm_dyn };3037 return { steps, width, height, sm, sm_dyn };
3011}3038}
30123039
3013async 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 */
3046async function generateOpenAiImage(prompt, signal) {
3014 const dalle2PromptLimit = 1000;3047 const dalle2PromptLimit = 1000;
3015 const dalle3PromptLimit = 4000;3048 const dalle3PromptLimit = 4000;
30163049
@@ -3045,6 +3078,7 @@ async function generateOpenAiImage(prompt) {
3045 const result = await fetch('/api/openai/generate-image', {3078 const result = await fetch('/api/openai/generate-image', {
3046 method: 'POST',3079 method: 'POST',
3047 headers: getRequestHeaders(),3080 headers: getRequestHeaders(),
3081 signal: signal,
3048 body: JSON.stringify({3082 body: JSON.stringify({
3049 prompt: prompt,3083 prompt: prompt,
3050 model: extension_settings.sd.model,3084 model: extension_settings.sd.model,
@@ -3070,9 +3104,10 @@ async function generateOpenAiImage(prompt) {
3070 *3104 *
3071 * @param {string} prompt - The main instruction used to guide the image generation.3105 * @param {string} prompt - The main instruction used to guide the image generation.
3072 * @param {string} negativePrompt - The instruction used to restrict the image generation.3106 * @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.
3073 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.3108 * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
3074 */3109 */
3075async function generateComfyImage(prompt, negativePrompt) {3110async function generateComfyImage(prompt, negativePrompt, signal) {
3076 const placeholders = [3111 const placeholders = [
3077 'model',3112 'model',
3078 'vae',3113 'vae',
@@ -3133,6 +3168,7 @@ async function generateComfyImage(prompt, negativePrompt) {
3133 const promptResult = await fetch('/api/sd/comfy/generate', {3168 const promptResult = await fetch('/api/sd/comfy/generate', {
3134 method: 'POST',3169 method: 'POST',
3135 headers: getRequestHeaders(),3170 headers: getRequestHeaders(),
3171 signal: signal,
3136 body: JSON.stringify({3172 body: JSON.stringify({
3137 url: extension_settings.sd.comfy_url,3173 url: extension_settings.sd.comfy_url,
3138 prompt: `{3174 prompt: `{
@@ -3245,7 +3281,7 @@ async function onComfyNewWorkflowClick() {
3245 if (!name) {3281 if (!name) {
3246 return;3282 return;
3247 }3283 }
3248 if (!name.toLowerCase().endsWith('.json')) {3284 if (!String(name).toLowerCase().endsWith('.json')) {
3249 name += '.json';3285 name += '.json';
3250 }3286 }
3251 extension_settings.sd.comfy_workflow = name;3287 extension_settings.sd.comfy_workflow = name;
@@ -3448,8 +3484,10 @@ async function sdMessageButton(e) {
3448 const messageText = message?.mes;3484 const messageText = message?.mes;
3449 const hasSavedImage = message?.extra?.image && message?.extra?.title;3485 const hasSavedImage = message?.extra?.image && message?.extra?.title;
3450 const hasSavedNegative = message?.extra?.negative;3486 const hasSavedNegative = message?.extra?.negative;
3487 const abortController = new AbortController();
34513488
3452 if ($icon.hasClass(busyClass)) {3489 if ($icon.hasClass(busyClass)) {
3490 abortController.abort();
3453 console.log('Previous image is still being generated...');3491 console.log('Previous image is still being generated...');
3454 return;3492 return;
3455 }3493 }
@@ -3466,7 +3504,7 @@ async function sdMessageButton(e) {
3466 const generationType = message?.extra?.generationType ?? generationMode.FREE;3504 const generationType = message?.extra?.generationType ?? generationMode.FREE;
3467 console.log('Regenerating an image, using existing prompt:', prompt);3505 console.log('Regenerating an image, using existing prompt:', prompt);
3468 dimensions = setTypeSpecificDimensions(generationType);3506 dimensions = setTypeSpecificDimensions(generationType);
3469 await sendGenerationRequest(generationType, prompt, negative, characterFileName, saveGeneratedImage, initiators.action);3507 await sendGenerationRequest(generationType, prompt, negative, characterFileName, saveGeneratedImage, initiators.action, abortController.signal);
3470 }3508 }
3471 else {3509 else {
3472 console.log('doing /sd raw last');3510 console.log('doing /sd raw last');
src/endpoints/horde.js+11 -0
@@ -339,7 +339,18 @@ router.post('/generate-image', jsonParser, async (request, response) => {
339 return response.sendStatus(400);339 return response.sendStatus(400);
340 }340 }
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
342 for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {352 for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
353 controller.signal.throwIfAborted();
343 await delay(CHECK_INTERVAL);354 await delay(CHECK_INTERVAL);
344 const check = await ai_horde.getImageGenerationCheck(generation.id);355 const check = await ai_horde.getImageGenerationCheck(generation.id);
345 console.log(check);356 console.log(check);