Merge branch 'staging' into webpack

f918b146e73692c7d06546fb4c870710cf8c02ef

Cohee <18619528+Cohee1207@users.noreply.github.com>

6 files changed, +171 -36Showing whitespace changes
public/script.js+3 -2
@@ -13,7 +13,7 @@ import {
1313 default as libs,
1414} from './lib.js';
1515
1616import { humanizedDateTime, favsToHotswap, getMessageTimeStamp, dragElement, isMobile, initRossMods, shouldSendOnEnter, addSafariPatch } from './scripts/RossAscends-mods.js';
1717import { userStatsHandler, statMesProcess, initStats } from './scripts/stats.js';
1818import {
1919 generateKoboldWithStreaming,
@@ -263,6 +263,7 @@ import { initSystemPrompts } from './scripts/sysprompt.js';
263263import { registerExtensionSlashCommands as initExtensionSlashCommands } from './scripts/extensions-slashcommands.js';
264264import { ToolManager } from './scripts/tool-calling.js';
265265import { addShowdownPatch } from './scripts/util/showdown-patch.js';
266+import { applyBrowserFixes } from './scripts/browser-fixes.js';
266267
267268//exporting functions and vars for mods
268269export {
@@ -932,7 +933,7 @@ async function firstLoadInit() {
932933 initLibraryShims();
933934 addShowdownPatch(showdown);
934935 reloadMarkdownProcessor();
935936 addSafariPatchapplyBrowserFixes();
936937 await getClientVersion();
937938 await readSecretState();
938939 await initLocales();
public/scripts/RossAscends-mods.js+13 -23
@@ -122,7 +122,7 @@ export function humanizeGenTime(total_gen_time) {
122122 */
123123var parsedUA = null;
124124
125125export function getParsedUA() {
126126 if (!parsedUA) {
127127 try {
128128 parsedUA = Bowser.parse(navigator.userAgent);
@@ -718,18 +718,6 @@ export const autoFitSendTextAreaDebounced = debounce(autoFitSendTextArea, deboun
718718
719719// ---------------------------------------------------
720720
721-export function addSafariPatch() {
722- const userAgent = getParsedUA();
723- console.debug('User Agent', userAgent);
724- const isMobileSafari = /iPad|iPhone|iPod/.test(navigator.platform) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
725- const isDesktopSafari = userAgent?.browser?.name === 'Safari' && userAgent?.platform?.type === 'desktop';
726- const isIOS = userAgent?.os?.name === 'iOS';
727-
728- if (isIOS || isMobileSafari || isDesktopSafari) {
729- document.body.classList.add('safari');
730- }
731-}
732-
733721export function initRossMods() {
734722 // initial status check
735723 checkStatusDebounced();
@@ -742,16 +730,6 @@ export function initRossMods() {
742730 RA_autoconnect();
743731 }
744732
745- if (isMobile()) {
746- const fixFunkyPositioning = () => {
747- console.debug('[Mobile] Device viewport change detected.');
748- document.documentElement.style.position = 'fixed';
749- requestAnimationFrame(() => document.documentElement.style.position = '');
750- };
751- window.addEventListener('resize', fixFunkyPositioning);
752- window.addEventListener('orientationchange', fixFunkyPositioning);
753- }
754-
755733 $('#main_api').change(function () {
756734 var PrevAPI = main_api;
757735 setTimeout(() => RA_autoconnect(PrevAPI), 100);
@@ -934,6 +912,12 @@ export function initRossMods() {
934912 if (!$(e.target).closest('#sheld').length) {
935913 return;
936914 }
915+ if ($('#curEditTextarea').length) {
916+ // Don't swipe while in text edit mode
917+ // the ios selection gestures get picked up
918+ // as swipe gestures
919+ return;
920+ }
937921 var SwipeButR = $('.swipe_right:last');
938922 var SwipeTargetMesClassParent = $(e.target).closest('.last_mes');
939923 if (SwipeTargetMesClassParent !== null) {
@@ -952,6 +936,12 @@ export function initRossMods() {
952936 if (!$(e.target).closest('#sheld').length) {
953937 return;
954938 }
939+ if ($('#curEditTextarea').length) {
940+ // Don't swipe while in text edit mode
941+ // the ios selection gestures get picked up
942+ // as swipe gestures
943+ return;
944+ }
955945 var SwipeButL = $('.swipe_left:last');
956946 var SwipeTargetMesClassParent = $(e.target).closest('.last_mes');
957947 if (SwipeTargetMesClassParent !== null) {
public/scripts/browser-fixes.js+77 -0
@@ -0,0 +1,77 @@
1+import { getParsedUA, isMobile } from './RossAscends-mods.js';
2+
3+const isFirefox = () => /firefox/i.test(navigator.userAgent);
4+
5+function sanitizeInlineQuotationOnCopy() {
6+ // STRG+C, STRG+V on firefox leads to duplicate double quotes when inline quotation elements are copied.
7+ // To work around this, take the selection and transform <q> to <span> before calling toString().
8+ document.addEventListener('copy', function (event) {
9+ const selection = window.getSelection();
10+ if (!selection.anchorNode?.parentElement.closest('.mes_text')) {
11+ return;
12+ }
13+
14+ const range = selection.getRangeAt(0).cloneContents();
15+ const tempDOM = document.createDocumentFragment();
16+
17+ function processNode(node) {
18+ if (node.nodeType === Node.ELEMENT_NODE && node.tagName.toLowerCase() === 'q') {
19+ // Transform <q> to <span>, preserve children
20+ const span = document.createElement('span');
21+
22+ [...node.childNodes].forEach(child => {
23+ const processedChild = processNode(child);
24+ span.appendChild(processedChild);
25+ });
26+
27+ return span;
28+ } else {
29+ // Nested structures containing <q> elements are unlikely
30+ return node.cloneNode(true);
31+ }
32+ }
33+
34+ [...range.childNodes].forEach(child => {
35+ const processedChild = processNode(child);
36+ tempDOM.appendChild(processedChild);
37+ });
38+
39+ const newRange = document.createRange();
40+ newRange.selectNodeContents(tempDOM);
41+
42+ event.preventDefault();
43+ event.clipboardData.setData('text/plain', newRange.toString());
44+ });
45+}
46+
47+function addSafariPatch() {
48+ const userAgent = getParsedUA();
49+ console.debug('User Agent', userAgent);
50+ const isMobileSafari = /iPad|iPhone|iPod/.test(navigator.platform) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
51+ const isDesktopSafari = userAgent?.browser?.name === 'Safari' && userAgent?.platform?.type === 'desktop';
52+ const isIOS = userAgent?.os?.name === 'iOS';
53+
54+ if (isIOS || isMobileSafari || isDesktopSafari) {
55+ document.body.classList.add('safari');
56+ }
57+}
58+
59+function applyBrowserFixes() {
60+ if (isFirefox()) {
61+ sanitizeInlineQuotationOnCopy();
62+ }
63+
64+ if (isMobile()) {
65+ const fixFunkyPositioning = () => {
66+ console.debug('[Mobile] Device viewport change detected.');
67+ document.documentElement.style.position = 'fixed';
68+ requestAnimationFrame(() => document.documentElement.style.position = '');
69+ };
70+ window.addEventListener('resize', fixFunkyPositioning);
71+ window.addEventListener('orientationchange', fixFunkyPositioning);
72+ }
73+
74+ addSafariPatch();
75+}
76+
77+export { isFirefox, applyBrowserFixes };
public/scripts/extensions/stable-diffusion/index.js+36 -11
@@ -19,7 +19,7 @@ import {
1919} from '../../../script.js';
2020import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules, renderExtensionTemplateAsync, writeExtensionField } from '../../extensions.js';
2121import { selected_group } from '../../group-chats.js';
2222import { stringFormat, initScrollHeight, resetScrollHeight, getCharaFilename, saveBase64AsFile, getBase64Async, delay, isTrueBoolean, debounce, isFalseBoolean, deepMerge } from '../../utils.js';
2323import { getMessageTimeStamp, humanizedDateTime } from '../../RossAscends-mods.js';
2424import { SECRET_KEYS, secret_state, writeSecret } from '../../secrets.js';
2525import { getNovelUnlimitedImageGeneration, getNovelAnlas, loadNovelSubscriptionData } from '../../nai-settings.js';
@@ -220,6 +220,7 @@ const defaultSettings = {
220220 // Automatic1111/Horde exclusives
221221 restore_faces: false,
222222 enable_hr: false,
223+ adetailer_face: false,
223224
224225 // Horde settings
225226 horde: false,
@@ -436,6 +437,7 @@ async function loadSettings() {
436437 $('#sd_horde_sanitize').prop('checked', extension_settings.sd.horde_sanitize);
437438 $('#sd_restore_faces').prop('checked', extension_settings.sd.restore_faces);
438439 $('#sd_enable_hr').prop('checked', extension_settings.sd.enable_hr);
440+ $('#sd_adetailer_face').prop('checked', extension_settings.sd.adetailer_face);
439441 $('#sd_refine_mode').prop('checked', extension_settings.sd.refine_mode);
440442 $('#sd_multimodal_captioning').prop('checked', extension_settings.sd.multimodal_captioning);
441443 $('#sd_auto_url').val(extension_settings.sd.auto_url);
@@ -854,6 +856,11 @@ function onSamplerChange() {
854856 saveSettingsDebounced();
855857}
856858
859+function onADetailerFaceChange() {
860+ extension_settings.sd.adetailer_face = !!$('#sd_adetailer_face').prop('checked');
861+ saveSettingsDebounced();
862+}
863+
857864const resolutionOptions = {
858865 sd_res_512x512: { width: 512, height: 512, name: '512x512 (1:1, icons, profile pictures)' },
859866 sd_res_600x600: { width: 600, height: 600, name: '600x600 (1:1, icons, profile pictures)' },
@@ -2921,11 +2928,7 @@ async function generateHordeImage(prompt, negativePrompt, signal) {
29212928 */
29222929async function generateAutoImage(prompt, negativePrompt, signal) {
29232930 const isValidVae = extension_settings.sd.vae && !['N/A', placeholderVae].includes(extension_settings.sd.vae);
2924- const result = await fetch('/api/sd/generate', {
2931+ let payload = {
2925- method: 'POST',
2926- headers: getRequestHeaders(),
2927- signal: signal,
2928- body: JSON.stringify({
29292932 ...getSdRequestBody(),
29302933 prompt: prompt,
29312934 negative_prompt: negativePrompt,
@@ -2942,20 +2945,41 @@ async function generateAutoImage(prompt, negativePrompt, signal) {
29422945 denoising_strength: extension_settings.sd.denoising_strength,
29432946 hr_second_pass_steps: extension_settings.sd.hr_second_pass_steps,
29442947 seed: extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : undefined,
2945- // For AUTO1111
29462948 override_settings: {
29472949 CLIP_stop_at_last_layers: extension_settings.sd.clip_skip,
29482950 sd_vae: isValidVae ? extension_settings.sd.vae : undefined,
29492951 },
29502952 override_settings_restore_afterwards: true,
29512953 clip_skip: extension_settings.sd.clip_skip, // For SD.Next
2952- clip_skip: extension_settings.sd.clip_skip,
2953- // Ensure generated img is saved to disk
29542954 save_images: true,
29552955 send_images: true,
29562956 do_not_save_grid: false,
29572957 do_not_save_samples: false,
2958- }),
2958+ };
2959+
2960+ // Conditionally add the ADetailer if adetailer_face is enabled
2961+ if (extension_settings.sd.adetailer_face) {
2962+ payload = deepMerge(payload, {
2963+ alwayson_scripts: {
2964+ ADetailer: {
2965+ args: [
2966+ true, // ad_enable
2967+ true, // skip_img2img
2968+ {
2969+ 'ad_model': 'face_yolov8n.pt',
2970+ },
2971+ ],
2972+ },
2973+ },
2974+ });
2975+ }
2976+
2977+ // Make the fetch call with the payload
2978+ const result = await fetch('/api/sd/generate', {
2979+ method: 'POST',
2980+ headers: getRequestHeaders(),
2981+ signal: signal,
2982+ body: JSON.stringify(payload),
29592983 });
29602984
29612985 if (result.ok) {
@@ -4187,6 +4211,7 @@ jQuery(async () => {
41874211 $('#sd_horde_sanitize').on('input', onHordeSanitizeInput);
41884212 $('#sd_restore_faces').on('input', onRestoreFacesInput);
41894213 $('#sd_enable_hr').on('input', onHighResFixInput);
4214+ $('#sd_adetailer_face').on('change', onADetailerFaceChange);
41904215 $('#sd_refine_mode').on('input', onRefineModeInput);
41914216 $('#sd_character_prompt').on('input', onCharacterPromptInput);
41924217 $('#sd_character_negative_prompt').on('input', onCharacterNegativePromptInput);
public/scripts/extensions/stable-diffusion/settings.html+10 -0
@@ -356,6 +356,16 @@
356356 </label>
357357 </div>
358358
359+ <div class="flex-container marginTopBot5" data-sd-source="auto,vlad">
360+ <label for="sd_adetailer_face" class="flex1 checkbox_label" data-i18n="[title]sd_adetailer_face" title="Use ADetailer with face model during the generation. The ADetailer extension must be installed on the backend.">
361+ <input id="sd_adetailer_face" type="checkbox" />
362+ <small data-i18n="Use ADetailer (Face)">Use ADetailer (Face)</small>
363+ </label>
364+ <div class="flex1">
365+ <!-- I will be useful later! -->
366+ </div>
367+ </div>
368+
359369 <div class="flex-container marginTopBot5" data-sd-source="novel">
360370 <label class="flex1 checkbox_label" data-i18n="[title]SMEA versions of samplers are modified to perform better at high resolution." title="SMEA versions of samplers are modified to perform better at high resolution.">
361371 <input id="sd_novel_sm" type="checkbox" />
public/scripts/utils.js+32 -0
@@ -30,6 +30,38 @@ export const navigation_option = {
3030 previous: -1000,
3131};
3232
33+/**
34+ * Determines if a value is an object.
35+ * @param {any} item The item to check.
36+ * @returns {boolean} True if the item is an object, false otherwise.
37+ */
38+function isObject(item) {
39+ return (item && typeof item === 'object' && !Array.isArray(item));
40+}
41+
42+/**
43+ * Merges properties of two objects. If the property is an object, it will be merged recursively.
44+ * @param {object} target The target object
45+ * @param {object} source The source object
46+ * @returns {object} Merged object
47+ */
48+export function deepMerge(target, source) {
49+ let output = Object.assign({}, target);
50+ if (isObject(target) && isObject(source)) {
51+ Object.keys(source).forEach(key => {
52+ if (isObject(source[key])) {
53+ if (!(key in target))
54+ Object.assign(output, { [key]: source[key] });
55+ else
56+ output[key] = deepMerge(target[key], source[key]);
57+ } else {
58+ Object.assign(output, { [key]: source[key] });
59+ }
60+ });
61+ }
62+ return output;
63+}
64+
3365export function escapeHtml(str) {
3466 return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
3567}