Blame Raw
Cohee · e3f41666 · · 497 lines (16.7 KB)
1 contributor
1import {
2 chat_metadata,
3 substituteParams,
4 this_chid,
5 eventSource,
6 event_types,
7 saveSettingsDebounced,
8 animation_duration,
9} from '../script.js';
10import { extension_settings, saveMetadataDebounced } from './extensions.js';
11import { selected_group } from './group-chats.js';
12import { getCharaFilename, delay } from './utils.js';
13import { power_user } from './power-user.js';
14
15const extensionName = 'cfg';
16const defaultSettings = {
17 global: {
18 'guidance_scale': 1,
19 'negative_prompt': '',
20 },
21 chara: [],
22};
23const settingType = {
24 guidance_scale: 0,
25 negative_prompt: 1,
26 positive_prompt: 2,
27};
28
29// Used for character and chat CFG values
30function updateSettings() {
31 saveSettingsDebounced();
32 loadSettings();
33}
34
35function setCharCfg(tempValue, setting) {
36 const avatarName = getCharaFilename();
37
38 // Assign temp object
39 let tempCharaCfg = {
40 name: avatarName,
41 };
42
43 switch (setting) {
44 case settingType.guidance_scale:
45 tempCharaCfg.guidance_scale = Number(tempValue);
46 break;
47 case settingType.negative_prompt:
48 tempCharaCfg.negative_prompt = tempValue;
49 break;
50 case settingType.positive_prompt:
51 tempCharaCfg.positive_prompt = tempValue;
52 break;
53 default:
54 return false;
55 }
56
57 let existingCharaCfgIndex;
58 let existingCharaCfg;
59
60 if (extension_settings.cfg.chara) {
61 existingCharaCfgIndex = extension_settings.cfg.chara.findIndex((e) => e.name === avatarName);
62 existingCharaCfg = extension_settings.cfg.chara[existingCharaCfgIndex];
63 }
64
65 if (extension_settings.cfg.chara && existingCharaCfg) {
66 const tempAssign = Object.assign(existingCharaCfg, tempCharaCfg);
67
68 // If both values are default, remove the entry
69 if (!existingCharaCfg.useChara &&
70 (tempAssign.guidance_scale ?? 1.00) === 1.00 &&
71 (tempAssign.negative_prompt?.length ?? 0) === 0 &&
72 (tempAssign.positive_prompt?.length ?? 0) === 0) {
73 extension_settings.cfg.chara.splice(existingCharaCfgIndex, 1);
74 }
75 } else if (avatarName && tempValue.length > 0) {
76 if (!extension_settings.cfg.chara) {
77 extension_settings.cfg.chara = [];
78 }
79
80 extension_settings.cfg.chara.push(tempCharaCfg);
81 } else {
82 console.debug('Character CFG error: No avatar name key could be found.');
83
84 // Don't save settings if something went wrong
85 return false;
86 }
87
88 updateSettings();
89
90 return true;
91}
92
93function setChatCfg(tempValue, setting) {
94 switch (setting) {
95 case settingType.guidance_scale:
96 chat_metadata[metadataKeys.guidance_scale] = tempValue;
97 break;
98 case settingType.negative_prompt:
99 chat_metadata[metadataKeys.negative_prompt] = tempValue;
100 break;
101 case settingType.positive_prompt:
102 chat_metadata[metadataKeys.positive_prompt] = tempValue;
103 break;
104 default:
105 return false;
106 }
107
108 saveMetadataDebounced();
109
110 return true;
111}
112
113// TODO: Only change CFG when character is selected
114function onCfgMenuItemClick() {
115 if (!selected_group && this_chid === undefined) {
116 toastr.warning('Select a character before trying to configure CFG', '', { timeOut: 2000 });
117 return;
118 }
119
120 //show CFG config if it's hidden
121 if ($('#cfgConfig').css('display') !== 'flex') {
122 $('#cfgConfig').addClass('resizing');
123 $('#cfgConfig').css('display', 'flex');
124 $('#cfgConfig').css('opacity', 0.0);
125 $('#cfgConfig').transition({
126 opacity: 1.0,
127 duration: animation_duration,
128 }, async function () {
129 await delay(50);
130 $('#cfgConfig').removeClass('resizing');
131 });
132
133 //auto-open the main AN inline drawer
134 if ($('#CFGBlockToggle')
135 .siblings('.inline-drawer-content')
136 .css('display') !== 'block') {
137 $('#floatingPrompt').addClass('resizing');
138 $('#CFGBlockToggle').trigger('click');
139 }
140 } else {
141 //hide AN if it's already displayed
142 $('#cfgConfig').addClass('resizing');
143 $('#cfgConfig').transition({
144 opacity: 0.0,
145 duration: animation_duration,
146 }, async function () {
147 await delay(50);
148 $('#cfgConfig').removeClass('resizing');
149 });
150 setTimeout(function () {
151 $('#cfgConfig').hide();
152 }, animation_duration);
153 }
154 //duplicate options menu close handler from script.js
155 //because this listener takes priority
156 $('#options').stop().fadeOut(animation_duration);
157}
158
159async function onChatChanged() {
160 loadSettings();
161 await modifyCharaHtml();
162}
163
164// Rearrange the panel if a group chat is present
165async function modifyCharaHtml() {
166 if (selected_group) {
167 $('#chara_cfg_container').hide();
168 $('#groupchat_cfg_use_chara_container').show();
169 } else {
170 $('#chara_cfg_container').show();
171 $('#groupchat_cfg_use_chara_container').hide();
172 // TODO: Remove chat checkbox here
173 }
174}
175
176// Reloads chat-specific settings
177function loadSettings() {
178 // Set chat CFG if it exists
179 $('#chat_cfg_guidance_scale').val(chat_metadata[metadataKeys.guidance_scale] ?? 1.0.toFixed(2));
180 $('#chat_cfg_guidance_scale_counter').val(chat_metadata[metadataKeys.guidance_scale]?.toFixed(2) ?? 1.0.toFixed(2));
181 $('#chat_cfg_negative_prompt').val(chat_metadata[metadataKeys.negative_prompt] ?? '');
182 $('#chat_cfg_positive_prompt').val(chat_metadata[metadataKeys.positive_prompt] ?? '');
183 $('#groupchat_cfg_use_chara').prop('checked', chat_metadata[metadataKeys.groupchat_individual_chars] ?? false);
184 if (chat_metadata[metadataKeys.prompt_combine]?.length > 0) {
185 chat_metadata[metadataKeys.prompt_combine].forEach((element) => {
186 $(`input[name="cfg_prompt_combine"][value="${element}"]`)
187 .prop('checked', true);
188 });
189 }
190
191 // Display the negative separator in quotes if not quoted already
192 let promptSeparatorDisplay = [];
193 const promptSeparator = chat_metadata[metadataKeys.prompt_separator];
194 if (promptSeparator) {
195 promptSeparatorDisplay.push(promptSeparator);
196 if (!promptSeparator.startsWith('"')) {
197 promptSeparatorDisplay.unshift('"');
198 }
199
200 if (!promptSeparator.endsWith('"')) {
201 promptSeparatorDisplay.push('"');
202 }
203 }
204
205 $('#cfg_prompt_separator').val(promptSeparatorDisplay.length === 0 ? '' : promptSeparatorDisplay.join(''));
206
207 $('#cfg_prompt_insertion_depth').val(chat_metadata[metadataKeys.prompt_insertion_depth] ?? 1);
208
209 // Set character CFG if it exists
210 if (!selected_group) {
211 const charaCfg = extension_settings.cfg.chara.find((e) => e.name === getCharaFilename());
212 $('#chara_cfg_guidance_scale').val(charaCfg?.guidance_scale ?? 1.00);
213 $('#chara_cfg_guidance_scale_counter').val(charaCfg?.guidance_scale?.toFixed(2) ?? 1.0.toFixed(2));
214 $('#chara_cfg_negative_prompt').val(charaCfg?.negative_prompt ?? '');
215 $('#chara_cfg_positive_prompt').val(charaCfg?.positive_prompt ?? '');
216 }
217}
218
219// Load initial extension settings
220async function initialLoadSettings() {
221 // Create the settings if they don't exist
222 extension_settings[extensionName] = extension_settings[extensionName] || {};
223 if (Object.keys(extension_settings[extensionName]).length === 0) {
224 Object.assign(extension_settings[extensionName], defaultSettings);
225 saveSettingsDebounced();
226 }
227
228 // Set global CFG values on load
229 $('#global_cfg_guidance_scale').val(extension_settings.cfg.global.guidance_scale);
230 $('#global_cfg_guidance_scale_counter').val(extension_settings.cfg.global.guidance_scale.toFixed(2));
231 $('#global_cfg_negative_prompt').val(extension_settings.cfg.global.negative_prompt);
232 $('#global_cfg_positive_prompt').val(extension_settings.cfg.global.positive_prompt);
233}
234
235function migrateSettings() {
236 let performSettingsSave = false;
237 let performMetaSave = false;
238
239 if (power_user.guidance_scale) {
240 extension_settings.cfg.global.guidance_scale = power_user.guidance_scale;
241 delete power_user.guidance_scale;
242 performSettingsSave = true;
243 }
244
245 if (power_user.negative_prompt) {
246 extension_settings.cfg.global.negative_prompt = power_user.negative_prompt;
247 delete power_user.negative_prompt;
248 performSettingsSave = true;
249 }
250
251 if (chat_metadata.cfg_negative_combine) {
252 chat_metadata[metadataKeys.prompt_combine] = chat_metadata.cfg_negative_combine;
253 chat_metadata.cfg_negative_combine = undefined;
254 performMetaSave = true;
255 }
256
257 if (chat_metadata.cfg_negative_insertion_depth) {
258 chat_metadata[metadataKeys.prompt_insertion_depth] = chat_metadata.cfg_negative_insertion_depth;
259 chat_metadata.cfg_negative_insertion_depth = undefined;
260 performMetaSave = true;
261 }
262
263 if (chat_metadata.cfg_negative_separator) {
264 chat_metadata[metadataKeys.prompt_separator] = chat_metadata.cfg_negative_separator;
265 chat_metadata.cfg_negative_separator = undefined;
266 performMetaSave = true;
267 }
268
269 if (performSettingsSave) {
270 saveSettingsDebounced();
271 }
272
273 if (performMetaSave) {
274 saveMetadataDebounced();
275 }
276}
277
278// This function is called when the extension is loaded
279export function initCfg() {
280 $('#CFGClose').on('click', function () {
281 $('#cfgConfig').transition({
282 opacity: 0,
283 duration: animation_duration,
284 easing: 'ease-in-out',
285 });
286 setTimeout(function () { $('#cfgConfig').hide(); }, animation_duration);
287 });
288
289 $('#chat_cfg_guidance_scale').on('input', function () {
290 const numberValue = Number($(this).val());
291 const success = setChatCfg(numberValue, settingType.guidance_scale);
292 if (success) {
293 $('#chat_cfg_guidance_scale_counter').val(numberValue.toFixed(2));
294 }
295 });
296
297 $('#chat_cfg_negative_prompt').on('input', function () {
298 setChatCfg($(this).val(), settingType.negative_prompt);
299 });
300
301 $('#chat_cfg_positive_prompt').on('input', function () {
302 setChatCfg($(this).val(), settingType.positive_prompt);
303 });
304
305 $('#chara_cfg_guidance_scale').on('input', function () {
306 const value = $(this).val();
307 const success = setCharCfg(value, settingType.guidance_scale);
308 if (success) {
309 $('#chara_cfg_guidance_scale_counter').val(Number(value).toFixed(2));
310 }
311 });
312
313 $('#chara_cfg_negative_prompt').on('input', function () {
314 setCharCfg($(this).val(), settingType.negative_prompt);
315 });
316
317 $('#chara_cfg_positive_prompt').on('input', function () {
318 setCharCfg($(this).val(), settingType.positive_prompt);
319 });
320
321 $('#global_cfg_guidance_scale').on('input', function () {
322 extension_settings.cfg.global.guidance_scale = Number($(this).val());
323 $('#global_cfg_guidance_scale_counter').val(extension_settings.cfg.global.guidance_scale.toFixed(2));
324 saveSettingsDebounced();
325 });
326
327 $('#global_cfg_negative_prompt').on('input', function () {
328 extension_settings.cfg.global.negative_prompt = $(this).val();
329 saveSettingsDebounced();
330 });
331
332 $('#global_cfg_positive_prompt').on('input', function () {
333 extension_settings.cfg.global.positive_prompt = $(this).val();
334 saveSettingsDebounced();
335 });
336
337 $('input[name="cfg_prompt_combine"]').on('input', function () {
338 const values = $('#cfgConfig').find('input[name="cfg_prompt_combine"]')
339 .filter(':checked')
340 .map(function () { return Number($(this).val()); })
341 .get()
342 .filter((e) => !Number.isNaN(e)) || [];
343
344 chat_metadata[metadataKeys.prompt_combine] = values;
345 saveMetadataDebounced();
346 });
347
348 $('#cfg_prompt_insertion_depth').on('input', function () {
349 chat_metadata[metadataKeys.prompt_insertion_depth] = Number($(this).val());
350 saveMetadataDebounced();
351 });
352
353 $('#cfg_prompt_separator').on('input', function () {
354 chat_metadata[metadataKeys.prompt_separator] = $(this).val();
355 saveMetadataDebounced();
356 });
357
358 $('#groupchat_cfg_use_chara').on('input', function () {
359 const checked = !!$(this).prop('checked');
360 chat_metadata[metadataKeys.groupchat_individual_chars] = checked;
361
362 if (checked) {
363 toastr.info('You can edit character CFG values in their respective character chats.');
364 }
365
366 saveMetadataDebounced();
367 });
368
369 initialLoadSettings();
370
371 if (extension_settings.cfg) {
372 migrateSettings();
373 }
374
375 $('#option_toggle_CFG').on('click', onCfgMenuItemClick);
376
377 // Hook events
378 eventSource.on(event_types.CHAT_CHANGED, async () => {
379 await onChatChanged();
380 });
381}
382
383export const cfgType = {
384 chat: 0,
385 chara: 1,
386 global: 2,
387};
388
389export const metadataKeys = {
390 guidance_scale: 'cfg_guidance_scale',
391 negative_prompt: 'cfg_negative_prompt',
392 positive_prompt: 'cfg_positive_prompt',
393 prompt_combine: 'cfg_prompt_combine',
394 groupchat_individual_chars: 'cfg_groupchat_individual_chars',
395 prompt_insertion_depth: 'cfg_prompt_insertion_depth',
396 prompt_separator: 'cfg_prompt_separator',
397};
398
399// Gets the CFG guidance scale
400// If the guidance scale is 1, ignore the CFG prompt(s) since it won't be used anyways
401export function getGuidanceScale() {
402 if (!extension_settings.cfg) {
403 console.warn('CFG extension is not enabled. Skipping CFG guidance.');
404 return;
405 }
406
407 const charaCfg = extension_settings.cfg.chara?.find((e) => e.name === getCharaFilename(this_chid));
408 const chatGuidanceScale = chat_metadata[metadataKeys.guidance_scale];
409 const groupchatCharOverride = chat_metadata[metadataKeys.groupchat_individual_chars] ?? false;
410
411 if (chatGuidanceScale && chatGuidanceScale !== 1 && !groupchatCharOverride) {
412 return {
413 type: cfgType.chat,
414 value: chatGuidanceScale,
415 };
416 }
417
418 if ((!selected_group && charaCfg || groupchatCharOverride) && charaCfg?.guidance_scale !== 1) {
419 return {
420 type: cfgType.chara,
421 value: charaCfg.guidance_scale,
422 };
423 }
424
425 if (extension_settings.cfg.global && extension_settings.cfg.global?.guidance_scale !== 1) {
426 return {
427 type: cfgType.global,
428 value: extension_settings.cfg.global.guidance_scale,
429 };
430 }
431}
432
433/**
434 * Gets the CFG prompt separator.
435 * @returns {string} The CFG prompt separator
436 */
437function getCustomSeparator() {
438 const defaultSeparator = '\n';
439
440 try {
441 if (chat_metadata[metadataKeys.prompt_separator]) {
442 return JSON.parse(chat_metadata[metadataKeys.prompt_separator]);
443 }
444
445 return defaultSeparator;
446 } catch {
447 console.warn('Invalid JSON detected for prompt separator. Using default separator.');
448 return defaultSeparator;
449 }
450}
451
452/**
453 * Gets the CFG prompt based on the guidance scale.
454 * @param {{type: number, value: number}} guidanceScale The CFG guidance scale
455 * @param {boolean} isNegative Whether to get the negative prompt
456 * @param {boolean} quiet Whether to suppress console output
457 * @returns {{value: string, depth: number}} The CFG prompt and insertion depth
458 */
459export function getCfgPrompt(guidanceScale, isNegative, quiet = false) {
460 let splitCfgPrompt = [];
461
462 const cfgPromptCombine = chat_metadata[metadataKeys.prompt_combine] ?? [];
463 if (guidanceScale.type === cfgType.chat || cfgPromptCombine.includes(cfgType.chat)) {
464 splitCfgPrompt.unshift(
465 substituteParams(
466 chat_metadata[isNegative ? metadataKeys.negative_prompt : metadataKeys.positive_prompt],
467 ),
468 );
469 }
470
471 const charaCfg = extension_settings.cfg.chara?.find((e) => e.name === getCharaFilename(this_chid));
472 if (guidanceScale.type === cfgType.chara || cfgPromptCombine.includes(cfgType.chara)) {
473 splitCfgPrompt.unshift(
474 substituteParams(
475 isNegative ? charaCfg.negative_prompt : charaCfg.positive_prompt,
476 ),
477 );
478 }
479
480 if (guidanceScale.type === cfgType.global || cfgPromptCombine.includes(cfgType.global)) {
481 splitCfgPrompt.unshift(
482 substituteParams(
483 isNegative ? extension_settings.cfg.global.negative_prompt : extension_settings.cfg.global.positive_prompt,
484 ),
485 );
486 }
487
488 const customSeparator = getCustomSeparator();
489 const combinedCfgPrompt = splitCfgPrompt.filter((e) => e.length > 0).join(customSeparator);
490 const insertionDepth = chat_metadata[metadataKeys.prompt_insertion_depth] ?? 1;
491 !quiet && console.log(`Setting CFG with guidance scale: ${guidanceScale.value}, negatives: ${combinedCfgPrompt}`);
492
493 return {
494 value: combinedCfgPrompt,
495 depth: insertionDepth,
496 };
497}