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