active_units.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. /**
  2. * Give a badge on ControlNet Accordion indicating total number of active
  3. * units.
  4. * Make active unit's tab name green.
  5. * Append control type to tab name.
  6. * Disable resize mode selection when A1111 img2img input is used.
  7. */
  8. (function () {
  9. const cnetAllUnits = new Map/* <Element, ControlNetUnitTab> */();
  10. const cnetAllAccordions = new Set();
  11. onUiUpdate(() => {
  12. const ImgChangeType = {
  13. NO_CHANGE: 0,
  14. REMOVE: 1,
  15. ADD: 2,
  16. SRC_CHANGE: 3,
  17. };
  18. function imgChangeObserved(mutationsList) {
  19. // Iterate over all mutations that just occured
  20. for (let mutation of mutationsList) {
  21. // Check if the mutation is an addition or removal of a node
  22. if (mutation.type === 'childList') {
  23. // Check if nodes were added
  24. if (mutation.addedNodes.length > 0) {
  25. for (const node of mutation.addedNodes) {
  26. if (node.tagName === 'IMG') {
  27. return ImgChangeType.ADD;
  28. }
  29. }
  30. }
  31. // Check if nodes were removed
  32. if (mutation.removedNodes.length > 0) {
  33. for (const node of mutation.removedNodes) {
  34. if (node.tagName === 'IMG') {
  35. return ImgChangeType.REMOVE;
  36. }
  37. }
  38. }
  39. }
  40. // Check if the mutation is a change of an attribute
  41. else if (mutation.type === 'attributes') {
  42. if (mutation.target.tagName === 'IMG' && mutation.attributeName === 'src') {
  43. return ImgChangeType.SRC_CHANGE;
  44. }
  45. }
  46. }
  47. return ImgChangeType.NO_CHANGE;
  48. }
  49. function childIndex(element) {
  50. // Get all child nodes of the parent
  51. let children = Array.from(element.parentNode.childNodes);
  52. // Filter out non-element nodes (like text nodes and comments)
  53. children = children.filter(child => child.nodeType === Node.ELEMENT_NODE);
  54. return children.indexOf(element);
  55. }
  56. function imageInputDisabledAlert() {
  57. alert('Inpaint control type must use a1111 input in img2img mode.');
  58. }
  59. class ControlNetUnitTab {
  60. constructor(tab) {
  61. this.tab = tab;
  62. this.isImg2Img = tab.querySelector('.cnet-unit-enabled').id.includes('img2img');
  63. this.enabledCheckbox = tab.querySelector('.cnet-unit-enabled input');
  64. this.inputImage = tab.querySelector('.cnet-input-image-group .cnet-image input[type="file"]');
  65. this.inputImageContainer = tab.querySelector('.cnet-input-image-group .cnet-image');
  66. this.controlTypeRadios = tab.querySelectorAll('.controlnet_control_type_filter_group input[type="radio"]');
  67. this.resizeModeRadios = tab.querySelectorAll('.controlnet_resize_mode_radio input[type="radio"]');
  68. this.runPreprocessorButton = tab.querySelector('.cnet-run-preprocessor');
  69. const tabs = tab.parentNode;
  70. this.tabNav = tabs.querySelector('.tab-nav');
  71. this.tabIndex = childIndex(tab) - 1; // -1 because tab-nav is also at the same level.
  72. this.attachEnabledButtonListener();
  73. this.attachControlTypeRadioListener();
  74. this.attachTabNavChangeObserver();
  75. this.attachImageUploadListener();
  76. this.attachImageStateChangeObserver();
  77. // Initial updates:
  78. if (this.isImg2Img)
  79. this.updateResizeModeState();
  80. }
  81. getTabNavButton() {
  82. return this.tabNav.querySelector(`:nth-child(${this.tabIndex + 1})`);
  83. }
  84. getActiveControlType() {
  85. for (let radio of this.controlTypeRadios) {
  86. if (radio.checked) {
  87. return radio.value;
  88. }
  89. }
  90. return undefined;
  91. }
  92. updateActiveState() {
  93. const tabNavButton = this.getTabNavButton();
  94. if (!tabNavButton) return;
  95. if (this.enabledCheckbox.checked) {
  96. tabNavButton.classList.add('cnet-unit-active');
  97. } else {
  98. tabNavButton.classList.remove('cnet-unit-active');
  99. }
  100. }
  101. /**
  102. * Add the active control type to tab displayed text.
  103. */
  104. updateActiveControlType() {
  105. const tabNavButton = this.getTabNavButton();
  106. if (!tabNavButton) return;
  107. // Remove the control if exists
  108. const controlTypeSuffix = tabNavButton.querySelector('.control-type-suffix');
  109. if (controlTypeSuffix) controlTypeSuffix.remove();
  110. // Add new suffix.
  111. const controlType = this.getActiveControlType();
  112. if (controlType === 'All') return;
  113. const span = document.createElement('span');
  114. span.innerHTML = `[${controlType}]`;
  115. span.classList.add('control-type-suffix');
  116. tabNavButton.appendChild(span);
  117. }
  118. /**
  119. * When 'Inpaint' control type is selected in img2img:
  120. * - Make image input disabled
  121. * - Clear existing image input
  122. */
  123. updateImageInputState() {
  124. if (!this.isImg2Img) return;
  125. const tabNavButton = this.getTabNavButton();
  126. if (!tabNavButton) return;
  127. const controlType = this.getActiveControlType();
  128. if (controlType.toLowerCase() === 'inpaint') {
  129. this.inputImage.disabled = true;
  130. this.inputImage.parentNode.addEventListener('click', imageInputDisabledAlert);
  131. const removeButton = this.tab.querySelector(
  132. '.cnet-input-image-group .cnet-image button[aria-label="Remove Image"]');
  133. if (removeButton) removeButton.click();
  134. } else {
  135. this.inputImage.disabled = false;
  136. this.inputImage.parentNode.removeEventListener('click', imageInputDisabledAlert);
  137. }
  138. }
  139. /**
  140. * For img2img, disable resize mode selection when using A1111
  141. * input, as the selected resize mode won't take any effect in
  142. * the backend when using A1111 input.
  143. */
  144. updateResizeModeState() {
  145. const img = this.inputImageContainer.querySelector('img');
  146. for (const radio of this.resizeModeRadios) {
  147. if (img) {
  148. radio.disabled = false;
  149. radio.parentNode.classList.remove('cnet-disabled-radio');
  150. radio.parentNode.removeAttribute('title');
  151. } else {
  152. radio.disabled = true;
  153. radio.parentNode.classList.add('cnet-disabled-radio');
  154. radio.parentNode.title = "Use A1111 resize mode when input is from A1111.";
  155. }
  156. }
  157. }
  158. attachEnabledButtonListener() {
  159. this.enabledCheckbox.addEventListener('change', () => {
  160. this.updateActiveState();
  161. });
  162. }
  163. attachControlTypeRadioListener() {
  164. for (const radio of this.controlTypeRadios) {
  165. radio.addEventListener('change', () => {
  166. this.updateActiveControlType();
  167. });
  168. }
  169. }
  170. /**
  171. * Each time the active tab change, all tab nav buttons are cleared and
  172. * regenerated by gradio. So we need to reapply the active states on
  173. * them.
  174. */
  175. attachTabNavChangeObserver() {
  176. new MutationObserver((mutationsList) => {
  177. for (const mutation of mutationsList) {
  178. if (mutation.type === 'childList') {
  179. this.updateActiveState();
  180. this.updateActiveControlType();
  181. }
  182. }
  183. }).observe(this.tabNav, { childList: true });
  184. }
  185. attachImageUploadListener() {
  186. // Automatically check `enable` checkbox when image is uploaded.
  187. this.inputImage.addEventListener('change', (event) => {
  188. if (!event.target.files) return;
  189. if (!this.enabledCheckbox.checked)
  190. this.enabledCheckbox.click();
  191. });
  192. }
  193. attachImageStateChangeObserver() {
  194. new MutationObserver((mutationsList) => {
  195. const changeObserved = imgChangeObserved(mutationsList);
  196. if (changeObserved === ImgChangeType.ADD ||
  197. changeObserved === ImgChangeType.REMOVE) {
  198. if (this.isImg2Img)
  199. this.updateResizeModeState();
  200. }
  201. if (changeObserved === ImgChangeType.ADD) {
  202. // enabling the run preprocessor button
  203. this.runPreprocessorButton.removeAttribute("disabled");
  204. this.runPreprocessorButton.title = 'Run preprocessor';
  205. }
  206. if (changeObserved === ImgChangeType.REMOVE) {
  207. // disabling the run preprocessor button
  208. this.runPreprocessorButton.setAttribute("disabled", true);
  209. this.runPreprocessorButton.title = "No ControlNet input image available";
  210. }
  211. }).observe(this.inputImageContainer, {
  212. childList: true,
  213. subtree: true,
  214. });
  215. }
  216. }
  217. gradioApp().querySelectorAll('.cnet-unit-tab').forEach(tab => {
  218. if (cnetAllUnits.has(tab)) return;
  219. cnetAllUnits.set(tab, new ControlNetUnitTab(tab));
  220. });
  221. function getActiveUnitCount(checkboxes) {
  222. let activeUnitCount = 0;
  223. for (const checkbox of checkboxes) {
  224. if (checkbox.checked)
  225. activeUnitCount++;
  226. }
  227. return activeUnitCount;
  228. }
  229. gradioApp().querySelectorAll('#controlnet').forEach(accordion => {
  230. if (cnetAllAccordions.has(accordion)) return;
  231. const checkboxes = accordion.querySelectorAll('.cnet-unit-enabled input');
  232. if (!checkboxes) return;
  233. const span = accordion.querySelector('.label-wrap span');
  234. checkboxes.forEach(checkbox => {
  235. checkbox.addEventListener('change', () => {
  236. // Remove existing badge.
  237. if (span.childNodes.length !== 1) {
  238. span.removeChild(span.lastChild);
  239. }
  240. // Add new badge if necessary.
  241. const activeUnitCount = getActiveUnitCount(checkboxes);
  242. if (activeUnitCount > 0) {
  243. const div = document.createElement('div');
  244. div.classList.add('cnet-badge');
  245. div.classList.add('primary');
  246. div.innerHTML = `${activeUnitCount} unit${activeUnitCount > 1 ? 's' : ''}`;
  247. span.appendChild(div);
  248. }
  249. });
  250. });
  251. cnetAllAccordions.add(accordion);
  252. });
  253. });
  254. })();