Stable Diffusion: fix image-prompt profile being ignored (exclude from presets; generate under the chosen profile with full context)

fec2f49e9803e457282f43757f59fac971229bea

permissionBRICK <permissionBRICK@gmail.com>

1 files changed, +77 -15Ignore whitespace
public/scripts/extensions/stable-diffusion/index.js+77 -15
@@ -385,6 +385,9 @@ const PRESET_EXCLUDE_KEYS = [
385385 'settings_preset_primary',
386386 'settings_preset_secondary',
387387 'settings_fallback_enabled',
388+ // The image-prompt LLM profile is independent of the image backend, so it must
389+ // never be captured/swapped by image-generation presets or the fallback retry.
390+ 'prompt_generation_profile',
388391 'prompts',
389392 'character_prompts',
390393 'character_negative_prompts',
@@ -421,6 +424,11 @@ function applySdSettingsSnapshot(snapshot) {
421424 return;
422425 }
423426 for (const key of Object.keys(snapshot)) {
427+ // Skip excluded keys so older presets that were saved before a key was
428+ // excluded (e.g. prompt_generation_profile) can't clobber it on load.
429+ if (PRESET_EXCLUDE_KEYS.includes(key)) {
430+ continue;
431+ }
424432 extension_settings.sd[key] = structuredClone(snapshot[key]);
425433 }
426434}
@@ -543,6 +551,9 @@ function toggleSourceControls() {
543551}
544552
545553let promptGenerationProfileDropdownInitialized = false;
554+// Warn at most once per session if a prompt-gen profile is selected but cannot be
555+// applied because there is no active connection profile to restore afterward.
556+let promptProfileWarnedNoBaseProfile = false;
546557
547558/**
548559 * Populates the dedicated prompt-generation connection profile dropdown.
@@ -3652,26 +3663,18 @@ function getUserAvatarUrl() {
36523663 */
36533664async function generatePrompt(quietPrompt) {
36543665 const toast = toastr.info('Generating image prompt with an LLM...', 'Image Generation');
3655- let reply;
36563666 const profileId = extension_settings.sd.prompt_generation_profile;
3667+ let reply;
36573668
36583669 if (profileId)try {
3659- try {
3670+ reply = profileId
3660- // Use the chosen Connection Manager profile (non-streaming) regardless of the active model.
3671+ ? await withConnectionProfile(profileId, () => generateQuietPrompt({ quietPrompt }))
3661- // 1024 tokens is a sensible default cap for an image-prompt description.
3672+ : await generateQuietPrompt({ quietPrompt });
3662- const data = await ConnectionManagerRequestService.sendRequest(profileId, quietPrompt, 1024);
3673+ } finally {
3663- // Non-streaming requests resolve to ExtractedData ({ content, reasoning }); extract the plain text.
3674+ toastr.clear(toast);
3664- reply = typeof data === 'object' && data !== null ? String(data.content ?? '') : '';
3665- } catch (err) {
3666- console.warn('SD: prompt-generation profile failed, falling back to active model', err);
3667- reply = await generateQuietPrompt({ quietPrompt });
3668- }
3669- } else {
3670- reply = await generateQuietPrompt({ quietPrompt });
36713675 }
36723676
36733677 const processedReply = processReply(reply);
3674- toastr.clear(toast);
36753678
36763679 if (!processedReply) {
36773680 toastr.error('Prompt generation produced no text. Make sure you\'re using a valid instruct template and try again', 'Image Generation');
@@ -3682,6 +3685,65 @@ async function generatePrompt(quietPrompt) {
36823685}
36833686
36843687/**
3688+ * Runs a callback with a specific Connection Manager profile temporarily active,
3689+ * then restores the previously active profile. This lets the image prompt be
3690+ * generated by the chosen LLM *with the full chat context* (via generateQuietPrompt),
3691+ * instead of a context-free one-off request.
3692+ *
3693+ * The switch is only performed when a real connection profile is currently active
3694+ * (so it can be reliably restored). When no profile is active — i.e. the user drives
3695+ * the API panel manually — switching to a profile could not be undone without
3696+ * clobbering those manual settings, so we leave the active model in place and warn once.
3697+ *
3698+ * @param {string} targetProfileId Profile to activate for the duration of the callback.
3699+ * @param {() => Promise<any>} callback Work to run while the target profile is active.
3700+ * @returns {Promise<any>} The callback's result.
3701+ */
3702+async function withConnectionProfile(targetProfileId, callback) {
3703+ const select = /** @type {HTMLSelectElement} */ (document.getElementById('connection_profiles'));
3704+ const connectionManager = extension_settings.connectionManager;
3705+ const currentProfileId = connectionManager?.selectedProfile;
3706+
3707+ const canSwitch = !!select
3708+ && !!connectionManager
3709+ && Array.isArray(connectionManager.profiles)
3710+ && connectionManager.profiles.some(p => p.id === targetProfileId)
3711+ && Array.from(select.options).some(o => o.value === targetProfileId)
3712+ && !!currentProfileId // a real profile is active, so it can be restored afterwards
3713+ && currentProfileId !== targetProfileId;
3714+
3715+ if (!canSwitch) {
3716+ // Selected but no base profile to restore from -> use the active model and warn once.
3717+ if (targetProfileId && !currentProfileId && !promptProfileWarnedNoBaseProfile) {
3718+ promptProfileWarnedNoBaseProfile = true;
3719+ toastr.info('The image-prompt LLM profile is only applied while a connection profile is active (so the original can be restored). Using the current model.', 'Image Generation');
3720+ }
3721+ return await callback();
3722+ }
3723+
3724+ const switchToProfile = async (profileId) => {
3725+ const loaded = new Promise(resolve => eventSource.once(event_types.CONNECTION_PROFILE_LOADED, resolve));
3726+ const timeout = new Promise(resolve => setTimeout(resolve, 10000));
3727+ const index = Array.from(select.options).findIndex(o => o.value === profileId);
3728+ select.selectedIndex = index >= 0 ? index : 0;
3729+ select.dispatchEvent(new Event('change'));
3730+ // Don't hang the image generation if the event never fires.
3731+ await Promise.race([loaded, timeout]);
3732+ };
3733+
3734+ await switchToProfile(targetProfileId);
3735+ try {
3736+ return await callback();
3737+ } finally {
3738+ try {
3739+ await switchToProfile(currentProfileId);
3740+ } catch (err) {
3741+ console.error('SD: failed to restore the previous connection profile after image-prompt generation', err);
3742+ }
3743+ }
3744+}
3745+
3746+/**
36853747 * Sends a request to image generation endpoint and processes the result.
36863748 * @param {number} generationType Type of image generation
36873749 * @param {string} prompt Prompt to be used for image generation