Stable Diffusion: dedicated LLM connection profile for image prompt generation Reviewed-by: auto-review
| @@ -44,7 +44,7 @@ import { | |||
| 44 | import { getMessageTimeStamp, humanizedDateTime } from '../../RossAscends-mods.js'; | 44 | import { getMessageTimeStamp, humanizedDateTime } from '../../RossAscends-mods.js'; |
| 45 | import { SECRET_KEYS, secret_state } from '../../secrets.js'; | 45 | import { SECRET_KEYS, secret_state } from '../../secrets.js'; |
| 46 | import { getNovelAnlas, getNovelUnlimitedImageGeneration, loadNovelSubscriptionData } from '../../nai-settings.js'; | 46 | import { getNovelAnlas, getNovelUnlimitedImageGeneration, loadNovelSubscriptionData } from '../../nai-settings.js'; |
| 47 | import { getMultimodalCaption } from '../shared.js'; | 47 | import { ConnectionManagerRequestService, getMultimodalCaption } from '../shared.js'; |
| 48 | import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; | 48 | import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; |
| 49 | import { SlashCommand } from '../../slash-commands/SlashCommand.js'; | 49 | import { SlashCommand } from '../../slash-commands/SlashCommand.js'; |
| 50 | import { | 50 | import { |
| @@ -365,6 +365,9 @@ const defaultSettings = { | |||
| 365 | settings_preset_primary: null, | 365 | settings_preset_primary: null, |
| 366 | settings_preset_secondary: null, | 366 | settings_preset_secondary: null, |
| 367 | settings_fallback_enabled: false, | 367 | settings_fallback_enabled: false, |
| 368 | |||
| 369 | // Dedicated LLM connection profile for image-prompt generation ('' = use active model) | ||
| 370 | prompt_generation_profile: '', | ||
| 368 | }; | 371 | }; |
| 369 | 372 | ||
| 370 | /** | 373 | /** |
| @@ -534,6 +537,34 @@ function toggleSourceControls() { | |||
| 534 | }); | 537 | }); |
| 535 | } | 538 | } |
| 536 | 539 | ||
| 540 | let promptGenerationProfileDropdownInitialized = false; | ||
| 541 | |||
| 542 | /** | ||
| 543 | * Populates the dedicated prompt-generation connection profile dropdown. | ||
| 544 | * Only initializes once to avoid attaching duplicate Connection Manager event listeners | ||
| 545 | * when loadSettings() is called again (e.g. after loading a settings preset). | ||
| 546 | */ | ||
| 547 | function initPromptGenerationProfileDropdown() { | ||
| 548 | if (promptGenerationProfileDropdownInitialized) { | ||
| 549 | return; | ||
| 550 | } | ||
| 551 | |||
| 552 | try { | ||
| 553 | ConnectionManagerRequestService.handleDropdown( | ||
| 554 | '#sd_prompt_generation_profile', | ||
| 555 | extension_settings.sd.prompt_generation_profile, | ||
| 556 | (profile) => { | ||
| 557 | extension_settings.sd.prompt_generation_profile = profile?.id ?? ''; | ||
| 558 | saveSettingsDebounced(); | ||
| 559 | }, | ||
| 560 | ); | ||
| 561 | promptGenerationProfileDropdownInitialized = true; | ||
| 562 | } catch (error) { | ||
| 563 | // Connection Manager may be unavailable/disabled; leave the dropdown empty in that case. | ||
| 564 | console.warn('SD: could not populate prompt-generation profile dropdown', error); | ||
| 565 | } | ||
| 566 | } | ||
| 567 | |||
| 537 | async function loadSettings() { | 568 | async function loadSettings() { |
| 538 | // Initialize settings | 569 | // Initialize settings |
| 539 | if (Object.keys(extension_settings.sd).length === 0) { | 570 | if (Object.keys(extension_settings.sd).length === 0) { |
| @@ -661,6 +692,8 @@ async function loadSettings() { | |||
| 661 | const resolutionId = getClosestKnownResolution(); | 692 | const resolutionId = getClosestKnownResolution(); |
| 662 | $('#sd_resolution').val(resolutionId); | 693 | $('#sd_resolution').val(resolutionId); |
| 663 | 694 | ||
| 695 | initPromptGenerationProfileDropdown(); | ||
| 696 | |||
| 664 | toggleSourceControls(); | 697 | toggleSourceControls(); |
| 665 | addPromptTemplates(); | 698 | addPromptTemplates(); |
| 666 | registerFunctionTool(); | 699 | registerFunctionTool(); |
| @@ -3421,7 +3454,24 @@ function getUserAvatarUrl() { | |||
| 3421 | */ | 3454 | */ |
| 3422 | async function generatePrompt(quietPrompt) { | 3455 | async function generatePrompt(quietPrompt) { |
| 3423 | const toast = toastr.info('Generating image prompt with an LLM...', 'Image Generation'); | 3456 | const toast = toastr.info('Generating image prompt with an LLM...', 'Image Generation'); |
| 3424 | const reply = await generateQuietPrompt({ quietPrompt }); | 3457 | let reply; |
| 3458 | const profileId = extension_settings.sd.prompt_generation_profile; | ||
| 3459 | |||
| 3460 | if (profileId) { | ||
| 3461 | try { | ||
| 3462 | // Use the chosen Connection Manager profile (non-streaming) regardless of the active model. | ||
| 3463 | // 1024 tokens is a sensible default cap for an image-prompt description. | ||
| 3464 | const data = await ConnectionManagerRequestService.sendRequest(profileId, quietPrompt, 1024); | ||
| 3465 | // Non-streaming requests resolve to ExtractedData ({ content, reasoning }); extract the plain text. | ||
| 3466 | reply = typeof data === 'object' && data !== null ? String(data.content ?? '') : ''; | ||
| 3467 | } catch (err) { | ||
| 3468 | console.warn('SD: prompt-generation profile failed, falling back to active model', err); | ||
| 3469 | reply = await generateQuietPrompt({ quietPrompt }); | ||
| 3470 | } | ||
| 3471 | } else { | ||
| 3472 | reply = await generateQuietPrompt({ quietPrompt }); | ||
| 3473 | } | ||
| 3474 | |||
| 3425 | const processedReply = processReply(reply); | 3475 | const processedReply = processReply(reply); |
| 3426 | toastr.clear(toast); | 3476 | toastr.clear(toast); |
| 3427 | 3477 | ||
| @@ -39,6 +39,9 @@ | |||
| 39 | <input id="sd_minimal_prompt_processing" type="checkbox" /> | 39 | <input id="sd_minimal_prompt_processing" type="checkbox" /> |
| 40 | <span data-i18n="sd_minimal_prompt_processing_txt">Minimal response prompt processing</span> | 40 | <span data-i18n="sd_minimal_prompt_processing_txt">Minimal response prompt processing</span> |
| 41 | </label> | 41 | </label> |
| 42 | <label for="sd_prompt_generation_profile" data-i18n="Prompt generation connection profile">Prompt generation connection profile</label> | ||
| 43 | <select id="sd_prompt_generation_profile" class="text_pole"></select> | ||
| 44 | <small data-i18n="sd_prompt_generation_profile_small">Connection profile always used to generate the image prompt text. Leave empty to use the currently active model.</small> | ||
| 42 | <hr> | 45 | <hr> |
| 43 | <h4 data-i18n="Settings Presets & Fallback">Settings Presets & Fallback</h4> | 46 | <h4 data-i18n="Settings Presets & Fallback">Settings Presets & Fallback</h4> |
| 44 | <small data-i18n="sd_settings_presets_small">Save the current backend/connection settings (source, model, sampler, dimensions, etc.) as Primary or Secondary presets and load them back later. Prompt templates, styles, and custom entries are not included.</small> | 47 | <small data-i18n="sd_settings_presets_small">Save the current backend/connection settings (source, model, sampler, dimensions, etc.) as Primary or Secondary presets and load them back later. Prompt templates, styles, and custom entries are not included.</small> |