mirror of
				https://github.com/SillyTavern/SillyTavern.git
				synced 2025-06-05 21:59:27 +02:00 
			
		
		
		
	Merge branch 'staging' into parser-followup-2
This commit is contained in:
		@@ -22,7 +22,7 @@ import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules, rend
 | 
			
		||||
import { selected_group } from '../../group-chats.js';
 | 
			
		||||
import { stringFormat, initScrollHeight, resetScrollHeight, getCharaFilename, saveBase64AsFile, getBase64Async, delay, isTrueBoolean, debounce } from '../../utils.js';
 | 
			
		||||
import { getMessageTimeStamp, humanizedDateTime } from '../../RossAscends-mods.js';
 | 
			
		||||
import { SECRET_KEYS, secret_state } from '../../secrets.js';
 | 
			
		||||
import { SECRET_KEYS, secret_state, writeSecret } from '../../secrets.js';
 | 
			
		||||
import { getNovelUnlimitedImageGeneration, getNovelAnlas, loadNovelSubscriptionData } from '../../nai-settings.js';
 | 
			
		||||
import { getMultimodalCaption } from '../shared.js';
 | 
			
		||||
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
 | 
			
		||||
@@ -49,6 +49,7 @@ const sources = {
 | 
			
		||||
    togetherai: 'togetherai',
 | 
			
		||||
    drawthings: 'drawthings',
 | 
			
		||||
    pollinations: 'pollinations',
 | 
			
		||||
    stability: 'stability',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const initiators = {
 | 
			
		||||
@@ -282,6 +283,9 @@ const defaultSettings = {
 | 
			
		||||
    wand_visible: false,
 | 
			
		||||
    command_visible: false,
 | 
			
		||||
    interactive_visible: false,
 | 
			
		||||
 | 
			
		||||
    // Stability AI settings
 | 
			
		||||
    stability_style_preset: 'anime',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const writePromptFieldsDebounced = debounce(writePromptFields, debounce_timeout.relaxed);
 | 
			
		||||
@@ -444,6 +448,7 @@ async function loadSettings() {
 | 
			
		||||
    $('#sd_wand_visible').prop('checked', extension_settings.sd.wand_visible);
 | 
			
		||||
    $('#sd_command_visible').prop('checked', extension_settings.sd.command_visible);
 | 
			
		||||
    $('#sd_interactive_visible').prop('checked', extension_settings.sd.interactive_visible);
 | 
			
		||||
    $('#sd_stability_style_preset').val(extension_settings.sd.stability_style_preset);
 | 
			
		||||
 | 
			
		||||
    for (const style of extension_settings.sd.styles) {
 | 
			
		||||
        const option = document.createElement('option');
 | 
			
		||||
@@ -671,7 +676,7 @@ async function refinePrompt(prompt, allowExpand, isNegative = false) {
 | 
			
		||||
        const refinedPrompt = await callGenericPopup(text + 'Press "Cancel" to abort the image generation.', POPUP_TYPE.INPUT, prompt.trim(), { rows: 5, okButton: 'Continue' });
 | 
			
		||||
 | 
			
		||||
        if (refinedPrompt) {
 | 
			
		||||
            return refinedPrompt;
 | 
			
		||||
            return String(refinedPrompt);
 | 
			
		||||
        } else {
 | 
			
		||||
            throw new Error('Generation aborted by user.');
 | 
			
		||||
        }
 | 
			
		||||
@@ -1084,6 +1089,26 @@ function onComfyWorkflowChange() {
 | 
			
		||||
    extension_settings.sd.comfy_workflow = $('#sd_comfy_workflow').find(':selected').val();
 | 
			
		||||
    saveSettingsDebounced();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function onStabilityKeyClick() {
 | 
			
		||||
    const popupText = 'Stability AI API Key:';
 | 
			
		||||
    const key = await callGenericPopup(popupText, POPUP_TYPE.INPUT);
 | 
			
		||||
 | 
			
		||||
    if (!key) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await writeSecret(SECRET_KEYS.STABILITY, String(key));
 | 
			
		||||
 | 
			
		||||
    toastr.success('API Key saved');
 | 
			
		||||
    await loadSettingOptions();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function onStabilityStylePresetChange() {
 | 
			
		||||
    extension_settings.sd.stability_style_preset = String($('#sd_stability_style_preset').val());
 | 
			
		||||
    saveSettingsDebounced();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function changeComfyWorkflow(_, name) {
 | 
			
		||||
    name = name.replace(/(\.json)?$/i, '.json');
 | 
			
		||||
    if ($(`#sd_comfy_workflow > [value="${name}"]`).length > 0) {
 | 
			
		||||
@@ -1193,7 +1218,7 @@ async function onModelChange() {
 | 
			
		||||
    extension_settings.sd.model = $('#sd_model').find(':selected').val();
 | 
			
		||||
    saveSettingsDebounced();
 | 
			
		||||
 | 
			
		||||
    const cloudSources = [sources.horde, sources.novel, sources.openai, sources.togetherai, sources.pollinations];
 | 
			
		||||
    const cloudSources = [sources.horde, sources.novel, sources.openai, sources.togetherai, sources.pollinations, sources.stability];
 | 
			
		||||
 | 
			
		||||
    if (cloudSources.includes(extension_settings.sd.source)) {
 | 
			
		||||
        return;
 | 
			
		||||
@@ -1402,6 +1427,9 @@ async function loadSamplers() {
 | 
			
		||||
        case sources.pollinations:
 | 
			
		||||
            samplers = ['N/A'];
 | 
			
		||||
            break;
 | 
			
		||||
        case sources.stability:
 | 
			
		||||
            samplers = ['N/A'];
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const sampler of samplers) {
 | 
			
		||||
@@ -1585,6 +1613,9 @@ async function loadModels() {
 | 
			
		||||
        case sources.pollinations:
 | 
			
		||||
            models = await loadPollinationsModels();
 | 
			
		||||
            break;
 | 
			
		||||
        case sources.stability:
 | 
			
		||||
            models = await loadStabilityModels();
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const model of models) {
 | 
			
		||||
@@ -1601,6 +1632,16 @@ async function loadModels() {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function loadStabilityModels() {
 | 
			
		||||
    $('#sd_stability_key').toggleClass('success', !!secret_state[SECRET_KEYS.STABILITY]);
 | 
			
		||||
 | 
			
		||||
    return [
 | 
			
		||||
        { value: 'stable-image-ultra', text: 'Stable Image Ultra' },
 | 
			
		||||
        { value: 'stable-image-core', text: 'Stable Image Core' },
 | 
			
		||||
        { value: 'stable-diffusion-3', text: 'Stable Diffusion 3' },
 | 
			
		||||
    ];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function loadPollinationsModels() {
 | 
			
		||||
    return [
 | 
			
		||||
        {
 | 
			
		||||
@@ -1932,6 +1973,9 @@ async function loadSchedulers() {
 | 
			
		||||
        case sources.comfy:
 | 
			
		||||
            schedulers = await loadComfySchedulers();
 | 
			
		||||
            break;
 | 
			
		||||
        case sources.stability:
 | 
			
		||||
            schedulers = ['N/A'];
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const scheduler of schedulers) {
 | 
			
		||||
@@ -2005,6 +2049,9 @@ async function loadVaes() {
 | 
			
		||||
        case sources.comfy:
 | 
			
		||||
            vaes = await loadComfyVaes();
 | 
			
		||||
            break;
 | 
			
		||||
        case sources.stability:
 | 
			
		||||
            vaes = ['N/A'];
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const vae of vaes) {
 | 
			
		||||
@@ -2485,6 +2532,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
 | 
			
		||||
            case sources.pollinations:
 | 
			
		||||
                result = await generatePollinationsImage(prefixedPrompt, negativePrompt);
 | 
			
		||||
                break;
 | 
			
		||||
            case sources.stability:
 | 
			
		||||
                result = await generateStabilityImage(prefixedPrompt, negativePrompt);
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!result.data) {
 | 
			
		||||
@@ -2508,6 +2558,12 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
 | 
			
		||||
    return base64Image;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Generates an image using the TogetherAI API.
 | 
			
		||||
 * @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 generateTogetherAIImage(prompt, negativePrompt) {
 | 
			
		||||
    const result = await fetch('/api/sd/together/generate', {
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
@@ -2532,6 +2588,12 @@ async function generateTogetherAIImage(prompt, negativePrompt) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Generates an image using the Pollinations API.
 | 
			
		||||
 * @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 generatePollinationsImage(prompt, negativePrompt) {
 | 
			
		||||
    const result = await fetch('/api/sd/pollinations/generate', {
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
@@ -2600,6 +2662,84 @@ async function generateExtrasImage(prompt, negativePrompt) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Gets an aspect ratio for Stability that is the closest to the given width and height.
 | 
			
		||||
 * @param {number} width Target width
 | 
			
		||||
 * @param {number} height Target height
 | 
			
		||||
 * @returns {string} Closest aspect ratio as a string
 | 
			
		||||
 */
 | 
			
		||||
function getClosestAspectRatio(width, height) {
 | 
			
		||||
    const aspectRatios = {
 | 
			
		||||
        '16:9': 16 / 9,
 | 
			
		||||
        '1:1': 1,
 | 
			
		||||
        '21:9': 21 / 9,
 | 
			
		||||
        '2:3': 2 / 3,
 | 
			
		||||
        '3:2': 3 / 2,
 | 
			
		||||
        '4:5': 4 / 5,
 | 
			
		||||
        '5:4': 5 / 4,
 | 
			
		||||
        '9:16': 9 / 16,
 | 
			
		||||
        '9:21': 9 / 21,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const aspectRatio = width / height;
 | 
			
		||||
 | 
			
		||||
    let closestAspectRatio = Object.keys(aspectRatios)[0];
 | 
			
		||||
    let minDiff = Math.abs(aspectRatio - aspectRatios[closestAspectRatio]);
 | 
			
		||||
 | 
			
		||||
    for (const key in aspectRatios) {
 | 
			
		||||
        const diff = Math.abs(aspectRatio - aspectRatios[key]);
 | 
			
		||||
        if (diff < minDiff) {
 | 
			
		||||
            minDiff = diff;
 | 
			
		||||
            closestAspectRatio = key;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return closestAspectRatio;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Generates an image using Stability AI.
 | 
			
		||||
 * @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 generateStabilityImage(prompt, negativePrompt) {
 | 
			
		||||
    const IMAGE_FORMAT = 'png';
 | 
			
		||||
    const PROMPT_LIMIT = 10000;
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        const response = await fetch('/api/sd/stability/generate', {
 | 
			
		||||
            method: 'POST',
 | 
			
		||||
            headers: getRequestHeaders(),
 | 
			
		||||
            body: JSON.stringify({
 | 
			
		||||
                model: extension_settings.sd.model,
 | 
			
		||||
                payload: {
 | 
			
		||||
                    prompt: prompt.slice(0, PROMPT_LIMIT),
 | 
			
		||||
                    negative_prompt: negativePrompt.slice(0, PROMPT_LIMIT),
 | 
			
		||||
                    aspect_ratio: getClosestAspectRatio(extension_settings.sd.width, extension_settings.sd.height),
 | 
			
		||||
                    seed: extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : undefined,
 | 
			
		||||
                    style_preset: extension_settings.sd.stability_style_preset,
 | 
			
		||||
                    output_format: IMAGE_FORMAT,
 | 
			
		||||
                },
 | 
			
		||||
            }),
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (!response.ok) {
 | 
			
		||||
            throw new Error(`HTTP ${response.status}: ${response.statusText}`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const base64Image = await response.text();
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            format: IMAGE_FORMAT,
 | 
			
		||||
            data: base64Image,
 | 
			
		||||
        };
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        console.error('Error generating image with Stability AI:', error);
 | 
			
		||||
        throw error;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Generates a "horde" image using the provided prompt and configuration settings.
 | 
			
		||||
 *
 | 
			
		||||
@@ -3228,6 +3368,8 @@ function isValidState() {
 | 
			
		||||
            return secret_state[SECRET_KEYS.TOGETHERAI];
 | 
			
		||||
        case sources.pollinations:
 | 
			
		||||
            return true;
 | 
			
		||||
        case sources.stability:
 | 
			
		||||
            return secret_state[SECRET_KEYS.STABILITY];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -3455,6 +3597,8 @@ jQuery(async () => {
 | 
			
		||||
    $('#sd_command_visible').on('input', onCommandVisibleInput);
 | 
			
		||||
    $('#sd_interactive_visible').on('input', onInteractiveVisibleInput);
 | 
			
		||||
    $('#sd_swap_dimensions').on('click', onSwapDimensionsClick);
 | 
			
		||||
    $('#sd_stability_key').on('click', onStabilityKeyClick);
 | 
			
		||||
    $('#sd_stability_style_preset').on('change', onStabilityStylePresetChange);
 | 
			
		||||
 | 
			
		||||
    $('.sd_settings .inline-drawer-toggle').on('click', function () {
 | 
			
		||||
        initScrollHeight($('#sd_prompt_prefix'));
 | 
			
		||||
 
 | 
			
		||||
@@ -44,6 +44,7 @@
 | 
			
		||||
                <option value="openai">OpenAI (DALL-E)</option>
 | 
			
		||||
                <option value="pollinations">Pollinations</option>
 | 
			
		||||
                <option value="vlad">SD.Next (vladmandic)</option>
 | 
			
		||||
                <option value="stability">Stability AI</option>
 | 
			
		||||
                <option value="auto">Stable Diffusion Web UI (AUTOMATIC1111)</option>
 | 
			
		||||
                <option value="horde">Stable Horde</option>
 | 
			
		||||
                <option value="togetherai">TogetherAI</option>
 | 
			
		||||
@@ -189,7 +190,45 @@
 | 
			
		||||
                    </label>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div data-sd-source="stability">
 | 
			
		||||
                <div class="flex-container flexnowrap alignItemsBaseline marginBot5">
 | 
			
		||||
                    <strong class="flex1" data-i18n="API Key">API Key</strong>
 | 
			
		||||
                    <div id="sd_stability_key" class="menu_button menu_button_icon">
 | 
			
		||||
                        <i class="fa-fw fa-solid fa-key"></i>
 | 
			
		||||
                        <span data-i18n="Click to set">Click to set</span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="marginBot5">
 | 
			
		||||
                    <i data-i18n="You can find your API key in the Stability AI dashboard.">
 | 
			
		||||
                        You can find your API key in the Stability AI dashboard.
 | 
			
		||||
                    </i>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <div class="flex-container">
 | 
			
		||||
                    <div class="flex1">
 | 
			
		||||
                        <label for="sd_stability_style_preset" data-i18n="Style Preset">Style Preset</label>
 | 
			
		||||
                        <select id="sd_stability_style_preset">
 | 
			
		||||
                            <option value="anime">Anime</option>
 | 
			
		||||
                            <option value="3d-model">3D Model</option>
 | 
			
		||||
                            <option value="analog-film">Analog Film</option>
 | 
			
		||||
                            <option value="cinematic">Cinematic</option>
 | 
			
		||||
                            <option value="comic-book">Comic Book</option>
 | 
			
		||||
                            <option value="digital-art">Digital Art</option>
 | 
			
		||||
                            <option value="enhance">Enhance</option>
 | 
			
		||||
                            <option value="fantasy-art">Fantasy Art</option>
 | 
			
		||||
                            <option value="isometric">Isometric</option>
 | 
			
		||||
                            <option value="line-art">Line Art</option>
 | 
			
		||||
                            <option value="low-poly">Low Poly</option>
 | 
			
		||||
                            <option value="modeling-compound">Modeling Compound</option>
 | 
			
		||||
                            <option value="neon-punk">Neon Punk</option>
 | 
			
		||||
                            <option value="origami">Origami</option>
 | 
			
		||||
                            <option value="photographic">Photographic</option>
 | 
			
		||||
                            <option value="pixel-art">Pixel Art</option>
 | 
			
		||||
                            <option value="tile-texture">Tile Texture</option>
 | 
			
		||||
                        </select>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="flex-container">
 | 
			
		||||
                <div class="flex1">
 | 
			
		||||
                    <label for="sd_model" data-i18n="Model">Model</label>
 | 
			
		||||
@@ -203,7 +242,7 @@
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="flex-container">
 | 
			
		||||
                <div class="flex1">
 | 
			
		||||
                <div class="flex1" data-sd-source="extras,horde,auto,drawthings,novel,vlad,comfy">
 | 
			
		||||
                    <label for="sd_sampler" data-i18n="Sampling method">Sampling method</label>
 | 
			
		||||
                    <select id="sd_sampler"></select>
 | 
			
		||||
                </div>
 | 
			
		||||
@@ -339,7 +378,7 @@
 | 
			
		||||
                </label>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div data-sd-source="novel,togetherai,pollinations,comfy,drawthings,vlad,auto,horde,extras" class="marginTop5">
 | 
			
		||||
            <div data-sd-source="novel,togetherai,pollinations,comfy,drawthings,vlad,auto,horde,extras,stability" class="marginTop5">
 | 
			
		||||
                <label for="sd_seed">
 | 
			
		||||
                    <span data-i18n="Seed">Seed</span>
 | 
			
		||||
                    <small data-i18n="(-1 for random)">(-1 for random)</small>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user