diff --git a/CHANGELOG.md b/CHANGELOG.md
index adb52e92b..16da92008 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,10 +5,14 @@
One week later, another large update!
- system:
- - new default theme: **black-teal**
- full **python 3.11** support
note that changing python version does require reinstall
and if you're already on python 3.10, really no need to upgrade
+- themes:
+ - new default theme: **black-teal**
+ - new light theme: **light-teal**
+ - new additional theme: **midnight-barbie**
+ thanks @nyxia
- extra networks:
- support for **tags**
show tags on hover, search by tag, list tags, add to prompt, etc.
@@ -44,8 +48,6 @@ One week later, another large update!
- added **force zeros** setting
create zero-tensor for prompt if prompt is empty (positive or negative)
- general:
- - new additional theme: **midnight-barbie**
- thanks @nyxia
- `rembg` remove backgrounds support for **is-net** model
- **settings** now show markers for all items set to non-default values
- **metadata** refactored how/what/when metadata is added to images
diff --git a/extensions-builtin/sd-webui-controlnet b/extensions-builtin/sd-webui-controlnet
index fd37e9fc7..f77c5eb5f 160000
--- a/extensions-builtin/sd-webui-controlnet
+++ b/extensions-builtin/sd-webui-controlnet
@@ -1 +1 @@
-Subproject commit fd37e9fc7ced2c3a39aaa3860916672c8d0fbfe8
+Subproject commit f77c5eb5fa6dea0136d36a6fc9f9f00a99d8f2cb
diff --git a/html/light-teal.jpg b/html/light-teal.jpg
new file mode 100644
index 000000000..2b6292c6e
Binary files /dev/null and b/html/light-teal.jpg differ
diff --git a/html/midnight-barbie.jpg b/html/midnight-barbie.jpg
new file mode 100644
index 000000000..c4182e8f1
Binary files /dev/null and b/html/midnight-barbie.jpg differ
diff --git a/javascript/black-orange.css b/javascript/black-orange.css
index 3a0595b2d..483e5c525 100644
--- a/javascript/black-orange.css
+++ b/javascript/black-orange.css
@@ -158,7 +158,7 @@ svg.feather.feather-image, .feather .feather-image { display: none }
--checkbox-border-color: var(--neutral-700);
--checkbox-border-color-focus: var(--secondary-500);
--checkbox-border-color-hover: var(--neutral-600);
- --checkbox-border-color-selected: var(--secondary-600);
+ --checkbox-border-color-selected: var(--primary-600);
--checkbox-border-width: var(--input-border-width);
--checkbox-label-background-fill: None;
--checkbox-label-background-fill-hover: None;
diff --git a/javascript/black-teal.css b/javascript/black-teal.css
index 54be70897..fe9309265 100644
--- a/javascript/black-teal.css
+++ b/javascript/black-teal.css
@@ -17,10 +17,19 @@
--primary-950: #193232;
--highlight-color: var(--primary-200);
--inactive-color: var(--primary--800);
+ --body-text-color: var(--neutral-100);
+ --body-text-color-subdued: var(--neutral-300);
--background-color: #000000;
+ --background-fill-primary: var(--neutral-700);
--input-padding: 4px;
- --radius-lg: 4px;
+ --input-background-fill: var(--neutral-800);
+ --input-shadow: 2px 2px 2px 2px var(--background-color);
+ --button-secondary-text-color: white;
+ --button-secondary-background-fill: linear-gradient(to bottom right, var(--neutral-400), var(--neutral-700));
+ --button-secondary-background-fill-hover: linear-gradient(to bottom right, var(--neutral-700), var(--neutral-400));
+ --block-title-text-color: var(--neutral-900);
--radius-sm: 2px;
+ --radius-lg: 4px;
--spacing-md: 4px;
--spacing-xxl: 8px;
--line-sm: 1.2em;
@@ -31,7 +40,6 @@ html { font-size: var(--font-size); }
body, button, input, select, textarea { font-family: var(--font);}
button { font-size: 1.2rem; max-width: 400px; }
img { background-color: var(--background-color); }
-input[type=checkbox] { background-color: transparent !important; }
input[type=range] { height: var(--line-sm); appearance: none; margin-top: 0; min-width: 160px; background-color: var(--background-color); width: 100%; background: transparent; }
input[type=range]::-webkit-slider-runnable-track { width: 100%; height: var(--line-sm); cursor: pointer; box-shadow: 2px 2px 3px #111111; background: var(--input-background-fill); border-radius: var(--radius-lg); border: 0px solid #222222; }
input[type=range]::-moz-range-track { width: 100%; height: var(--line-sm); cursor: pointer; box-shadow: 2px 2px 3px #111111; background: var(--input-background-fill); border-radius: var(--radius-lg); border: 0px solid #222222; }
@@ -68,7 +76,7 @@ svg.feather.feather-image, .feather .feather-image { display: none }
.px-4 { padding-lefT: 1rem; padding-right: 1rem; }
.py-6 { padding-bottom: 0; }
.tabs { background-color: var(--background-color); }
-.block.token-counter span { background-color: #222 !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.8rem; }
+.block.token-counter span { background-color: var(--input-background-fill) !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.8rem; }
.tab-nav { zoom: 120%; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; }
.label-wrap { margin: 16px 0px 8px 0px; }
.gradio-slider input[type="number"] { width: 4.5em; font-size: 0.8rem; height: 20px; }
@@ -122,9 +130,7 @@ svg.feather.feather-image, .feather .feather-image { display: none }
/* based on gradio built-in dark theme */
:root, .light, .dark {
--body-background-fill: var(--background-color);
- --body-text-color: var(--neutral-100);
--color-accent-soft: var(--neutral-700);
- --background-fill-primary: #222222;
--background-fill-secondary: none;
--border-color-accent: var(--background-color);
--border-color-primary: var(--background-color);
@@ -132,7 +138,6 @@ svg.feather.feather-image, .feather .feather-image { display: none }
--link-text-color: var(--secondary-500);
--link-text-color-hover: var(--secondary-400);
--link-text-color-visited: var(--secondary-600);
- --body-text-color-subdued: var(--neutral-400);
--shadow-spread: 1px;
--block-background-fill: None;
--block-border-color: var(--border-color-primary);
@@ -146,18 +151,17 @@ svg.feather.feather-image, .feather .feather-image { display: none }
--block_title_background_fill: None;
--block_title_border_color: None;
--block_title_border_width: None;
- --block-title-text-color: white;
--panel-background-fill: var(--background-fill-secondary);
--panel-border-color: var(--border-color-primary);
--panel_border_width: None;
- --checkbox-background-color: var(--neutral-800);
+ --checkbox-background-color: var(--neutral-500);
--checkbox-background-color-focus: var(--checkbox-background-color);
--checkbox-background-color-hover: var(--checkbox-background-color);
- --checkbox-background-color-selected: var(--secondary-600);
- --checkbox-border-color: var(--neutral-700);
+ --checkbox-background-color-selected: var(--primary-500);
+ --checkbox-border-color: transparent;
--checkbox-border-color-focus: var(--secondary-500);
--checkbox-border-color-hover: var(--neutral-600);
- --checkbox-border-color-selected: var(--secondary-600);
+ --checkbox-border-color-selected: var(--primary-600);
--checkbox-border-width: var(--input-border-width);
--checkbox-label-background-fill: None;
--checkbox-label-background-fill-hover: None;
@@ -171,7 +175,6 @@ svg.feather.feather-image, .feather .feather-image { display: none }
--error-border-color: var(--border-color-primary);
--error_border_width: None;
--error-text-color: #ef4444;
- --input-background-fill: var(--neutral-800);
--input-background-fill-focus: var(--secondary-600);
--input-background-fill-hover: var(--input-background-fill);
--input-border-color: var(--border-color-primary);
@@ -179,7 +182,6 @@ svg.feather.feather-image, .feather .feather-image { display: none }
--input-border-color-hover: var(--input-border-color);
--input_border_width: None;
--input-placeholder-color: var(--neutral-500);
- --input-shadow: 2px 2px 2px 2px #111111;
--input-shadow-focus: 2px 2px 2px 2px #111111;
--loader_color: None;
--slider_color: None;
@@ -201,11 +203,8 @@ svg.feather.feather-image, .feather .feather-image { display: none }
--button-primary-border-color-hover: var(--button-primary-border-color);
--button-primary-text-color: white;
--button-primary-text-color-hover: var(--button-primary-text-color);
- --button-secondary-background-fill: linear-gradient(to bottom right, var(--neutral-600), var(--neutral-800));
- --button-secondary-background-fill-hover: linear-gradient(to bottom right, var(--neutral-600), var(--neutral-400));
--button-secondary-border-color: var(--neutral-600);
--button-secondary-border-color-hover: var(--button-secondary-border-color);
- --button-secondary-text-color: white;
--button-secondary-text-color-hover: var(--button-secondary-text-color);
--secondary-50: #eff6ff;
--secondary-100: #dbeafe;
diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js
index e6b905e9f..f0e999ac5 100644
--- a/javascript/extraNetworks.js
+++ b/javascript/extraNetworks.js
@@ -187,7 +187,7 @@ function setupExtraNetworksForTab(tabname) {
gradioApp().getElementById(`${tabname}_settings`).parentNode.style.width = 'unset';
} else if (window.opts.extra_networks_card_cover === 'sidebar') {
en.style.transition = 'width 0.2s ease';
- en.style.zIndex = 0;
+ en.style.zIndex = 9999;
en.style.position = 'absolute';
en.style.right = '0';
en.style.width = `${window.opts.extra_networks_sidebar_width}vw`;
diff --git a/javascript/light-teal.css b/javascript/light-teal.css
new file mode 100644
index 000000000..eb9cd6aae
--- /dev/null
+++ b/javascript/light-teal.css
@@ -0,0 +1,315 @@
+/* generic html tags */
+:root, .light, .dark {
+ --font: 'system-ui', 'ui-sans-serif', 'system-ui', "Roboto", sans-serif;
+ --font-mono: 'ui-monospace', 'Consolas', monospace;
+ --font-size: 16px;
+ --left-column: 490px;
+ --primary-50: #7dffff;
+ --primary-100: #72e8e8;
+ --primary-200: #67d2d2;
+ --primary-300: #5dbcbc;
+ --primary-400: #52a7a7;
+ --primary-500: #489292;
+ --primary-600: #3e7d7d;
+ --primary-700: #356969;
+ --primary-800: #2b5656;
+ --primary-900: #224444;
+ --primary-950: #193232;
+ --highlight-color: var(--primary-200);
+ --inactive-color: var(--primary--800);
+ --body-text-color: var(--neutral-800);
+ --body-text-color-subdued: var(--neutral-600);
+ --background-color: #FFFFFF;
+ --background-fill-primary: var(--neutral-400);
+ --input-padding: 4px;
+ --input-background-fill: var(--neutral-300);
+ --input-shadow: 2px 2px 2px 2px var(--neutral-500);
+ --button-secondary-text-color: black;
+ --button-secondary-background-fill: linear-gradient(to bottom right, var(--neutral-200), var(--neutral-500));
+ --button-secondary-background-fill-hover: linear-gradient(to bottom right, var(--neutral-500), var(--neutral-200));
+ --block-title-text-color: var(--neutral-900);
+ --radius-sm: 2px;
+ --radius-lg: 4px;
+ --spacing-md: 4px;
+ --spacing-xxl: 8px;
+ --line-sm: 1.2em;
+ --line-md: 1.4em;
+}
+
+html { font-size: var(--font-size); }
+body, button, input, select, textarea { font-family: var(--font);}
+button { font-size: 1.2rem; max-width: 400px; }
+img { background-color: var(--background-color); }
+input[type=range] { height: var(--line-sm); appearance: none; margin-top: 0; min-width: 160px; background-color: var(--background-color); width: 100%; background: transparent; }
+input[type=range]::-webkit-slider-runnable-track { width: 100%; height: var(--line-sm); cursor: pointer; box-shadow: 2px 2px 3px #111111; background: var(--input-background-fill); border-radius: var(--radius-lg); border: 0px solid #222222; }
+input[type=range]::-moz-range-track { width: 100%; height: var(--line-sm); cursor: pointer; box-shadow: 2px 2px 3px #111111; background: var(--input-background-fill); border-radius: var(--radius-lg); border: 0px solid #222222; }
+input[type=range]::-webkit-slider-thumb { box-shadow: 2px 2px 3px #111111; border: 0px solid #000000; height: var(--line-sm); width: var(--line-sm); border-radius: var(--radius-lg); background: var(--highlight-color); cursor: pointer; appearance: none; margin-top: 0px; }
+input[type=range]::-moz-range-thumb { box-shadow: 2px 2px 3px #111111; border: 0px solid #000000; height: var(--line-sm); width: var(--line-sm); border-radius: var(--radius-lg); background: var(--highlight-color); cursor: pointer; appearance: none; margin-top: 0px; }
+::-webkit-scrollbar { width: 12px; }
+::-webkit-scrollbar-track { background: #333333; }
+::-webkit-scrollbar-thumb { background-color: var(--highlight-color); border-radius: var(--radius-lg); border-width: 0; box-shadow: 2px 2px 3px #111111; }
+div.form { border-width: 0; box-shadow: none; background: transparent; overflow: visible; margin-bottom: 6px; }
+div.compact { gap: 1em; }
+
+/* gradio style classes */
+fieldset .gr-block.gr-box, label.block span { padding: 0; margin-top: -4px; }
+.border-2 { border-width: 0; }
+.border-b-2 { border-bottom-width: 2px; border-color: var(--highlight-color) !important; padding-bottom: 2px; margin-bottom: 8px; }
+.bg-white { color: lightyellow; background-color: var(--inactive-color); }
+.gr-box { border-radius: var(--radius-sm) !important; background-color: #111111 !important; box-shadow: 2px 2px 3px #111111; border-width: 0; padding: 4px; margin: 12px 0px 12px 0px }
+.gr-button { font-weight: normal; box-shadow: 2px 2px 3px #111111; font-size: 0.8rem; min-width: 32px; min-height: 32px; padding: 3px; margin: 3px; }
+.gr-check-radio { background-color: var(--inactive-color); border-width: 0; border-radius: var(--radius-lg); box-shadow: 2px 2px 3px #111111; }
+.gr-check-radio:checked { background-color: var(--highlight-color); }
+.gr-compact { background-color: var(--background-color); }
+.gr-form { border-width: 0; }
+.gr-input { background-color: #333333 !important; padding: 4px; margin: 4px; }
+.gr-input-label { color: lightyellow; border-width: 0; background: transparent; padding: 2px !important; }
+.gr-panel { background-color: var(--background-color); }
+.eta-bar { display: none !important }
+svg.feather.feather-image, .feather .feather-image { display: none }
+.gap-2 { padding-top: 8px; }
+.gr-box > div > div > input.gr-text-input { right: 0; width: 4em; padding: 0; top: -12px; border: none; max-height: 20px; }
+.output-html { line-height: 1.2rem; overflow-x: hidden; }
+.output-html > div { margin-bottom: 8px; }
+.overflow-hidden .flex .flex-col .relative col .gap-4 { min-width: var(--left-column); max-width: var(--left-column); } /* this is a problematic one */
+.p-2 { padding: 0; }
+.px-4 { padding-lefT: 1rem; padding-right: 1rem; }
+.py-6 { padding-bottom: 0; }
+.tabs { background-color: var(--background-color); }
+.block.token-counter span { background-color: var(--input-background-fill) !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.8rem; }
+.tab-nav { zoom: 120%; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; }
+.label-wrap { margin: 16px 0px 8px 0px; }
+.gradio-slider input[type="number"] { width: 4.5em; font-size: 0.8rem; height: 20px; }
+.gradio-button.tool { border: none; background: none; box-shadow: none; filter: hue-rotate(340deg) saturate(0.5); }
+#tab_extensions table td, #tab_extensions table th { border: none; padding: 0.5em; }
+#tab_extensions table { width: 96vw }
+#tab_extensions table thead { background-color: var(--neutral-700); }
+
+/* automatic style classes */
+.progressDiv { border-radius: var(--radius-sm) !important; position: fixed; top: 44px; right: 26px; max-width: 262px; height: 48px; z-index: 99; box-shadow: var(--button-shadow); }
+.progressDiv .progress { border-radius: var(--radius-lg) !important; background: var(--highlight-color); line-height: 3rem; height: 48px; }
+.gallery-item { box-shadow: none !important; }
+.performance { color: #888; }
+.extra-networks { border-left: 2px solid var(--highlight-color) !important; padding-left: 4px; }
+.image-buttons { gap: 10px !important; justify-content: center; }
+.image-buttons > button { max-width: 160px; }
+#system_row > button, #settings_row > button, #config_row > button { max-width: 190px; }
+
+/* gradio elements overrides */
+#div.gradio-container { overflow-x: hidden; }
+#img2img_label_copy_to_img2img { font-weight: normal; }
+#txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt { background-color: var(--background-color); box-shadow: 4px 4px 4px 0px #333333 !important; }
+#txt2img_prompt > label > textarea, #txt2img_neg_prompt > label > textarea, #img2img_prompt > label > textarea, #img2img_neg_prompt > label > textarea { font-size: 1.1rem; }
+#img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: #111111; padding-top: 16px; }
+#interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; }
+#quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-left: -20px; margin-top: -2px; height: 2.4em; }
+#quicksettings > div, #quicksettings > fieldset { line-height: 1.4em; margin-top: 0.4em; }
+#open_folder_extras, #footer, #style_pos_col, #style_neg_col, #roll_col, #extras_upscaler_2, #extras_upscaler_2_visibility, #txt2img_seed_resize_from_w, #txt2img_seed_resize_from_h { display: none; }
+#save-animation { border-radius: var(--radius-sm) !important; margin-bottom: 16px; background-color: #111111; }
+#script_list { padding: 4px; margin-top: 20px; margin-bottom: 20px; }
+#settings > div.flex-wrap { width: 15em; }
+#tab_extensions table { background-color: #222222; }
+#txt2img_cfg_scale { min-width: 200px; }
+#txt2img_checkboxes, #img2img_checkboxes { background-color: transparent; }
+#txt2img_checkboxes, #img2img_checkboxes { margin-bottom: 0.2em; }
+#txt2img_actions_column, #img2img_actions_column { flex-flow: wrap; justify-content: space-between; }
+#txt2img_enqueue_wrapper, #img2img_enqueue_wrapper { min-width: unset; width: 48%; }
+#txt2img_generate_box, #img2img_generate_box { min-width: unset; width: 48%; }
+
+#extras_upscale { margin-top: 10px }
+#txt2img_progress_row > div { min-width: var(--left-column); max-width: var(--left-column); }
+#txt2img_results, #img2img_results, #extras_results { background-color: var(--background-color); padding: 0; }
+#txt2img_settings { min-width: var(--left-column); max-width: var(--left-column); background-color: #111111; padding-top: 16px; }
+#pnginfo_html2_info { margin-top: -18px; background-color: var(--input-background-fill); padding: var(--input-padding) }
+#txt2img_tools, #img2img_tools { margin-top: -4px; margin-bottom: -4px; }
+#txt2img_styles_row, #img2img_styles_row { margin-top: -6px; }
+
+/* custom elements overrides */
+#steps-animation, #controlnet { border-width: 0; }
+
+/* based on gradio built-in dark theme */
+:root, .light, .dark {
+ --body-background-fill: var(--background-color);
+ --color-accent-soft: var(--neutral-700);
+ --background-fill-secondary: none;
+ --border-color-accent: var(--background-color);
+ --border-color-primary: var(--background-color);
+ --link-text-color-active: var(--secondary-500);
+ --link-text-color: var(--secondary-500);
+ --link-text-color-hover: var(--secondary-400);
+ --link-text-color-visited: var(--secondary-600);
+ --shadow-spread: 1px;
+ --block-background-fill: None;
+ --block-border-color: var(--border-color-primary);
+ --block_border_width: None;
+ --block-info-text-color: var(--body-text-color-subdued);
+ --block-label-background-fill: var(--background-fill-secondary);
+ --block-label-border-color: var(--border-color-primary);
+ --block_label_border_width: None;
+ --block-label-text-color: var(--neutral-200);
+ --block_shadow: None;
+ --block_title_background_fill: None;
+ --block_title_border_color: None;
+ --block_title_border_width: None;
+ --panel-background-fill: var(--background-fill-secondary);
+ --panel-border-color: var(--border-color-primary);
+ --panel_border_width: None;
+ --checkbox-background-color: var(--neutral-500);
+ --checkbox-background-color-focus: var(--checkbox-background-color);
+ --checkbox-background-color-hover: var(--checkbox-background-color);
+ --checkbox-background-color-selected: var(--primary-500);
+ --checkbox-border-color: transparent;
+ --checkbox-border-color-focus: var(--secondary-500);
+ --checkbox-border-color-hover: var(--neutral-600);
+ --checkbox-border-color-selected: var(--primary-600);
+ --checkbox-border-width: var(--input-border-width);
+ --checkbox-label-background-fill: None;
+ --checkbox-label-background-fill-hover: None;
+ --checkbox-label-background-fill-selected: var(--checkbox-label-background-fill);
+ --checkbox-label-border-color: var(--border-color-primary);
+ --checkbox-label-border-color-hover: var(--checkbox-label-border-color);
+ --checkbox-label-border-width: var(--input-border-width);
+ --checkbox-label-text-color: var(--body-text-color);
+ --checkbox-label-text-color-selected: var(--checkbox-label-text-color);
+ --error-background-fill: var(--background-fill-primary);
+ --error-border-color: var(--border-color-primary);
+ --error_border_width: None;
+ --error-text-color: #ef4444;
+ --input-background-fill-focus: var(--secondary-600);
+ --input-background-fill-hover: var(--input-background-fill);
+ --input-border-color: var(--border-color-primary);
+ --input-border-color-focus: var(--neutral-700);
+ --input-border-color-hover: var(--input-border-color);
+ --input_border_width: None;
+ --input-placeholder-color: var(--neutral-500);
+ --input-shadow-focus: 2px 2px 2px 2px #111111;
+ --loader_color: None;
+ --slider_color: None;
+ --stat-background-fill: linear-gradient(to right, var(--primary-400), var(--primary-600));
+ --table-border-color: var(--neutral-700);
+ --table-even-background-fill: #222222;
+ --table-odd-background-fill: #333333;
+ --table-row-focus: var(--color-accent-soft);
+ --button-border-width: var(--input-border-width);
+ --button-cancel-background-fill: linear-gradient(to bottom right, #dc2626, #b91c1c);
+ --button-cancel-background-fill-hover: linear-gradient(to bottom right, #dc2626, #dc2626);
+ --button-cancel-border-color: #dc2626;
+ --button-cancel-border-color-hover: var(--button-cancel-border-color);
+ --button-cancel-text-color: white;
+ --button-cancel-text-color-hover: var(--button-cancel-text-color);
+ --button-primary-background-fill: linear-gradient(to bottom right, var(--primary-500), var(--primary-800));
+ --button-primary-background-fill-hover: linear-gradient(to bottom right, var(--primary-500), var(--primary-300));
+ --button-primary-border-color: var(--primary-500);
+ --button-primary-border-color-hover: var(--button-primary-border-color);
+ --button-primary-text-color: white;
+ --button-primary-text-color-hover: var(--button-primary-text-color);
+ --button-secondary-border-color: var(--neutral-600);
+ --button-secondary-border-color-hover: var(--button-secondary-border-color);
+ --button-secondary-text-color-hover: var(--button-secondary-text-color);
+ --secondary-50: #eff6ff;
+ --secondary-100: #dbeafe;
+ --secondary-200: #bfdbfe;
+ --secondary-300: #93c5fd;
+ --secondary-400: #60a5fa;
+ --secondary-500: #3b82f6;
+ --secondary-600: #2563eb;
+ --secondary-700: #1d4ed8;
+ --secondary-800: #1e40af;
+ --secondary-900: #1e3a8a;
+ --secondary-950: #1d3660;
+ --neutral-50: #f0f0f0;
+ --neutral-100: #e0e0e0;
+ --neutral-200: #d0d0d0;
+ --neutral-300: #b0b0b0;
+ --neutral-400: #909090;
+ --neutral-500: #707070;
+ --neutral-600: #606060;
+ --neutral-700: #404040;
+ --neutral-800: #333333;
+ --neutral-900: #111827;
+ --neutral-950: #0b0f19;
+ --spacing-xxs: 1px;
+ --spacing-xs: 2px;
+ --spacing-sm: 4px;
+ --spacing-lg: 8px;
+ --spacing-xl: 10px;
+ --radius-xxs: 0;
+ --radius-xs: 0;
+ --radius-md: 0;
+ --radius-xl: 0;
+ --radius-xxl: 0;
+ --text-xxs: 9px;
+ --text-xs: 10px;
+ --text-sm: 12px;
+ --text-md: 14px;
+ --text-lg: 16px;
+ --text-xl: 22px;
+ --text-xxl: 26px;
+ --body-text-size: var(--text-md);
+ --body-text-weight: 400;
+ --embed-radius: var(--radius-lg);
+ --color-accent: var(--primary-500);
+ --shadow-drop: 0;
+ --shadow-drop-lg: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
+ --shadow-inset: rgba(0,0,0,0.05) 0px 2px 4px 0px inset;
+ --block-border-width: 1px;
+ --block-info-text-size: var(--text-sm);
+ --block-info-text-weight: 400;
+ --block-label-border-width: 1px;
+ --block-label-margin: 0;
+ --block-label-padding: var(--spacing-sm) var(--spacing-lg);
+ --block-label-radius: calc(var(--radius-lg) - 1px) 0 calc(var(--radius-lg) - 1px) 0;
+ --block-label-right-radius: 0 calc(var(--radius-lg) - 1px) 0 calc(var(--radius-lg) - 1px);
+ --block-label-text-size: var(--text-sm);
+ --block-label-text-weight: 400;
+ --block-padding: var(--spacing-xl) calc(var(--spacing-xl) + 2px);
+ --block-radius: var(--radius-lg);
+ --block-shadow: var(--shadow-drop);
+ --block-title-background-fill: none;
+ --block-title-border-color: none;
+ --block-title-border-width: 0px;
+ --block-title-padding: 0;
+ --block-title-radius: none;
+ --block-title-text-size: var(--text-md);
+ --block-title-text-weight: 400;
+ --container-radius: var(--radius-lg);
+ --form-gap-width: 1px;
+ --layout-gap: var(--spacing-xxl);
+ --panel-border-width: 0;
+ --section-header-text-size: var(--text-md);
+ --section-header-text-weight: 400;
+ --checkbox-border-radius: var(--radius-sm);
+ --checkbox-label-gap: 2px;
+ --checkbox-label-padding: var(--spacing-md);
+ --checkbox-label-shadow: var(--shadow-drop);
+ --checkbox-label-text-size: var(--text-md);
+ --checkbox-label-text-weight: 400;
+ --checkbox-check: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
+ --radio-circle: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e");
+ --checkbox-shadow: var(--input-shadow);
+ --error-border-width: 1px;
+ --input-border-width: 0;
+ --input-radius: var(--radius-lg);
+ --input-text-size: var(--text-md);
+ --input-text-weight: 400;
+ --loader-color: var(--color-accent);
+ --prose-text-size: var(--text-md);
+ --prose-text-weight: 400;
+ --prose-header-text-weight: 600;
+ --slider-color: ;
+ --table-radius: var(--radius-lg);
+ --button-large-padding: 2px 10px;
+ --button-large-radius: var(--radius-lg);
+ --button-large-text-size: var(--text-lg);
+ --button-large-text-weight: 400;
+ --button-shadow: 4px 4px 4px 0px #333333;
+ --button-shadow-active: 1px 1px 4px 0px #555555;
+ --button-shadow-hover: 1px 1px 4px 0px #555555;
+ --button-small-padding: var(--spacing-sm) calc(2 * var(--spacing-sm));
+ --button-small-radius: var(--radius-lg);
+ --button-small-text-size: var(--text-md);
+ --button-small-text-weight: 400;
+ --button-transition: none;
+ --size-9: 64px;
+ --size-14: 64px;
+}
diff --git a/javascript/midnight-barbie.css b/javascript/midnight-barbie.css
old mode 100755
new mode 100644
diff --git a/javascript/style.css b/javascript/style.css
index 866a1bb5a..b390cbafa 100644
--- a/javascript/style.css
+++ b/javascript/style.css
@@ -57,7 +57,7 @@ button.custom-button{
.theme-preview { display: none; position: fixed; border: 4px solid var(--neutral-600); box-shadow: 2px 2px 2px 2px var(--neutral-700); top: 0; bottom: 0; left: 0; right: 0; margin: auto; max-width: 75vw; z-index: 999; }
/* txt2img/img2img specific */
-.block.token-counter{ position: absolute; display: inline-block; right: 1em; min-width: 0 !important; width: auto; z-index: 100; top: -0.75em; }
+.block.token-counter{ position: absolute; display: inline-block; right: 0; min-width: 0 !important; width: auto; z-index: 100; top: -0.75em; }
.block.token-counter span{ background: var(--input-background-fill) !important; box-shadow: 0 0 0.0 0.3em rgba(192,192,192,0.15), inset 0 0 0.6em rgba(192,192,192,0.075); border: 2px solid rgba(192,192,192,0.4) !important; }
.block.token-counter.error span{ box-shadow: 0 0 0.0 0.3em rgba(255,0,0,0.15), inset 0 0 0.6em rgba(255,0,0,0.075); border: 2px solid rgba(255,0,0,0.4) !important; }
.block.token-counter div{ display: inline; }
@@ -226,7 +226,7 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt
/* extra networks */
.extra-networks > div { margin: 0; gap: 0.2em; border-bottom: none !important; }
-.extra-networks .second-line { display: flex; width: -webkit-fill-available; gap: 0.2em; }
+.extra-networks .second-line { display: flex; width: -webkit-fill-available; gap: 0.3em; box-shadow: var(--input-shadow); }
.extra-networks .search { flex: 1; }
.extra-networks .description { flex: 3; }
.extra-networks .tab-nav > button { margin-right: 0; height: 24px; padding: 2px 4px 2px 4px; }
@@ -271,6 +271,7 @@ div.controlnet_main_options { display: grid; grid-template-columns: 1fr 1fr; gri
#refresh_tac_refreshTempFiles { display: none; }
#train_tab { flex-flow: row-reverse; }
#models_tab { flex-flow: row-reverse; }
+#swap_axes > button { min-width: 100px; font-size: 1em; }
.log-monitor { display: none; justify-content: unset !important; overflow: hidden; padding: 0; margin-top: auto; font-family: monospace; font-size: 0.85em; }
.log-monitor td, .log-monitor th { padding-left: 1em; }
diff --git a/modules/call_queue.py b/modules/call_queue.py
index 8eb703051..568d79344 100644
--- a/modules/call_queue.py
+++ b/modules/call_queue.py
@@ -47,10 +47,8 @@ def wrap_gradio_gpu_call(func, extra_outputs=None):
def wrap_gradio_call(func, extra_outputs=None, add_stats=False):
def f(*args, extra_outputs_array=extra_outputs, **kwargs):
- run_memmon = shared.opts.memmon_poll_rate > 0 and not shared.mem_mon.disabled and add_stats
- if run_memmon:
- shared.mem_mon.monitor()
t = time.perf_counter()
+ shared.mem_mon.reset()
try:
if shared.cmd_opts.profile:
pr = cProfile.Profile()
@@ -83,18 +81,11 @@ def wrap_gradio_call(func, extra_outputs=None, add_stats=False):
elapsed = time.perf_counter() - t
elapsed_m = int(elapsed // 60)
elapsed_s = elapsed % 60
- elapsed_text = f"{elapsed_s:.2f}s"
- if elapsed_m > 0:
- elapsed_text = f"{elapsed_m}m "+elapsed_text
- if run_memmon:
- mem_stats = {k: -(v//-(1024*1024)) for k, v in shared.mem_mon.stop().items()}
- active_peak = mem_stats['active_peak']
- reserved_peak = mem_stats['reserved_peak']
- sys_peak = mem_stats['system_peak']
- sys_total = mem_stats['total']
- vram_html = f" |
GPU active {active_peak} MB reserved {reserved_peak} MB | System peak {sys_peak} MB total {sys_total} MB
"
- else:
- vram_html = ''
- res[-1] += f""
+ elapsed_text = f"{elapsed_m}m {elapsed_s:.2f}s" if elapsed_m > 0 else f"{elapsed_s:.2f}s"
+ vram_html = ''
+ if not shared.mem_mon.disabled:
+ vram = {k: -(v//-(1024*1024)) for k, v in shared.mem_mon.read().items()}
+ vram_html += f" | GPU active {max(vram['active_peak'], vram['reserved_peak'])} MB reserved {vram['reserved']} | used {vram['used']} MB free {vram['free']} MB total {vram['total']} MB | retries {vram['retries']} oom {vram['oom']}
"
+ res[-1] += f""
return tuple(res)
return f
diff --git a/modules/images.py b/modules/images.py
index f507f1f77..5d7684285 100644
--- a/modules/images.py
+++ b/modules/images.py
@@ -451,7 +451,7 @@ def atomically_save_image():
image_format = 'JPEG'
if shared.opts.image_watermark_enabled:
image = set_watermark(image, shared.opts.image_watermark)
- shared.log.debug(f'Saving image: type={image_format} size={image.size} {fn}')
+ shared.log.debug(f'Saving: image={fn} type={image_format} size={image.width}x{image.height}')
# actual save
exifinfo = (exifinfo or "") if shared.opts.image_metadata else ""
if image_format == 'PNG':
diff --git a/modules/img2img.py b/modules/img2img.py
index 50fc4524d..fcfc50d90 100644
--- a/modules/img2img.py
+++ b/modules/img2img.py
@@ -74,7 +74,7 @@ def process_batch(p, input_files, input_dir, output_dir, inpaint_mask_dir, args)
for k, v in items.items():
image.info[k] = v
images.save_image(image, path=output_dir, basename=basename, seed=None, prompt=None, extension=ext, info=geninfo, short_filename=True, no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=image.info, forced_filename=None)
- shared.log.debug(f'Processed: {len(image_files)} Memory: {memory_stats()} batch')
+ shared.log.debug(f'Processed: images={len(image_files)} memory={memory_stats()} op=batch')
def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_styles, init_img, sketch, init_img_with_mask, inpaint_color_sketch, inpaint_color_sketch_orig, init_img_inpaint, init_mask_inpaint, steps: int, sampler_index: int, latent_index: int, mask_blur: int, mask_alpha: float, inpainting_fill: int, full_quality: bool, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, image_cfg_scale: float, diffusers_guidance_rescale: float, refiner_steps: int, refiner_start: float, clip_skip: int, denoising_strength: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, selected_scale_tab: int, height: int, width: int, scale_by: float, resize_mode: int, inpaint_full_res: bool, inpaint_full_res_padding: int, inpainting_mask_invert: int, img2img_batch_files: list, img2img_batch_input_dir: str, img2img_batch_output_dir: str, img2img_batch_inpaint_mask_dir: str, override_settings_texts, *args): # pylint: disable=unused-argument
@@ -195,5 +195,4 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s
processed = processing.process_images(p)
p.close()
generation_info_js = processed.js()
- shared.log.debug(f'Processed: {len(processed.images)} Memory: {memory_stats()} img')
return processed.images, generation_info_js, processed.info, plaintext_to_html(processed.comments)
diff --git a/modules/memmon.py b/modules/memmon.py
index 1e426b9cb..ceb53d972 100644
--- a/modules/memmon.py
+++ b/modules/memmon.py
@@ -1,73 +1,52 @@
-import threading
-import time
from collections import defaultdict
import torch
-from modules import devices
-class MemUsageMonitor(threading.Thread):
- run_flag = None
+class MemUsageMonitor():
device = None
disabled = False
opts = None
data = None
- def __init__(self, name, device, opts):
- threading.Thread.__init__(self)
+ def __init__(self, name, device):
self.name = name
self.device = device
- self.opts = opts
- self.daemon = True
- self.run_flag = threading.Event()
self.data = defaultdict(int)
if not torch.cuda.is_available():
self.disabled = True
else:
try:
- self.cuda_mem_get_info()
+ torch.cuda.mem_get_info(self.device.index if self.device.index is not None else torch.cuda.current_device())
torch.cuda.memory_stats(self.device)
except Exception:
self.disabled = True
- def cuda_mem_get_info(self):
- index = self.device.index if self.device.index is not None else torch.cuda.current_device()
- return torch.cuda.mem_get_info(index)
-
- def run(self):
+ def cuda_mem_get_info(self): # legacy for extensions only
if self.disabled:
- return
- while True:
- self.run_flag.wait()
- torch.cuda.reset_peak_memory_stats()
- self.data.clear()
- if self.opts.memmon_poll_rate <= 0:
- self.run_flag.clear()
- continue
- self.data["min_free"] = self.cuda_mem_get_info()[0]
- while self.run_flag.is_set():
- free, _total = self.cuda_mem_get_info()
- self.data["min_free"] = min(self.data["min_free"], free)
- time.sleep(1 / self.opts.memmon_poll_rate)
+ return 0, 0
+ return torch.cuda.mem_get_info(self.device.index if self.device.index is not None else torch.cuda.current_device())
- def monitor(self):
- self.run_flag.set()
+ def reset(self):
+ if not self.disabled:
+ torch.cuda.reset_peak_memory_stats(self.device)
+ self.data['retries'] = 0
+ self.data['oom'] = 0
+ # torch.cuda.reset_accumulated_memory_stats(self.device)
+ # torch.cuda.reset_max_memory_allocated(self.device)
+ # torch.cuda.reset_max_memory_cached(self.device)
def read(self):
if not self.disabled:
- free, total = self.cuda_mem_get_info()
- self.data["free"] = free
- self.data["total"] = total
try:
+ self.data["free"], self.data["total"] = torch.cuda.mem_get_info(self.device.index if self.device.index is not None else torch.cuda.current_device())
torch_stats = torch.cuda.memory_stats(self.device)
self.data["active"] = torch_stats["active.all.current"]
self.data["active_peak"] = torch_stats["active_bytes.all.peak"]
self.data["reserved"] = torch_stats["reserved_bytes.all.current"]
self.data["reserved_peak"] = torch_stats["reserved_bytes.all.peak"]
- self.data["system_peak"] = total - self.data["min_free"]
+ self.data['retries'] = torch_stats["num_alloc_retries"]
+ self.data['oom'] = torch_stats["num_ooms"]
+ self.data["used"] = self.data["total"] - self.data["free"]
except Exception:
self.disabled = True
return self.data
-
- def stop(self):
- self.run_flag.clear()
- return self.read()
diff --git a/modules/paths.py b/modules/paths.py
index ab6ef42e4..f4b991f5c 100644
--- a/modules/paths.py
+++ b/modules/paths.py
@@ -42,10 +42,6 @@ for d, must_exist, what, _options in path_dirs:
print(f"Warning: {what} not found at path {must_exist_path}", file=sys.stderr)
else:
d = os.path.abspath(d)
- # if "atstart" in options:
- # sys.path.insert(0, d)
- # else:
- # sys.path.append(d)
sys.path.append(d)
paths[what] = d
diff --git a/modules/processing.py b/modules/processing.py
index a5fddd210..aa5365fc8 100644
--- a/modules/processing.py
+++ b/modules/processing.py
@@ -1,29 +1,39 @@
+import os
import json
import math
-import os
+import time
import hashlib
import random
from contextlib import nullcontext
from typing import Any, Dict, List
import torch
import numpy as np
-from PIL import Image, ImageFilter, ImageOps
import cv2
+from PIL import Image, ImageFilter, ImageOps
from skimage import exposure
from ldm.data.util import AddMiDaS
from ldm.models.diffusion.ddpm import LatentDepth2ImageDiffusion
from einops import repeat, rearrange
from blendmodes.blend import blendLayers, BlendType
from installer import git_commit
-import modules.sd_hijack
-from modules import devices, prompt_parser, masking, sd_samplers, lowvram, generation_parameters_copypaste, script_callbacks, extra_networks, sd_vae_approx, scripts, sd_samplers_common # pylint: disable=unused-import
-import modules.shared as shared
-import modules.paths as paths
+from modules import shared, devices
+import modules.memstats
+import modules.lowvram
+import modules.masking
+import modules.paths
+import modules.scripts
+import modules.prompt_parser
+import modules.extra_networks
import modules.face_restoration
import modules.images as images
import modules.styles
-import modules.sd_models as sd_models
-import modules.sd_vae as sd_vae
+import modules.sd_hijack
+import modules.sd_samplers
+import modules.sd_samplers_common
+import modules.sd_models
+import modules.sd_vae
+import modules.sd_vae_approx
+import modules.generation_parameters_copypaste
opt_C = 4
@@ -451,7 +461,7 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts, all_seeds, all_su
index = position_in_batch + iteration * p.batch_size
if all_negative_prompts is None:
all_negative_prompts = p.all_negative_prompts
- vae = (None if not shared.opts.add_model_name_to_info or sd_vae.loaded_vae_file is None else os.path.splitext(os.path.basename(sd_vae.loaded_vae_file))[0]) if p.full_quality else 'TAESD'
+ vae = (None if not shared.opts.add_model_name_to_info or modules.sd_vae.loaded_vae_file is None else os.path.splitext(os.path.basename(modules.sd_vae.loaded_vae_file))[0]) if p.full_quality else 'TAESD'
comment = ', '.join(comments) if comments is not None and type(comments) is list else None
args = {
@@ -473,7 +483,7 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts, all_seeds, all_su
"Prompt2": p.refiner_prompt if len(p.refiner_prompt) > 0 else None,
"Negative2": p.refiner_negative if len(p.refiner_negative) > 0 else None,
# other
- "ENSD": shared.opts.eta_noise_seed_delta if shared.opts.eta_noise_seed_delta != 0 and sd_samplers_common.is_sampler_using_eta_noise_seed_delta(p) else None,
+ "ENSD": shared.opts.eta_noise_seed_delta if shared.opts.eta_noise_seed_delta != 0 and modules.sd_samplers_common.is_sampler_using_eta_noise_seed_delta(p) else None,
"Tiling": p.tiling if p.tiling else None,
# sdnext
"Backend": 'Diffusers' if shared.backend == shared.Backend.DIFFUSERS else 'Original',
@@ -524,7 +534,7 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts, all_seeds, all_su
args['Token merging ratio hr'] = token_merging_ratio_hr if token_merging_ratio_hr != 0 else None
args.update(p.extra_generation_params)
- params_text = ", ".join([k if k == v else f'{k}: {generation_parameters_copypaste.quote(v)}' for k, v in args.items() if v is not None])
+ params_text = ", ".join([k if k == v else f'{k}: {modules.generation_parameters_copypaste.quote(v)}' for k, v in args.items() if v is not None])
negative_prompt_text = f"\nNegative prompt: {all_negative_prompts[index]}" if all_negative_prompts[index] else ""
infotext = f"{all_prompts[index]}{negative_prompt_text}\n{params_text}".strip()
return infotext
@@ -568,18 +578,18 @@ def process_images(p: StableDiffusionProcessing) -> Processed:
stored_opts[k] = shared.opts.data.get(k, None) or shared.opts.data_labels[k].default
try:
# if no checkpoint override or the override checkpoint can't be found, remove override entry and load opts checkpoint
- if p.override_settings.get('sd_model_checkpoint', None) is not None and sd_models.checkpoint_aliases.get(p.override_settings.get('sd_model_checkpoint')) is None:
+ if p.override_settings.get('sd_model_checkpoint', None) is not None and modules.sd_models.checkpoint_aliases.get(p.override_settings.get('sd_model_checkpoint')) is None:
p.override_settings.pop('sd_model_checkpoint', None)
- sd_models.reload_model_weights()
+ modules.sd_models.reload_model_weights()
for k, v in p.override_settings.items():
setattr(shared.opts, k, v)
if k == 'sd_model_checkpoint':
- sd_models.reload_model_weights()
+ modules.sd_models.reload_model_weights()
if k == 'sd_vae':
- sd_vae.reload_vae_weights()
+ modules.sd_vae.reload_vae_weights()
if not shared.opts.cuda_compile:
- sd_models.apply_token_merging(p.sd_model, p.get_token_merging_ratio())
+ modules.sd_models.apply_token_merging(p.sd_model, p.get_token_merging_ratio())
if shared.cmd_opts.profile:
"""
@@ -598,16 +608,16 @@ def process_images(p: StableDiffusionProcessing) -> Processed:
res = process_images_inner(p)
finally:
if not shared.opts.cuda_compile:
- sd_models.apply_token_merging(p.sd_model, 0)
+ modules.sd_models.apply_token_merging(p.sd_model, 0)
if p.override_settings_restore_afterwards: # restore opts to original state
for k, v in stored_opts.items():
setattr(shared.opts, k, v)
if k == 'sd_model_checkpoint':
- sd_models.reload_model_weights()
+ modules.sd_models.reload_model_weights()
if k == 'sd_model_refiner':
- sd_models.reload_model_weights()
+ modules.sd_models.reload_model_weights()
if k == 'sd_vae':
- sd_vae.reload_vae_weights()
+ modules.sd_vae.reload_vae_weights()
return res
@@ -672,15 +682,6 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
cached_c = [None, None]
def get_conds_with_caching(function, required_prompts, steps, cache):
- """
- Returns the result of calling function(shared.sd_model, required_prompts, steps)
- using a cache to store the result if the same arguments have been used before.
-
- cache is an array containing two elements. The first element is a tuple
- representing the previously used arguments, or None if no arguments
- have been used before. The second element is where the previously
- computed result is stored.
- """
if cache[0] is not None and (required_prompts, steps) == cache[0]:
return cache[1]
with devices.autocast():
@@ -692,11 +693,12 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
return ''
ema_scope_context = p.sd_model.ema_scope if shared.backend == shared.Backend.ORIGINAL else nullcontext
- with torch.no_grad(), ema_scope_context():
+ with torch.inference_mode(), ema_scope_context():
+ t0 = time.time()
with devices.autocast():
p.init(p.all_prompts, p.all_seeds, p.all_subseeds)
if shared.opts.live_previews_enable and shared.opts.show_progress_type == "Approximate NN" and shared.backend == shared.Backend.ORIGINAL:
- sd_vae_approx.model()
+ modules.sd_vae_approx.model()
if shared.state.job_count == -1:
shared.state.job_count = p.n_iter
extra_network_data = None
@@ -717,25 +719,25 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
p.scripts.before_process_batch(p, batch_number=n, prompts=p.prompts, seeds=p.seeds, subseeds=p.subseeds)
if len(p.prompts) == 0:
break
- p.prompts, extra_network_data = extra_networks.parse_prompts(p.prompts)
+ p.prompts, extra_network_data = modules.extra_networks.parse_prompts(p.prompts)
if not p.disable_extra_networks:
with devices.autocast():
- extra_networks.activate(p, extra_network_data)
+ modules.extra_networks.activate(p, extra_network_data)
if p.scripts is not None:
p.scripts.process_batch(p, batch_number=n, prompts=p.prompts, seeds=p.seeds, subseeds=p.subseeds)
if n == 0:
- with open(os.path.join(paths.data_path, "params.txt"), "w", encoding="utf8") as file:
+ with open(os.path.join(modules.paths.data_path, "params.txt"), "w", encoding="utf8") as file:
processed = Processed(p, [], p.seed, "")
file.write(processed.infotext(p, 0))
step_multiplier = 1
- sampler_config = sd_samplers.find_sampler_config(p.sampler_name)
+ sampler_config = modules.sd_samplers.find_sampler_config(p.sampler_name)
step_multiplier = 2 if sampler_config and sampler_config.options.get("second_order", False) else 1
if p.n_iter > 1:
shared.state.job = f"Batch {n+1} out of {p.n_iter}"
if shared.backend == shared.Backend.ORIGINAL:
- uc = get_conds_with_caching(prompt_parser.get_learned_conditioning, p.negative_prompts, p.steps * step_multiplier, cached_uc)
- c = get_conds_with_caching(prompt_parser.get_multicond_learned_conditioning, p.prompts, p.steps * step_multiplier, cached_c)
+ uc = get_conds_with_caching(modules.prompt_parser.get_learned_conditioning, p.negative_prompts, p.steps * step_multiplier, cached_uc)
+ c = get_conds_with_caching(modules.prompt_parser.get_multicond_learned_conditioning, p.prompts, p.steps * step_multiplier, cached_c)
if len(modules.sd_hijack.model_hijack.comments) > 0:
for comment in modules.sd_hijack.model_hijack.comments:
comments[comment] = 1
@@ -749,8 +751,8 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
if not shared.opts.no_half and not shared.opts.no_half_vae and shared.cmd_opts.rollback_vae:
shared.log.warning('Tensor with all NaNs was produced in VAE')
devices.dtype_vae = torch.bfloat16
- vae_file, vae_source = sd_vae.resolve_vae(p.sd_model.sd_model_checkpoint)
- sd_vae.load_vae(p.sd_model, vae_file, vae_source)
+ vae_file, vae_source = modules.sd_vae.resolve_vae(p.sd_model.sd_model_checkpoint)
+ modules.sd_vae.load_vae(p.sd_model, vae_file, vae_source)
x_samples_ddim = [decode_first_stage(p.sd_model, samples_ddim[i:i+1].to(dtype=devices.dtype_vae))[0].cpu() for i in range(samples_ddim.size(0))]
for x in x_samples_ddim:
devices.test_for_nans(x, "vae")
@@ -768,14 +770,14 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
raise ValueError(f"Unknown backend {shared.backend}")
if shared.cmd_opts.lowvram or shared.cmd_opts.medvram and shared.backend == shared.Backend.ORIGINAL:
- lowvram.send_everything_to_cpu()
+ modules.lowvram.send_everything_to_cpu()
devices.torch_gc()
if p.scripts is not None:
p.scripts.postprocess_batch(p, x_samples_ddim, batch_number=n)
if p.scripts is not None:
p.prompts = p.all_prompts[n * p.batch_size:(n + 1) * p.batch_size]
p.negative_prompts = p.all_negative_prompts[n * p.batch_size:(n + 1) * p.batch_size]
- batch_params = scripts.PostprocessBatchListArgs(list(x_samples_ddim))
+ batch_params = modules.scripts.PostprocessBatchListArgs(list(x_samples_ddim))
p.scripts.postprocess_batch_list(p, batch_params, batch_number=n)
x_samples_ddim = batch_params.images
@@ -797,7 +799,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
x_sample = modules.face_restoration.restore_faces(x_sample)
image = Image.fromarray(x_sample)
if p.scripts is not None:
- pp = scripts.PostprocessImageArgs(image)
+ pp = modules.scripts.PostprocessImageArgs(image)
p.scripts.postprocess_image(p, pp)
image = pp.image
if p.color_corrections is not None and i < len(p.color_corrections):
@@ -832,6 +834,9 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
devices.torch_gc()
shared.state.nextjob()
+ t1 = time.time()
+ shared.log.info(f'Processed: images={len(output_images)} time={t1 - t0:.2f}s its={(p.steps * len(output_images)) / (t1 - t0):.2f} memory={modules.memstats.memory_stats()}')
+
p.color_corrections = None
index_of_first_image = 0
unwanted_grid_because_of_img_count = len(output_images) < 2 and shared.opts.grid_only_if_multiple
@@ -848,7 +853,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
images.save_image(grid, p.outpath_grids, "grid", p.all_seeds[0], p.all_prompts[0], shared.opts.grid_format, info=infotext(), short_filename=not shared.opts.grid_extended_filename, p=p, grid=True)
if not p.disable_extra_networks and extra_network_data:
- extra_networks.deactivate(p, extra_network_data)
+ modules.extra_networks.deactivate(p, extra_network_data)
res = Processed(
p,
@@ -905,7 +910,7 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing):
def init(self, all_prompts, all_seeds, all_subseeds):
if shared.backend == shared.Backend.DIFFUSERS:
- sd_models.set_diffuser_pipe(self.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE)
+ modules.sd_models.set_diffuser_pipe(self.sd_model, modules.sd_models.DiffusersTaskType.TEXT_2_IMAGE)
self.width = self.width or 512
self.height = self.height or 512
@@ -953,7 +958,7 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing):
if not shared.opts.save or self.do_not_save_samples or not shared.opts.save_images_before_highres_fix:
return
if not isinstance(image, Image.Image):
- image = sd_samplers.sample_to_image(image, index, approximation=0)
+ image = modules.sd_samplers.sample_to_image(image, index, approximation=0)
orig1 = self.extra_generation_params
orig2 = self.restore_faces
self.extra_generation_params = {}
@@ -964,10 +969,10 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing):
images.save_image(image, self.outpath_samples, "", seeds[index], prompts[index], shared.opts.samples_format, info=info, suffix="-before-highres-fix")
if shared.backend == shared.Backend.DIFFUSERS:
- sd_models.set_diffuser_pipe(self.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE)
+ modules.sd_models.set_diffuser_pipe(self.sd_model, modules.sd_models.DiffusersTaskType.TEXT_2_IMAGE)
self.ops.append('txt2img')
- self.sampler = sd_samplers.create_sampler(self.sampler_name, self.sd_model)
+ self.sampler = modules.sd_samplers.create_sampler(self.sampler_name, self.sd_model)
latent_scale_mode = shared.latent_upscale_modes.get(self.hr_upscaler, None) if self.hr_upscaler is not None else shared.latent_upscale_modes.get(shared.latent_upscale_default_mode, "None")
if self.enable_hr and latent_scale_mode is None:
if len([x for x in shared.sd_upscalers if x.name == self.hr_upscaler]) == 0:
@@ -1020,14 +1025,14 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing):
shared.state.nextjob()
if self.latent_sampler == "PLMS":
self.latent_sampler = 'UniPC'
- self.sampler = sd_samplers.create_sampler(self.latent_sampler or self.sampler_name, self.sd_model)
+ self.sampler = modules.sd_samplers.create_sampler(self.latent_sampler or self.sampler_name, self.sd_model)
samples = samples[:, :, self.truncate_y//2:samples.shape[2]-(self.truncate_y+1)//2, self.truncate_x//2:samples.shape[3]-(self.truncate_x+1)//2]
noise = create_random_tensors(samples.shape[1:], seeds=seeds, subseeds=subseeds, subseed_strength=subseed_strength, p=self)
x = None
devices.torch_gc() # GC now before running the next img2img to prevent running out of memory
- sd_models.apply_token_merging(self.sd_model, self.get_token_merging_ratio(for_hr=True))
+ modules.sd_models.apply_token_merging(self.sd_model, self.get_token_merging_ratio(for_hr=True))
samples = self.sampler.sample_img2img(self, samples, noise, conditioning, unconditional_conditioning, steps=self.hr_second_pass_steps or self.steps, image_conditioning=image_conditioning)
- sd_models.apply_token_merging(self.sd_model, self.get_token_merging_ratio())
+ modules.sd_models.apply_token_merging(self.sd_model, self.get_token_merging_ratio())
self.is_hr_pass = False
return samples
@@ -1065,14 +1070,14 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing):
def init(self, all_prompts, all_seeds, all_subseeds):
if shared.backend == shared.Backend.DIFFUSERS and self.image_mask is None:
- sd_models.set_diffuser_pipe(self.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE)
+ modules.sd_models.set_diffuser_pipe(self.sd_model, modules.sd_models.DiffusersTaskType.IMAGE_2_IMAGE)
elif shared.backend == shared.Backend.DIFFUSERS and self.image_mask is not None:
- sd_models.set_diffuser_pipe(self.sd_model, sd_models.DiffusersTaskType.INPAINTING)
+ modules.sd_models.set_diffuser_pipe(self.sd_model, modules.sd_models.DiffusersTaskType.INPAINTING)
self.sd_model.dtype = self.sd_model.unet.dtype
if self.sampler_name == "PLMS":
self.sampler_name = 'UniPC'
- self.sampler = sd_samplers.create_sampler(self.sampler_name, self.sd_model)
+ self.sampler = modules.sd_samplers.create_sampler(self.sampler_name, self.sd_model)
if self.image_mask is not None:
self.ops.append('inpaint')
@@ -1089,8 +1094,8 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing):
if self.inpaint_full_res:
self.mask_for_overlay = image_mask
mask = image_mask.convert('L')
- crop_region = masking.get_crop_region(np.array(mask), self.inpaint_full_res_padding)
- crop_region = masking.expand_crop_region(crop_region, self.width, self.height, mask.width, mask.height)
+ crop_region = modules.masking.get_crop_region(np.array(mask), self.inpaint_full_res_padding)
+ crop_region = modules.masking.expand_crop_region(crop_region, self.width, self.height, mask.width, mask.height)
x1, y1, x2, y2 = crop_region
mask = mask.crop(crop_region)
image_mask = images.resize_image(3, mask, self.width, self.height)
@@ -1137,7 +1142,7 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing):
self.init_images = [image] # assign early for diffusers
if image_mask is not None:
if self.inpainting_fill != 1:
- image = masking.fill(image, latent_mask)
+ image = modules.masking.fill(image, latent_mask)
if add_color_corrections:
self.color_corrections.append(setup_color_correction(image))
image = np.array(image).astype(np.float32) / 255.0
@@ -1183,9 +1188,9 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing):
def sample(self, conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength, prompts):
if shared.backend == shared.Backend.DIFFUSERS:
if self.init_mask is None: # pylint: disable=no-member
- sd_models.set_diffuser_pipe(self.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE)
+ modules.sd_models.set_diffuser_pipe(self.sd_model, modules.sd_models.DiffusersTaskType.IMAGE_2_IMAGE)
else:
- sd_models.set_diffuser_pipe(self.sd_model, sd_models.DiffusersTaskType.INPAINTING)
+ modules.sd_models.set_diffuser_pipe(self.sd_model, modules.sd_models.DiffusersTaskType.INPAINTING)
self.sd_model.dtype = self.sd_model.unet.dtype
x = create_random_tensors([4, self.height // 8, self.width // 8], seeds=seeds, subseeds=subseeds, subseed_strength=self.subseed_strength, seed_resize_from_h=self.seed_resize_from_h, seed_resize_from_w=self.seed_resize_from_w, p=self)
diff --git a/modules/scripts.py b/modules/scripts.py
index 30136b561..33111a3d3 100644
--- a/modules/scripts.py
+++ b/modules/scripts.py
@@ -276,6 +276,25 @@ def wrap_call(func, filename, funcname, *args, default=None, **kwargs):
return default
+class ScriptSummary:
+ def __init__(self, op):
+ self.start = time.time()
+ self.update = time.time()
+ self.op = op
+ self.time = {}
+
+ def record(self, script):
+ self.update = time.time()
+ self.time[script] = round(time.time() - self.update, 2)
+
+ def report(self):
+ total = sum(self.time.values())
+ if total == 0:
+ return
+ scripts = [f'{k}:{v}s' for k, v in self.time.items() if v > 0]
+ log.debug(f'Script: op={self.op} total={total}s scripts={scripts}')
+
+
class ScriptRunner:
def __init__(self):
self.scripts = []
@@ -405,6 +424,7 @@ class ScriptRunner:
return inputs
def run(self, p, *args):
+ s = ScriptSummary('run')
script_index = args[0]
if script_index == 0:
return None
@@ -412,117 +432,111 @@ class ScriptRunner:
if script is None:
return None
parsed = p.per_script_args.get(script.title(), args[script.args_from:script.args_to])
- t0 = time.time()
processed = script.run(p, *parsed)
- log.debug(f'Script run: {script.title()}:{round(time.time()-t0, 2)}s')
+ s.record(script.title())
+ s.report()
return processed
def process(self, p, **kwargs):
- s = []
+ s = ScriptSummary('process')
for script in self.alwayson_scripts:
try:
- t0 = time.time()
args = p.per_script_args.get(script.title(), p.script_args[script.args_from:script.args_to])
script.process(p, *args, **kwargs)
- s.append(f'{script.title()}:{round(time.time()-t0, 2)}s')
except Exception as e:
errors.display(e, f'Running script process: {script.filename}')
- log.debug(f'Script process: {s}')
+ s.record(script.title())
+ s.report()
def before_process_batch(self, p, **kwargs):
- s = []
+ s = ScriptSummary('before-process-batch')
for script in self.alwayson_scripts:
try:
- t0 = time.time()
args = p.per_script_args.get(script.title(), p.script_args[script.args_from:script.args_to])
script.before_process_batch(p, *args, **kwargs)
- s.append(f'{script.title()}:{round(time.time()-t0, 2)}s')
except Exception as e:
errors.display(e, f'Running script before process batch: {script.filename}')
- log.debug(f'Script before-process-batch: {s}')
+ s.record(script.title())
+ s.report()
def process_batch(self, p, **kwargs):
- s = []
+ s = ScriptSummary('process-batch')
for script in self.alwayson_scripts:
try:
- t0 = time.time()
args = p.per_script_args.get(script.title(), p.script_args[script.args_from:script.args_to])
script.process_batch(p, *args, **kwargs)
- s.append(f'{script.title()}:{round(time.time()-t0, 2)}s')
except Exception as e:
errors.display(e, f'Running script process batch: {script.filename}')
- log.debug(f'Script process-batch: {s}')
+ s.record(script.title())
+ s.report()
def postprocess(self, p, processed):
- s = []
+ s = ScriptSummary('postprocess')
for script in self.alwayson_scripts:
try:
- t0 = time.time()
args = p.per_script_args.get(script.title(), p.script_args[script.args_from:script.args_to])
script.postprocess(p, processed, *args)
- s.append(f'{script.title()}:{round(time.time()-t0, 2)}s')
except Exception as e:
errors.display(e, f'Running script postprocess: {script.filename}')
- log.debug(f'Script postprocess: {s}')
+ s.record(script.title())
+ s.report()
def postprocess_batch(self, p, images, **kwargs):
- s = []
+ s = ScriptSummary('postprocess-batch')
for script in self.alwayson_scripts:
try:
- t0 = time.time()
args = p.per_script_args.get(script.title(), p.script_args[script.args_from:script.args_to])
script.postprocess_batch(p, *args, images=images, **kwargs)
- s.append(f'{script.title()}:{round(time.time()-t0, 2)}s')
except Exception as e:
errors.display(e, f'Running script before postprocess batch: {script.filename}')
- log.debug(f'Script postprocess-batch: {s}')
+ s.record(script.title())
+ s.report()
def postprocess_batch_list(self, p, pp: PostprocessBatchListArgs, **kwargs):
- s = []
+ s = ScriptSummary('postprocess-batch-list')
for script in self.alwayson_scripts:
try:
- t0 = time.time()
args = p.per_script_args.get(script.title(), p.script_args[script.args_from:script.args_to])
script.postprocess_batch_list(p, pp, *args, **kwargs)
- s.append(f'{script.title()}:{round(time.time()-t0, 2)}s')
except Exception as e:
errors.display(e, f'Running script before postprocess batch list: {script.filename}')
- log.debug(f'Script postprocess-batch-list: {s}')
+ s.record(script.title())
+ s.report()
def postprocess_image(self, p, pp: PostprocessImageArgs):
- s = []
+ s = ScriptSummary('postprocess-image')
for script in self.alwayson_scripts:
try:
- t0 = time.time()
args = p.per_script_args.get(script.title(), p.script_args[script.args_from:script.args_to])
script.postprocess_image(p, pp, *args)
- s.append(f'{script.title()}:{round(time.time()-t0, 2)}s')
except Exception as e:
errors.display(e, f'Running script postprocess image: {script.filename}')
- log.debug(f'Script postprocess-image: {s}')
+ s.record(script.title())
+ s.report()
def before_component(self, component, **kwargs):
+ s = ScriptSummary('before-component')
for script in self.scripts:
try:
- t0 = time.time()
script.before_component(component, **kwargs)
- time_component[script.title()] = time_component.get(script.title(), 0) + (time.time()-t0)
except Exception as e:
errors.display(e, f'Running script before component: {script.filename}')
+ s.record(script.title())
+ s.report()
def after_component(self, component, **kwargs):
+ s = ScriptSummary('after-component')
for script in self.scripts:
try:
- t0 = time.time()
script.after_component(component, **kwargs)
- time_component[script.title()] = time_component.get(script.title(), 0) + (time.time()-t0)
except Exception as e:
errors.display(e, f'Running script after component: {script.filename}')
+ s.record(script.title())
+ s.report()
def reload_sources(self, cache):
- s = []
+ s = ScriptSummary('reload-sources')
for si, script in list(enumerate(self.scripts)):
- t0 = time.time()
args_from = script.args_from
args_to = script.args_to
filename = script.filename
@@ -536,8 +550,8 @@ class ScriptRunner:
self.scripts[si].filename = filename
self.scripts[si].args_from = args_from
self.scripts[si].args_to = args_to
- s.append(f'{script.title()}:{round(time.time()-t0, 2)}s')
- log.debug(f'Script reload-sources: {s}')
+ s.record(script.title())
+ s.report()
scripts_txt2img: ScriptRunner = None
diff --git a/modules/sd_models.py b/modules/sd_models.py
index 7b8da7fa3..b59ae8681 100644
--- a/modules/sd_models.py
+++ b/modules/sd_models.py
@@ -355,7 +355,7 @@ def read_state_dict(checkpoint_file, map_location=None): # pylint: disable=unuse
return None
try:
pl_sd = None
- with progress.open(checkpoint_file, 'rb', description=f'Loading weights: [cyan]{checkpoint_file}', auto_refresh=True) as f:
+ with progress.open(checkpoint_file, 'rb', description=f'[cyan]Loading weights: [yellow]{checkpoint_file}', auto_refresh=True) as f:
_, extension = os.path.splitext(checkpoint_file)
if extension.lower() == ".ckpt" and shared.opts.sd_disable_ckpt:
shared.log.warning(f"Checkpoint loading disabled: {checkpoint_file}")
diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py
index 41d1a4959..e0f6493c8 100644
--- a/modules/sd_samplers.py
+++ b/modules/sd_samplers.py
@@ -47,14 +47,14 @@ def create_sampler(name, model):
sampler = config.constructor(model)
sampler.config = config
sampler.name = name
- shared.log.debug(f'Sampler: {sampler.name} {sampler.config.options}')
+ shared.log.debug(f'Sampler: sampler={sampler.name} config={sampler.config.options}')
return sampler
elif shared.backend == shared.Backend.DIFFUSERS:
sampler = config.constructor(model)
if not hasattr(model, 'scheduler_config'):
model.scheduler_config = sampler.sampler.config.copy()
model.scheduler = sampler.sampler
- shared.log.debug(f'Sampler: {sampler.name} {sampler.config}')
+ shared.log.debug(f'Sampler: sampler={sampler.name} config={sampler.config}')
return sampler.sampler
else:
return None
diff --git a/modules/shared.py b/modules/shared.py
index 48406f80d..0dce43a9d 100644
--- a/modules/shared.py
+++ b/modules/shared.py
@@ -378,7 +378,7 @@ options_templates.update(options_section(('optimizations', "Optimizations"), {
}))
options_templates.update(options_section(('cuda', "Compute Settings"), {
- "memmon_poll_rate": OptionInfo(2, "VRAM usage polls per second during generation", gr.Slider, {"minimum": 0, "maximum": 40, "step": 1}),
+ # "memmon_poll_rate": OptionInfo(2, "VRAM usage polls per second during generation", gr.Slider, {"minimum": 0, "maximum": 40, "step": 1}),
"precision": OptionInfo("Autocast", "Precision type", gr.Radio, lambda: {"choices": ["Autocast", "Full"]}),
"cuda_dtype": OptionInfo("FP32" if sys.platform == "darwin" or cmd_opts.use_openvino else "BF16" if devices.backend == "ipex" else "FP16", "Device precision type", gr.Radio, lambda: {"choices": ["FP32", "FP16", "BF16"]}),
"no_half": OptionInfo(False, "Use full precision for model (--no-half)", None, None, None),
@@ -825,8 +825,7 @@ devices.device, devices.device_interrogate, devices.device_gfpgan, devices.devic
device = devices.device
batch_cond_uncond = opts.always_batch_cond_uncond or not (cmd_opts.lowvram or cmd_opts.medvram)
parallel_processing_allowed = not cmd_opts.lowvram
-mem_mon = modules.memmon.MemUsageMonitor("MemMon", device, opts)
-mem_mon.start()
+mem_mon = modules.memmon.MemUsageMonitor("MemMon", devices.device)
if devices.backend == "directml":
directml_do_hijack()
@@ -916,7 +915,7 @@ def restart_server(restart=True):
demo.server.close()
demo.fns = []
# os._exit(0)
- except Exception as e:
+ except (Exception, BaseException) as e:
log.error(f'Server shutdown error: {e}')
if restart:
log.info('Server will restart')
diff --git a/modules/txt2img.py b/modules/txt2img.py
index db246d834..648eb836a 100644
--- a/modules/txt2img.py
+++ b/modules/txt2img.py
@@ -2,7 +2,6 @@ import modules.scripts
from modules import sd_samplers, shared, processing
from modules.generation_parameters_copypaste import create_override_settings_dict
from modules.ui import plaintext_to_html
-from modules.memstats import memory_stats
def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, steps: int, sampler_index: int, latent_index: int, full_quality: bool, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, image_cfg_scale: float, diffusers_guidance_rescale: float, clip_skip: int, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, refiner_steps: int, refiner_start: int, refiner_prompt: str, refiner_negative: str, override_settings_texts, *args): # pylint: disable=unused-argument
@@ -68,5 +67,4 @@ def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, step
if processed is None:
return [], '', '', 'Error: processing failed'
generation_info_js = processed.js()
- shared.log.debug(f'Processed: {len(processed.images)} Memory: {memory_stats()} txt')
return processed.images, generation_info_js, processed.info, plaintext_to_html(processed.comments)
diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py
index 2b5cf3172..5ccf2bde0 100644
--- a/modules/ui_extra_networks.py
+++ b/modules/ui_extra_networks.py
@@ -17,7 +17,7 @@ import modules.ui_symbols as symbols
extra_pages = []
-allowed_dirs = set()
+allowed_dirs = []
dir_cache = {} # key=path, value=(mtime, listdir(path))
refresh_time = None
@@ -39,16 +39,21 @@ def register_page(page):
# registers extra networks page for the UI; recommend doing it in on_before_ui() callback for extensions
extra_pages.append(page)
allowed_dirs.clear()
- allowed_dirs.update(set(sum([x.allowed_directories_for_previews() for x in extra_pages], [])))
+ for page in extra_pages:
+ for folder in page.allowed_directories_for_previews():
+ if folder not in allowed_dirs:
+ allowed_dirs.append(os.path.abspath(folder))
def fetch_file(filename: str = ""):
- if filename.startswith('html/'):
+ if not os.path.exists(filename):
+ return JSONResponse({ "error": f"file {filename}: not found" }, status_code=404)
+ if filename.startswith('html/') or filename.startswith('models/'):
return FileResponse(filename, headers={"Accept-Ranges": "bytes"})
- if not any(Path(x).absolute() in Path(filename).absolute().parents for x in allowed_dirs):
- return JSONResponse({"error": f"File cannot be fetched: {filename}. Must be in one of directories registered by extra pages."})
+ if not any(Path(folder).absolute() in Path(filename).absolute().parents for folder in allowed_dirs):
+ return JSONResponse({ "error": f"file {filename}: must be in one of allowed directories" }, status_code=403)
if os.path.splitext(filename)[1].lower() not in (".png", ".jpg", ".jpeg", ".webp"):
- return JSONResponse({"error": f"File cannot be fetched: {filename}. Only png and jpg and webp."})
+ return JSONResponse({"error": f"file {filename}: not an image file"}, status_code=403)
return FileResponse(filename, headers={"Accept-Ranges": "bytes"})
@@ -362,7 +367,7 @@ def create_ui(container, button, tabname, skip_indexing = False):
def refresh(title):
res = []
for page in extra_pages:
- if title == '' or title == page.title:
+ if title == '' or title == page.title or len(page.html) == 0:
shared.log.debug(f"Refreshing Extra networks: page={page.title} tab={ui.tabname}")
page.refresh()
page.create_page(ui.tabname)