mirror of
https://github.com/vladmandic/automatic
synced 2026-09-20 01:31:13 +02:00
add en details view - phase one
This commit is contained in:
@@ -9,6 +9,8 @@
|
||||
any ui state including state of open/closed menus can be saved as default!
|
||||
see *System -> User interface -> Set menu states*
|
||||
- **Extra networks**:
|
||||
- new details interface to view and save data about extra networks
|
||||
main ui now has a single button on each en to trigger details view
|
||||
- faster search, ability to show/hide/sort networks
|
||||
- refactored subfolder handling
|
||||
*note*: this will trigger model hash recaclulation on first model use
|
||||
|
||||
@@ -94,6 +94,7 @@
|
||||
{"id":"","label":"save","localized":"","hint":"Save image"},
|
||||
{"id":"","label":"zip","localized":"","hint":"Create zip archive from images"},
|
||||
{"id":"","label":"delete","localized":"","hint":"Delete image"},
|
||||
{"id":"","label":"replace","localized":"","hint":"Replace image"},
|
||||
{"id":"","label":"➠ text","localized":"","hint":"Transfer image to text interface"},
|
||||
{"id":"","label":"➠ image","localized":"","hint":"Transfer image to image interface"},
|
||||
{"id":"","label":"➠ inpaint","localized":"","hint":"Transfer image to inpaint interface"},
|
||||
|
||||
+143
-137
@@ -1,34 +1,42 @@
|
||||
let globalPopup = null;
|
||||
let globalPopupInner = null;
|
||||
let previousCard = null;
|
||||
const activePromptTextarea = {};
|
||||
const re_extranet = /<([^:]+:[^:]+):[\d\.]+>/;
|
||||
const re_extranet_g = /\s+<([^:]+:[^:]+):[\d\.]+>/g;
|
||||
|
||||
const getENActiveTab = () => gradioApp().getElementById('tab_txt2img').style.display === 'block' ? 'txt2img' : 'img2img';
|
||||
// helpers
|
||||
|
||||
function requestGet(url, data, handler, errorHandler) {
|
||||
const requestGet = (url, data, handler) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
const args = Object.keys(data).map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(data[k])}`).join('&');
|
||||
xhr.open('GET', `${url}?${args}`, true);
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
const js = JSON.parse(xhr.responseText);
|
||||
handler(js);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
errorHandler();
|
||||
}
|
||||
} else {
|
||||
errorHandler();
|
||||
}
|
||||
if (xhr.status === 200) handler(JSON.parse(xhr.responseText));
|
||||
else console.error(`Request: url=${url} status=${xhr.status} err`);
|
||||
}
|
||||
};
|
||||
const js = JSON.stringify(data);
|
||||
xhr.send(js);
|
||||
}
|
||||
xhr.send(JSON.stringify(data));
|
||||
};
|
||||
|
||||
const getENActiveTab = () => gradioApp().getElementById('tab_txt2img').style.display === 'block' ? 'txt2img' : 'img2img';
|
||||
|
||||
const getENActivePage = () => {
|
||||
const tabname = getENActiveTab();
|
||||
const page = gradioApp().querySelector(`#${tabname}_extra_networks > .tabs > .tab-nav > .selected`);
|
||||
return page ? page.innerText : '';
|
||||
};
|
||||
|
||||
const setENState = (state) => {
|
||||
if (!state) return;
|
||||
state.tab = getENActiveTab();
|
||||
state.page = getENActivePage();
|
||||
log('setENState', state);
|
||||
const el = gradioApp().querySelector(`#${state.tab}_extra_state > label > textarea`);
|
||||
el.value = JSON.stringify(state);
|
||||
updateInput(el);
|
||||
};
|
||||
|
||||
// popup
|
||||
|
||||
let globalPopup = null;
|
||||
let globalPopupInner = null;
|
||||
|
||||
function popup(contents) {
|
||||
if (!globalPopup) {
|
||||
@@ -51,6 +59,45 @@ function popup(contents) {
|
||||
globalPopup.style.display = 'flex';
|
||||
}
|
||||
|
||||
// methods
|
||||
|
||||
function showCardDetails(event) {
|
||||
const tabname = getENActiveTab();
|
||||
setENState({ op: 'showCardDetails' });
|
||||
const btn = gradioApp().getElementById(`${tabname}_extra_details_btn`);
|
||||
btn.click();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
function getCardDetails(...args) {
|
||||
const el = event?.target?.parentElement?.parentElement?.parentElement;
|
||||
if (!el?.classList?.contains('card')) return [...args];
|
||||
const tabname = getENActiveTab();
|
||||
setENState({ op: 'getCardDetails', item: el.dataset.name });
|
||||
return [...args];
|
||||
}
|
||||
|
||||
function saveCardDescription(event) {
|
||||
const el = event?.target?.parentElement?.parentElement?.parentElement;
|
||||
if (!el?.classList?.contains('card')) return;
|
||||
const tabname = getENActiveTab();
|
||||
const description = gradioApp().getElementById(`${tabname}_description`)?.children[0].children[1].value;
|
||||
el.dataset.description = description;
|
||||
setENState({ op: 'saveCardDescription', item: el.dataset.name, description });
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
function saveCardPreview(event) {
|
||||
const el = event?.target?.parentElement?.parentElement?.parentElement;
|
||||
if (!el?.classList?.contains('card')) return;
|
||||
const index = selected_gallery_index();
|
||||
setENState({ op: 'saveCardPreview', item: el.dataset.name, index });
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
function readCardMetadata(event, extraPage, cardName) {
|
||||
requestGet('./sd_extra_networks/metadata', { page: extraPage, item: cardName }, (data) => {
|
||||
if (data?.metadata) {
|
||||
@@ -60,7 +107,7 @@ function readCardMetadata(event, extraPage, cardName) {
|
||||
elem.textContent = data.metadata;
|
||||
popup(elem);
|
||||
}
|
||||
}, () => { });
|
||||
});
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
@@ -88,54 +135,14 @@ function readCardTags(el, tags) {
|
||||
}
|
||||
}
|
||||
|
||||
function readCardInformation(event, extraPage, cardName) {
|
||||
requestGet('./sd_extra_networks/info', { page: extraPage, item: cardName }, (data) => {
|
||||
if (data?.info && (typeof (data?.info) === 'string')) {
|
||||
const elem = document.createElement('pre');
|
||||
elem.classList.add('popup-metadata');
|
||||
elem.textContent = data.info;
|
||||
popup(elem);
|
||||
}
|
||||
}, () => { });
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
function readCardDescription(filename, descript) {
|
||||
const tabname = getENActiveTab();
|
||||
const fn = gradioApp().querySelector(`#${tabname}_description_filename > label > textarea`);
|
||||
const description = gradioApp().querySelector(`#${tabname}_description > label > textarea`);
|
||||
fn.value = filename;
|
||||
description.value = descript?.trim() || '';
|
||||
description.focus();
|
||||
updateInput(fn);
|
||||
updateInput(description);
|
||||
}
|
||||
|
||||
function saveCardPreview(event) {
|
||||
const tabname = getENActiveTab();
|
||||
const textarea = gradioApp().querySelector(`#${tabname}_preview_filename > label > textarea`);
|
||||
const button = gradioApp().getElementById(`${tabname}_save_preview`);
|
||||
const el = event?.target?.parentElement?.parentElement?.parentElement;
|
||||
if (el?.classList?.contains('card')) {
|
||||
textarea.value = el.dataset.filename;
|
||||
updateInput(textarea);
|
||||
button.click();
|
||||
}
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
function saveCardDescription(event) {
|
||||
const tabname = getENActiveTab();
|
||||
const el = event?.target?.parentElement?.parentElement?.parentElement;
|
||||
if (el?.classList?.contains('card')) el.dataset.description = gradioApp().getElementById(`${tabname}_description`)?.children[0].children[1].value;
|
||||
const button = gradioApp().getElementById(`${tabname}_save_description`);
|
||||
button.click();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
async function filterExtraNetworksForTab(tabname, searchTerm) {
|
||||
let found = 0;
|
||||
let items = 0;
|
||||
@@ -161,6 +168,79 @@ async function filterExtraNetworksForTab(tabname, searchTerm) {
|
||||
else log(`filterExtraNetworks: text=all items=${items} time=${Math.round(1000 * (t1 - t0)) / 1000000}`);
|
||||
}
|
||||
|
||||
function tryToRemoveExtraNetworkFromPrompt(textarea, text) {
|
||||
const re_extranet = /<([^:]+:[^:]+):[\d\.]+>/;
|
||||
const re_extranet_g = /\s+<([^:]+:[^:]+):[\d\.]+>/g;
|
||||
let m = text.match(re_extranet);
|
||||
let replaced = false;
|
||||
let newTextareaText;
|
||||
if (m) {
|
||||
const partToSearch = m[1];
|
||||
newTextareaText = textarea.value.replaceAll(re_extranet_g, (found) => {
|
||||
m = found.match(re_extranet);
|
||||
if (m[1] === partToSearch) {
|
||||
replaced = true;
|
||||
return '';
|
||||
}
|
||||
return found;
|
||||
});
|
||||
} else {
|
||||
newTextareaText = textarea.value.replaceAll(new RegExp(text, 'g'), (found) => {
|
||||
if (found === text) {
|
||||
replaced = true;
|
||||
return '';
|
||||
}
|
||||
return found;
|
||||
});
|
||||
}
|
||||
if (replaced) {
|
||||
textarea.value = newTextareaText;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function refreshExtraNetworks(tabname) {
|
||||
log('refreshExtraNetworks', tabname, gradioApp().querySelector(`#${tabname}_extra_networks textarea`)?.value);
|
||||
gradioApp().querySelector(`#${tabname}_extra_networks textarea`)?.dispatchEvent(new Event('input'));
|
||||
}
|
||||
|
||||
function cardClicked(textToAdd, allowNegativePrompt) {
|
||||
const tabname = getENActiveTab();
|
||||
const textarea = allowNegativePrompt ? activePromptTextarea[tabname] : gradioApp().querySelector(`#${tabname}_prompt > label > textarea`);
|
||||
if (textarea.value.indexOf(textToAdd) !== -1) textarea.value = textarea.value.replace(textToAdd, '');
|
||||
else textarea.value += textToAdd;
|
||||
updateInput(textarea);
|
||||
}
|
||||
|
||||
function extraNetworksSearchButton(event) {
|
||||
const tabname = getENActiveTab();
|
||||
const searchTextarea = gradioApp().querySelector(`#${tabname}_extra_tabs > div > div > textarea`);
|
||||
const button = event.target;
|
||||
const text = button.classList.contains('search-all') ? '' : `${button.textContent.trim()}/`;
|
||||
searchTextarea.value = text;
|
||||
updateInput(searchTextarea);
|
||||
}
|
||||
|
||||
let desiredStyle = '';
|
||||
function selectStyle(name) {
|
||||
desiredStyle = name;
|
||||
const tabname = getENActiveTab();
|
||||
const button = gradioApp().querySelector(`#${tabname}_styles_select`);
|
||||
button.click();
|
||||
}
|
||||
|
||||
function applyStyles(styles) {
|
||||
let newStyles = [];
|
||||
if (styles) newStyles = Array.isArray(styles) ? styles : [styles];
|
||||
const index = newStyles.indexOf(desiredStyle);
|
||||
if (index > -1) newStyles.splice(index, 1);
|
||||
else newStyles.push(desiredStyle);
|
||||
return newStyles.join('|');
|
||||
}
|
||||
|
||||
// init
|
||||
|
||||
function setupExtraNetworksForTab(tabname) {
|
||||
gradioApp().querySelector(`#${tabname}_extra_tabs`).classList.add('extra-networks');
|
||||
const tabs = gradioApp().querySelector(`#${tabname}_extra_tabs > div`);
|
||||
@@ -188,6 +268,7 @@ function setupExtraNetworksForTab(tabname) {
|
||||
});
|
||||
|
||||
let hoverTimer = null;
|
||||
let previousCard = null;
|
||||
gradioApp().getElementById(`${tabname}_extra_tabs`).onmouseover = (e) => {
|
||||
const el = e.target.closest('.card'); // bubble-up to card
|
||||
if (!el || (el.title === previousCard)) return;
|
||||
@@ -261,79 +342,4 @@ function setupExtraNetworks() {
|
||||
log('initExtraNetworks');
|
||||
}
|
||||
|
||||
function tryToRemoveExtraNetworkFromPrompt(textarea, text) {
|
||||
let m = text.match(re_extranet);
|
||||
let replaced = false;
|
||||
let newTextareaText;
|
||||
if (m) {
|
||||
const partToSearch = m[1];
|
||||
newTextareaText = textarea.value.replaceAll(re_extranet_g, (found) => {
|
||||
m = found.match(re_extranet);
|
||||
if (m[1] === partToSearch) {
|
||||
replaced = true;
|
||||
return '';
|
||||
}
|
||||
return found;
|
||||
});
|
||||
} else {
|
||||
newTextareaText = textarea.value.replaceAll(new RegExp(text, 'g'), (found) => {
|
||||
if (found === text) {
|
||||
replaced = true;
|
||||
return '';
|
||||
}
|
||||
return found;
|
||||
});
|
||||
}
|
||||
if (replaced) {
|
||||
textarea.value = newTextareaText;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function refreshExtraNetworks(tabname) {
|
||||
console.log('refreshExtraNetworks', tabname, gradioApp().querySelector(`#${tabname}_extra_networks textarea`)?.value);
|
||||
gradioApp().querySelector(`#${tabname}_extra_networks textarea`)?.dispatchEvent(new Event('input'));
|
||||
}
|
||||
|
||||
function cardClicked(textToAdd, allowNegativePrompt) {
|
||||
const tabname = getENActiveTab();
|
||||
const textarea = allowNegativePrompt ? activePromptTextarea[tabname] : gradioApp().querySelector(`#${tabname}_prompt > label > textarea`);
|
||||
if (textarea.value.indexOf(textToAdd) !== -1) textarea.value = textarea.value.replace(textToAdd, '');
|
||||
else textarea.value += textToAdd;
|
||||
updateInput(textarea);
|
||||
}
|
||||
|
||||
function extraNetworksSearchButton(event) {
|
||||
const tabname = getENActiveTab();
|
||||
const searchTextarea = gradioApp().querySelector(`#${tabname}_extra_tabs > div > div > textarea`);
|
||||
const button = event.target;
|
||||
const text = button.classList.contains('search-all') ? '' : `${button.textContent.trim()}/`;
|
||||
searchTextarea.value = text;
|
||||
updateInput(searchTextarea);
|
||||
}
|
||||
|
||||
function getENActivePage() {
|
||||
const tabname = getENActiveTab();
|
||||
const page = gradioApp().querySelector(`#${tabname}_extra_networks > .tabs > .tab-nav > .selected`);
|
||||
return page ? page.innerText : '';
|
||||
}
|
||||
|
||||
let desiredStyle = '';
|
||||
function selectStyle(name) {
|
||||
desiredStyle = name;
|
||||
const tabname = getENActiveTab();
|
||||
const button = gradioApp().querySelector(`#${tabname}_styles_select`);
|
||||
button.click();
|
||||
}
|
||||
|
||||
function applyStyles(styles) {
|
||||
let newStyles = [];
|
||||
if (styles) newStyles = Array.isArray(styles) ? styles : [styles];
|
||||
const index = newStyles.indexOf(desiredStyle);
|
||||
if (index > -1) newStyles.splice(index, 1);
|
||||
else newStyles.push(desiredStyle);
|
||||
return newStyles.join('|');
|
||||
}
|
||||
|
||||
onUiLoaded(setupExtraNetworks);
|
||||
|
||||
+145
-145
@@ -38,7 +38,7 @@
|
||||
|
||||
html { font-size: var(--font-size); }
|
||||
body, button, input, select, textarea { font-family: var(--font);}
|
||||
button { font-size: 1.2rem; max-width: 400px; }
|
||||
button { font-size: 1.2rem; max-width: 400px; --button-large-radius: var(--radius-lg) }
|
||||
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; }
|
||||
@@ -128,137 +128,27 @@ 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);
|
||||
--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-background-fill: None;
|
||||
--block-border-color: var(--border-color-primary);
|
||||
--block-border-width: 1px;
|
||||
--block-info-text-color: var(--body-text-color-subdued);
|
||||
--block-info-text-size: var(--text-sm);
|
||||
--block-info-text-weight: 400;
|
||||
--block-label-background-fill: var(--background-fill-secondary);
|
||||
--block-label-border-color: var(--border-color-primary);
|
||||
--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-color: var(--neutral-200);
|
||||
--block-label-text-size: var(--text-sm);
|
||||
--block-label-text-weight: 400;
|
||||
--block-padding: var(--spacing-xl) calc(var(--spacing-xl) + 2px);
|
||||
@@ -271,44 +161,154 @@ svg.feather.feather-image, .feather .feather-image { display: none }
|
||||
--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);
|
||||
--body-background-fill: var(--background-color);
|
||||
--body-text-size: var(--text-md);
|
||||
--body-text-weight: 400;
|
||||
--border-color-accent: var(--background-color);
|
||||
--border-color-primary: var(--background-color);
|
||||
--button-border-width: var(--input-border-width);
|
||||
--button-cancel-background-fill-hover: linear-gradient(to bottom right, #dc2626, #dc2626);
|
||||
--button-cancel-background-fill: linear-gradient(to bottom right, #dc2626, #b91c1c);
|
||||
--button-cancel-border-color-hover: var(--button-cancel-border-color);
|
||||
--button-cancel-border-color: #dc2626;
|
||||
--button-cancel-text-color-hover: var(--button-cancel-text-color);
|
||||
--button-cancel-text-color: white;
|
||||
--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-primary-background-fill-hover: linear-gradient(to bottom right, var(--primary-500), var(--primary-300));
|
||||
--button-primary-background-fill: linear-gradient(to bottom right, var(--primary-500), var(--primary-800));
|
||||
--button-primary-border-color-hover: var(--button-primary-border-color);
|
||||
--button-primary-border-color: var(--primary-500);
|
||||
--button-primary-text-color-hover: var(--button-primary-text-color);
|
||||
--button-primary-text-color: white;
|
||||
--button-secondary-border-color-hover: var(--button-secondary-border-color);
|
||||
--button-secondary-border-color: var(--neutral-600);
|
||||
--button-secondary-text-color-hover: var(--button-secondary-text-color);
|
||||
--button-shadow-active: 1px 1px 4px 0px #555555;
|
||||
--button-shadow-hover: 1px 1px 4px 0px #555555;
|
||||
--button-shadow: 4px 4px 4px 0px #333333;
|
||||
--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;
|
||||
--checkbox-background-color-focus: var(--checkbox-background-color);
|
||||
--checkbox-background-color-hover: var(--checkbox-background-color);
|
||||
--checkbox-background-color-selected: var(--primary-500);
|
||||
--checkbox-background-color: var(--neutral-500);
|
||||
--checkbox-border-color-focus: var(--secondary-500);
|
||||
--checkbox-border-color-hover: var(--neutral-600);
|
||||
--checkbox-border-color-selected: var(--primary-600);
|
||||
--checkbox-border-color: transparent;
|
||||
--checkbox-border-radius: var(--radius-sm);
|
||||
--checkbox-border-width: var(--input-border-width);
|
||||
--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");
|
||||
--checkbox-label-background-fill-hover: None;
|
||||
--checkbox-label-background-fill-selected: var(--checkbox-label-background-fill);
|
||||
--checkbox-label-background-fill: None;
|
||||
--checkbox-label-border-color-hover: var(--checkbox-label-border-color);
|
||||
--checkbox-label-border-color: var(--border-color-primary);
|
||||
--checkbox-label-border-width: var(--input-border-width);
|
||||
--checkbox-label-gap: 2px;
|
||||
--checkbox-label-padding: var(--spacing-md);
|
||||
--checkbox-label-shadow: var(--shadow-drop);
|
||||
--checkbox-label-text-color-selected: var(--checkbox-label-text-color);
|
||||
--checkbox-label-text-color: var(--body-text-color);
|
||||
--checkbox-label-text-size: var(--text-md);
|
||||
--checkbox-label-text-weight: 400;
|
||||
--checkbox-shadow: var(--input-shadow);
|
||||
--color-accent-soft: var(--neutral-700);
|
||||
--color-accent: var(--primary-500);
|
||||
--container-radius: var(--radius-lg);
|
||||
--embed-radius: var(--radius-lg);
|
||||
--error_border_width: None;
|
||||
--error-background-fill: var(--background-fill-primary);
|
||||
--error-border-color: var(--border-color-primary);
|
||||
--error-border-width: 1px;
|
||||
--error-text-color: #ef4444;
|
||||
--form-gap-width: 1px;
|
||||
--input_border_width: None;
|
||||
--input-background-fill-focus: var(--secondary-600);
|
||||
--input-background-fill-hover: var(--input-background-fill);
|
||||
--input-border-color-focus: var(--neutral-700);
|
||||
--input-border-color-hover: var(--input-border-color);
|
||||
--input-border-color: var(--border-color-primary);
|
||||
--input-border-width: 0;
|
||||
--input-placeholder-color: var(--neutral-500);
|
||||
--input-radius: var(--radius-lg);
|
||||
--input-shadow-focus: 2px 2px 2px 2px #111111;
|
||||
--input-text-size: var(--text-md);
|
||||
--input-text-weight: 400;
|
||||
--layout-gap: var(--spacing-xxl);
|
||||
--link-text-color-active: var(--secondary-500);
|
||||
--link-text-color-hover: var(--secondary-400);
|
||||
--link-text-color-visited: var(--secondary-600);
|
||||
--link-text-color: var(--secondary-500);
|
||||
--loader_color: None;
|
||||
--loader-color: var(--color-accent);
|
||||
--neutral-100: #e0e0e0;
|
||||
--neutral-200: #d0d0d0;
|
||||
--neutral-300: #b0b0b0;
|
||||
--neutral-400: #909090;
|
||||
--neutral-50: #f0f0f0;
|
||||
--neutral-500: #707070;
|
||||
--neutral-600: #606060;
|
||||
--neutral-700: #404040;
|
||||
--neutral-800: #333333;
|
||||
--neutral-900: #111827;
|
||||
--neutral-950: #0b0f19;
|
||||
--panel_border_width: None;
|
||||
--panel-background-fill: var(--background-fill-secondary);
|
||||
--panel-border-color: var(--border-color-primary);
|
||||
--panel-border-width: 0;
|
||||
--prose-header-text-weight: 600;
|
||||
--prose-text-size: var(--text-md);
|
||||
--prose-text-weight: 400;
|
||||
--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");
|
||||
--radius-md: 0;
|
||||
--radius-xl: 0;
|
||||
--radius-xs: 0;
|
||||
--radius-xxl: 0;
|
||||
--radius-xxs: 0;
|
||||
--secondary-100: #dbeafe;
|
||||
--secondary-200: #bfdbfe;
|
||||
--secondary-300: #93c5fd;
|
||||
--secondary-400: #60a5fa;
|
||||
--secondary-50: #eff6ff;
|
||||
--secondary-500: #3b82f6;
|
||||
--secondary-600: #2563eb;
|
||||
--secondary-700: #1d4ed8;
|
||||
--secondary-800: #1e40af;
|
||||
--secondary-900: #1e3a8a;
|
||||
--secondary-950: #1d3660;
|
||||
--section-header-text-size: var(--text-md);
|
||||
--section-header-text-weight: 400;
|
||||
--shadow-drop-lg: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
--shadow-drop: 0;
|
||||
--shadow-inset: rgba(0,0,0,0.05) 0px 2px 4px 0px inset;
|
||||
--shadow-spread: 1px;
|
||||
--size-14: 64px;
|
||||
--size-9: 64px;
|
||||
--slider_color: None;
|
||||
--slider-color: ;
|
||||
--spacing-lg: 8px;
|
||||
--spacing-sm: 4px;
|
||||
--spacing-xl: 10px;
|
||||
--spacing-xs: 2px;
|
||||
--spacing-xxs: 1px;
|
||||
--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-radius: var(--radius-lg);
|
||||
--table-row-focus: var(--color-accent-soft);
|
||||
--text-lg: 16px;
|
||||
--text-md: 14px;
|
||||
--text-sm: 12px;
|
||||
--text-xl: 22px;
|
||||
--text-xs: 10px;
|
||||
--text-xxl: 26px;
|
||||
--text-xxs: 9px;
|
||||
}
|
||||
|
||||
+17
-24
@@ -1,7 +1,6 @@
|
||||
:root, .dark{ --checkbox-label-gap: 0.25em 0.1em; --section-header-text-size: 12pt; --block-background-fill: transparent;}
|
||||
a { font-weight: bold; cursor: pointer; }
|
||||
h2 { margin-top: 1em !important; font-size: 1.4em !important; }
|
||||
table { overflow-x: auto !important; overflow-y: auto !important; }
|
||||
div.gradio-container{ max-width: unset !important; padding: 8px !important; }
|
||||
div.tabitem { padding: 0 !important; }
|
||||
div.form { border-width: 0; box-shadow: none; background: transparent; overflow: visible; gap: 0.5em 1em; flex-grow: 1 !important; }
|
||||
@@ -35,7 +34,9 @@ div.gradio-html.min{ min-height: 0; }
|
||||
.small-accordion .label-wrap .icon { margin-right: 1.6em; margin-left: 0.6em; color: var(--button-primary-border-color); }
|
||||
.hidden { display: none; }
|
||||
footer { display: none; }
|
||||
td { border-bottom: none !important; }
|
||||
table { overflow-x: auto !important; overflow-y: auto !important; }
|
||||
td { border-bottom: none !important; padding: 0.1em 0.5em !important; }
|
||||
tr { border-bottom: none !important; padding: 0.1em 0.5em !important; }
|
||||
|
||||
/* general styled components */
|
||||
.gradio-button.tool { max-width: min-content; min-width: min-content !important; align-self: end; font-size: 1.4em; color: var(--body-text-color) !important; }
|
||||
@@ -248,39 +249,31 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt
|
||||
.extra-network-cards .card .preview { box-shadow: var(--button-shadow); min-height: 30px; }
|
||||
.extra-network-cards .card:hover .overlay { background: rgba(0, 0, 0, 0.40); }
|
||||
.extra-network-cards .card:hover .preview { box-shadow: none; filter: grayscale(100%); }
|
||||
.extra-network-cards .card:hover .overlay { background: rgba(0, 0, 0, 0.40); }
|
||||
.extra-network-cards .card .overlay .tags { margin: 4px; display: none; overflow-wrap: break-word; }
|
||||
.extra-network-cards .card .overlay .tag { padding: 2px; margin: 2px; background: var(--neutral-700); cursor: pointer; display: inline-block; }
|
||||
.extra-network-cards .card .overlay .actions { font-size: 2.2em; display: none; text-align-last: center; cursor: pointer; font-variant: unicase; height: 0.8em }
|
||||
.extra-network-cards .card .overlay .actions > span { padding: 4px; }
|
||||
.extra-network-cards .card .overlay .description { line-break: anywhere; display: none; color: white; }
|
||||
.extra-network-cards .card:hover .overlay .actions { display: block; }
|
||||
.extra-network-cards .card:hover .overlay .tags { display: block; }
|
||||
.extra-network-cards .card:hover .overlay .description { display: block; }
|
||||
|
||||
|
||||
#txt2img_description, #img2img_description { max-height: 63px; overflow-y: auto !important; }
|
||||
#txt2img_description > label > textarea, #img2img_description > label > textarea { font-size: 0.9em }
|
||||
|
||||
.extra-network-cards .card .info {
|
||||
position: absolute;
|
||||
font-size: 1.5em;
|
||||
top: 0.1em;
|
||||
right: 0.1em;
|
||||
background: var(--button-primary-border-color);
|
||||
width: 1.3em;
|
||||
text-align: center;
|
||||
border-radius: var(--block-radius);
|
||||
height: 1.3em;
|
||||
line-height: 1.2em;
|
||||
cursor: pointer;
|
||||
visibility: hidden;
|
||||
z-index: 100;
|
||||
}
|
||||
.extra-network-cards .card:hover .info { visibility: visible; }
|
||||
|
||||
.extra-networks_details {
|
||||
#txt2img_extra_details, #img2img_extra_details {
|
||||
position: fixed;
|
||||
bottom: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 50%);
|
||||
padding: 1em;
|
||||
padding: 0.8em;
|
||||
border: var(--block-border-width) solid var(--highlight-color) !important;
|
||||
z-index: 100;
|
||||
box-shadow: var(--button-shadow);
|
||||
width: 80vw;
|
||||
display: none !important;
|
||||
}
|
||||
#txt2img_extra_details > div, #img2img_extra_details > div { overflow-y: auto; min-height: 40vh; max-height: 80vh; align-self: flex-start; }
|
||||
#txt2img_extra_details td:first-child, #img2img_extra_details td:first-child { font-weight: bold; }
|
||||
|
||||
/* controlnet
|
||||
.controlnet_control_type .controlnet_control_type_filter_group .wrap:last-of-type { display: grid; grid-auto-flow: row; grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
||||
|
||||
+192
-156
@@ -5,13 +5,14 @@ import html
|
||||
import os.path
|
||||
import urllib.parse
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from types import SimpleNamespace
|
||||
from pathlib import Path
|
||||
from collections import OrderedDict
|
||||
import gradio as gr
|
||||
from PIL import Image
|
||||
from starlette.responses import FileResponse, JSONResponse
|
||||
from modules import shared, scripts, modelloader
|
||||
from modules.generation_parameters_copypaste import image_from_url_text
|
||||
from modules.ui_components import ToolButton
|
||||
import modules.ui_symbols as symbols
|
||||
|
||||
@@ -28,10 +29,7 @@ def listdir(path):
|
||||
if path in dir_cache and os.path.getmtime(path) == dir_cache[path][0]:
|
||||
return dir_cache[path][1]
|
||||
else:
|
||||
dir_cache[path] = (
|
||||
os.path.getmtime(path),
|
||||
[os.path.join(path, f) for f in os.listdir(path)]
|
||||
)
|
||||
dir_cache[path] = (os.path.getmtime(path), [os.path.join(path, f) for f in os.listdir(path)])
|
||||
return dir_cache[path][1]
|
||||
|
||||
|
||||
@@ -45,41 +43,39 @@ def register_page(page):
|
||||
allowed_dirs.append(os.path.abspath(folder))
|
||||
|
||||
|
||||
def fetch_file(filename: str = ""):
|
||||
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/'):
|
||||
def init_api(app):
|
||||
def fetch_file(filename: str = ""):
|
||||
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(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 {filename}: not an image file"}, status_code=403)
|
||||
return FileResponse(filename, headers={"Accept-Ranges": "bytes"})
|
||||
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 {filename}: not an image file"}, status_code=403)
|
||||
return FileResponse(filename, headers={"Accept-Ranges": "bytes"})
|
||||
|
||||
def get_metadata(page: str = "", item: str = ""):
|
||||
page = next(iter([x for x in shared.extra_networks if x.name == page]), None)
|
||||
if page is None:
|
||||
return JSONResponse({ 'metadata': 'none' })
|
||||
metadata = page.metadata.get(item, 'none')
|
||||
if metadata is None:
|
||||
metadata = ''
|
||||
shared.log.debug(f"Extra networks metadata: page='{page}' item={item} len={len(metadata)}")
|
||||
return JSONResponse({"metadata": metadata})
|
||||
|
||||
|
||||
def get_metadata(page: str = "", item: str = ""):
|
||||
page = next(iter([x for x in shared.extra_networks if x.name == page]), None)
|
||||
if page is None:
|
||||
return JSONResponse({ 'metadata': 'none' })
|
||||
metadata = page.metadata.get(item, 'none')
|
||||
if metadata is None:
|
||||
metadata = ''
|
||||
shared.log.debug(f"Extra networks metadata: page='{page}' item={item} len={len(metadata)}")
|
||||
return JSONResponse({"metadata": metadata})
|
||||
def get_info(page: str = "", item: str = ""):
|
||||
page = next(iter([x for x in shared.extra_networks if x.name == page]), None)
|
||||
if page is None:
|
||||
return JSONResponse({ 'info': 'none' })
|
||||
info = page.info.get(item, 'none')
|
||||
if info is None:
|
||||
info = ''
|
||||
shared.log.debug(f"Extra networks info: page='{page}' item={item} len={len(info)}")
|
||||
return JSONResponse({"info": info})
|
||||
|
||||
|
||||
def get_info(page: str = "", item: str = ""):
|
||||
page = next(iter([x for x in shared.extra_networks if x.name == page]), None)
|
||||
if page is None:
|
||||
return JSONResponse({ 'info': 'none' })
|
||||
info = page.info.get(item, 'none')
|
||||
if info is None:
|
||||
info = ''
|
||||
shared.log.debug(f"Extra networks info: page='{page}' item={item} len={len(info)}")
|
||||
return JSONResponse({"info": info})
|
||||
|
||||
|
||||
def add_pages_to_demo(app):
|
||||
app.add_api_route("/sd_extra_networks/thumb", fetch_file, methods=["GET"])
|
||||
app.add_api_route("/sd_extra_networks/metadata", get_metadata, methods=["GET"])
|
||||
app.add_api_route("/sd_extra_networks/info", get_info, methods=["GET"])
|
||||
@@ -98,16 +94,16 @@ class ExtraNetworksPage:
|
||||
self.refresh_time = None
|
||||
# class additional is to keep old extensions happy
|
||||
self.card = '''
|
||||
<div class='card' onclick={card_click} title='{title}' data-filename='{local_preview}' data-description='{description}' data-tags='{tags}'>
|
||||
<div class='card' onclick={card_click} title='{name}' data-name='{name}' data-filename='{local_preview}' data-description='{description}' data-tags='{tags}'>
|
||||
<div class='overlay'>
|
||||
<span style="display:none" class='search_term'>{search_term}</span>
|
||||
<div class='name'>{name}</div>
|
||||
<div class='name'>{title}</div>
|
||||
<div class='tags'></div>
|
||||
<div class='actions'>
|
||||
<div class='additional'><ul></ul></div>
|
||||
<span title="Save current image as preview image" onclick={card_save_preview}>⏺️</span>
|
||||
<span title="Save current description" onclick={card_save_desc}>🛅</span>
|
||||
{card_extra}
|
||||
<span title="Save current image as preview image" onclick="saveCardPreview(event)">⏺️</span>
|
||||
<span title="Save current description" onclick="saveCardDescription(event)">🛅</span>
|
||||
<span title="Get details" onclick="showCardDetails(event)">🛈</span>
|
||||
</div>
|
||||
</div>
|
||||
<img class='preview' src='{preview}' style='width: {width}px; height: {height}px; object-fit: {fit}' loading='lazy'></img>
|
||||
@@ -223,11 +219,11 @@ class ExtraNetworksPage:
|
||||
return []
|
||||
|
||||
def create_html(self, item, tabname):
|
||||
# try:
|
||||
try:
|
||||
args = {
|
||||
"tabname": json.dumps(tabname),
|
||||
"name": item["name"].replace('_', ' '),
|
||||
"title": item["name"],
|
||||
"name": item["name"],
|
||||
"title": item["name"].replace('_', ' '),
|
||||
"tags": '|'.join([item.get("tags")] if isinstance(item.get("tags", {}), str) else list(item.get("tags", {}).keys())),
|
||||
"preview": html.escape(item.get("preview", self.link_preview('html/card-no-preview.png'))),
|
||||
"width": shared.opts.extra_networks_card_size,
|
||||
@@ -238,25 +234,23 @@ class ExtraNetworksPage:
|
||||
"description": item.get("description") or "",
|
||||
"local_preview": item.get("local_preview"),
|
||||
"card_click": item.get("onclick", '"' + html.escape(f'return cardClicked({item.get("prompt", None)}, {"true" if self.allow_negative_prompt else "false"})') + '"'),
|
||||
"card_save_preview": '"' + html.escape('return saveCardPreview(event)') + '"',
|
||||
"card_save_desc": '"' + html.escape('return saveCardDescription(event)') + '"',
|
||||
"card_extra": "",
|
||||
}
|
||||
metadata = item.get("metadata", None)
|
||||
if metadata is not None and len(metadata) > 0:
|
||||
card_read_meta = '"' + html.escape(f'return readCardMetadata(event, {json.dumps(self.name)}, {json.dumps(item["name"])})') + '"'
|
||||
args['card_extra'] += f'<span title="Read metadata" onclick={card_read_meta}>📘</span>'
|
||||
info = item.get("info", None)
|
||||
if info is not None and len(info) > 0:
|
||||
card_read_info = '"' + html.escape(f'return readCardInformation(event, {json.dumps(self.name)}, {json.dumps(item["name"])})') + '"'
|
||||
args['card_extra'] += f'<span title="Read info" onclick={card_read_info}>ℹ️</span>' # noqa
|
||||
alias = item.get("alias", None)
|
||||
if alias is not None:
|
||||
args['title'] += f'\nAlias: {alias}'
|
||||
return self.card.format(**args)
|
||||
# except Exception as e:
|
||||
# shared.log.error(f'Extra networks item error: page={tabname} item={item["name"]} {e}')
|
||||
# return ""
|
||||
except Exception as e:
|
||||
shared.log.error(f'Extra networks item error: page={tabname} item={item["name"]} {e}')
|
||||
return ""
|
||||
|
||||
def find_preview_file(self, path):
|
||||
preview_extensions = ["jpg", "jpeg", "png", "webp", "tiff", "jp2"]
|
||||
fn = os.path.splitext(path)[0]
|
||||
files = listdir(os.path.dirname(path))
|
||||
for file in [f'{fn}{mid}{ext}' for ext in preview_extensions for mid in ['.thumb.', '.preview.', '.']]:
|
||||
if file in files:
|
||||
return file
|
||||
return 'html/card-no-preview.png'
|
||||
|
||||
def find_preview(self, path):
|
||||
preview_extensions = ["jpg", "jpeg", "png", "webp", "tiff", "jp2"]
|
||||
@@ -295,42 +289,6 @@ class ExtraNetworksPage:
|
||||
pass
|
||||
return ''
|
||||
|
||||
def save_preview(self, index, images, filename):
|
||||
try:
|
||||
image = image_from_url_text(images[int(index)])
|
||||
except Exception as e:
|
||||
shared.log.error(f'Extra network save preview: {filename} {e}')
|
||||
return
|
||||
is_allowed = False
|
||||
for page in shared.extra_networks:
|
||||
if any(path_is_parent(x, filename) for x in page.allowed_directories_for_previews()):
|
||||
is_allowed = True
|
||||
break
|
||||
if not is_allowed:
|
||||
shared.log.error(f'Extra network save preview: {filename} not allowed')
|
||||
return
|
||||
if image.width > 512 or image.height > 512:
|
||||
image = image.convert('RGB')
|
||||
image.thumbnail((512, 512), Image.HAMMING)
|
||||
image.save(filename, quality=50)
|
||||
fn, _ext = os.path.splitext(filename)
|
||||
thumb = fn + '.thumb.jpg'
|
||||
if os.path.exists(thumb):
|
||||
shared.log.debug(f'Extra network delete thumbnail: {thumb}')
|
||||
os.remove(thumb)
|
||||
shared.log.info(f'Extra network save preview: {filename}')
|
||||
|
||||
def save_description(self, filename, desc):
|
||||
lastDotIndex = filename.rindex('.')
|
||||
filename = filename[0:lastDotIndex]+".txt"
|
||||
if desc != "":
|
||||
try:
|
||||
with open(filename, 'w', encoding='utf-8') as f:
|
||||
f.write(desc)
|
||||
shared.log.info(f'Extra network save description: {filename} {desc}')
|
||||
except Exception as e:
|
||||
shared.log.error(f'Extra network save description: {filename} {e}')
|
||||
|
||||
|
||||
def initialize():
|
||||
shared.extra_networks.clear()
|
||||
@@ -364,31 +322,128 @@ def get_pages():
|
||||
|
||||
class ExtraNetworksUi:
|
||||
def __init__(self):
|
||||
self.pages = None
|
||||
self.button_save_preview = None
|
||||
self.preview_target_filename = None
|
||||
self.button_save_description = None
|
||||
self.button_read_description = None
|
||||
self.description_target_filename = None
|
||||
self.description = None
|
||||
self.tags = None
|
||||
self.tabname = None
|
||||
self.search = None
|
||||
self.tabname: str = None
|
||||
self.pages: list(str) = None
|
||||
self.visible: gr.State = None
|
||||
self.state: gr.Textbox = None
|
||||
self.details: gr.Group = None
|
||||
self.tabs: gr.Tabs = None
|
||||
self.gallery: gr.Gallery = None
|
||||
self.description: gr.Textbox = None
|
||||
self.search: gr.Textbox = None
|
||||
self.button_details: gr.Button = None
|
||||
self.details_components: list = []
|
||||
|
||||
|
||||
def create_ui(container, button, tabname, skip_indexing = False):
|
||||
def create_ui(container, button_parent, tabname, skip_indexing = False):
|
||||
ui = ExtraNetworksUi()
|
||||
ui.pages = []
|
||||
ui.tabname = tabname
|
||||
with gr.Tabs(elem_id=tabname+"_extra_tabs"):
|
||||
ui.pages = []
|
||||
ui.state = gr.Textbox('{}', elem_id=tabname+"_extra_state", visible=False)
|
||||
ui.visible = gr.State(value=False) # pylint: disable=abstract-class-instantiated
|
||||
ui.details = gr.Group(elem_id=tabname+"_extra_details", visible=False)
|
||||
ui.tabs = gr.Tabs(elem_id=tabname+"_extra_tabs")
|
||||
ui.button_details = gr.Button('Details', elem_id=tabname+"_extra_details_btn", visible=False)
|
||||
state = {}
|
||||
|
||||
def get_item(state):
|
||||
if state is None or state.page is None or state.item is None:
|
||||
return None, None
|
||||
page = next(iter([x for x in get_pages() if x.title == state.page]), None)
|
||||
if page is None:
|
||||
return None, None
|
||||
item = next(iter([x for x in page.items if x["name"] == state.item]), None)
|
||||
if item is None:
|
||||
return page, None
|
||||
item = SimpleNamespace(**item)
|
||||
return page, item
|
||||
|
||||
# main event that is triggered when js updates state text field with json values, used to communicate js -> python
|
||||
def state_change(state_text):
|
||||
try:
|
||||
nonlocal state
|
||||
state = SimpleNamespace(**json.loads(state_text))
|
||||
except Exception as e:
|
||||
shared.log.error(f'Extra networks state error: {e}')
|
||||
return
|
||||
page, item = get_item(state)
|
||||
shared.log.debug(f'Extra network: op={state.op} page={page.title if page is not None else None} item={item.filename if item is not None else None}')
|
||||
|
||||
if state.op == 'getCardDetails':
|
||||
pass
|
||||
|
||||
if state.op == 'saveCardDescription':
|
||||
if item is None or state.description is None or len(state.description) == 0:
|
||||
return
|
||||
try:
|
||||
filename = os.path.splitext(item.filename)[1] + '.txt'
|
||||
with open(filename, 'w', encoding='utf-8') as f:
|
||||
f.write(state.description)
|
||||
shared.log.info(f'Extra network save: file="{filename}" description={state.description}')
|
||||
except Exception as e:
|
||||
shared.log.error(f'Extra network save: file="{filename}" {e}')
|
||||
|
||||
if state.op == 'saveCardPreview':
|
||||
if item is None or item.local_preview is None or len(item.local_preview) == 0:
|
||||
return
|
||||
try:
|
||||
images = list(ui.gallery.temp_files)
|
||||
if len(images) < 1:
|
||||
shared.log.error(f'Extra network save: preview="{item.local_preview}" no images')
|
||||
return
|
||||
image = Image.open(images[state.index])
|
||||
except Exception as e:
|
||||
shared.log.error(f'Extra network save: preview="{item.local_preview}" {e}')
|
||||
return
|
||||
if image.width > 512 or image.height > 512:
|
||||
image = image.convert('RGB')
|
||||
image.thumbnail((512, 512), Image.HAMMING)
|
||||
image.save(item.local_preview, quality=50)
|
||||
thumb = os.path.splitext(item.local_preview)[0] + '.thumb.jpg'
|
||||
if os.path.exists(thumb):
|
||||
shared.log.debug(f'Extra network delete: thumbnail="{thumb}"')
|
||||
os.remove(thumb)
|
||||
shared.log.info(f'Extra network save: preview="{item.local_preview}"')
|
||||
|
||||
def toggle_visibility(is_visible):
|
||||
is_visible = not is_visible
|
||||
return is_visible, gr.update(visible=is_visible), gr.update(variant=("secondary-down" if is_visible else "secondary"))
|
||||
|
||||
with ui.details:
|
||||
details_close = ToolButton(symbols.close, elem_id=tabname+"_extra_details_close")
|
||||
details_close.click(fn=lambda: gr.update(visible=False), inputs=[], outputs=[ui.details])
|
||||
with gr.Row():
|
||||
with gr.Column(scale=1):
|
||||
text = gr.HTML('<div>title</div>')
|
||||
ui.details_components.append(text)
|
||||
with gr.Column(scale=1):
|
||||
img = gr.Image(value=None, show_label=False, interactive=False, container=True)
|
||||
ui.details_components.append(img)
|
||||
with gr.Row():
|
||||
gr.Button('Replace')
|
||||
gr.Button('Delete')
|
||||
with gr.Tabs():
|
||||
with gr.Tab('Description'):
|
||||
desc = gr.Textbox('', show_label=False, lines=8)
|
||||
ui.details_components.append(desc)
|
||||
with gr.Row():
|
||||
gr.Button('Save')
|
||||
gr.Button('Delete')
|
||||
with gr.Tab('Info'):
|
||||
info = gr.Textbox('', show_label=False, lines=8)
|
||||
ui.details_components.append(info)
|
||||
with gr.Row():
|
||||
gr.Button('Save')
|
||||
gr.Button('Delete')
|
||||
with gr.Tab('Metadata'):
|
||||
meta = gr.JSON({}, show_label=False, lines=8)
|
||||
ui.details_components.append(meta)
|
||||
|
||||
with ui.tabs:
|
||||
button_refresh = ToolButton(symbols.refresh, elem_id=tabname+"_extra_refresh")
|
||||
button_close = ToolButton(symbols.close, elem_id=tabname+"_extra_close")
|
||||
ui.search = gr.Textbox('', show_label=False, elem_id=tabname+"_extra_search", placeholder="Search...", elem_classes="textbox", lines=2)
|
||||
ui.description = gr.Textbox('', show_label=False, elem_id=tabname+"_description", placeholder="Save/Replace Extra Network Description...", elem_classes="textbox", lines=2)
|
||||
ui.preview_target_filename = gr.Textbox('Preview save filename', elem_id=tabname+"_preview_filename", visible=False)
|
||||
ui.button_save_preview = gr.Button('Save preview', elem_id=tabname+"_save_preview", visible=False)
|
||||
ui.description_target_filename = gr.Textbox('Description save filename', elem_id=tabname+"_description_filename", visible=False)
|
||||
ui.button_save_description = gr.Button('Save description', elem_id=tabname+"_save_description", visible=False)
|
||||
|
||||
if ui.tabname == 'txt2img': # refresh only once
|
||||
global refresh_time # pylint: disable=global-statement
|
||||
@@ -397,12 +452,29 @@ def create_ui(container, button, tabname, skip_indexing = False):
|
||||
page.create_page(ui.tabname, skip_indexing)
|
||||
with gr.Tab(page.title, id=page.title.lower().replace(" ", "_"), elem_classes="extra-networks-tab"):
|
||||
hmtl = gr.HTML(page.html, elem_id=f'{tabname}{page.name}_extra_page', elem_classes="extra-networks-page")
|
||||
# hmtl.change(fn=lambda: None, _js=f'() => refreshExtraNetworks("{tabname}")', inputs=[], outputs=[])
|
||||
ui.pages.append(hmtl)
|
||||
|
||||
def toggle_visibility(is_visible):
|
||||
is_visible = not is_visible
|
||||
return is_visible, gr.update(visible=is_visible), gr.update(variant=("secondary-down" if is_visible else "secondary"))
|
||||
def show_details(text, img, desc, info, meta):
|
||||
page, item = get_item(state)
|
||||
if item is not None and os.path.exists(item.filename):
|
||||
stat = os.stat(item.filename)
|
||||
text = f'''
|
||||
<h2 style="border-bottom: 1px solid var(--button-primary-border-color); margin-bottom: 1em; margin-top: -1.3em !important;">{item.name}</h2>
|
||||
<table style="width: 100%; line-height: 1.3em;"><tbody>
|
||||
<tr><td>Type</td><td>{page.title}</td></tr>
|
||||
<tr><td>Alias</td><td>{getattr(item, 'alias', 'N/A')}</td></tr>
|
||||
<tr><td>Tags</td><td>{getattr(item, 'tags', 'N/A')}</td></tr>
|
||||
<tr><td>Filename</td><td>{item.filename}</td></tr>
|
||||
<tr><td>Hash</td><td>{getattr(item, 'hash', 'N/A')}</td></tr>
|
||||
<tr><td>Size</td><td>{round(stat.st_size/1024/1024, 2)} MB</td></tr>
|
||||
<tr><td>Last modified</td><td>{datetime.fromtimestamp(stat.st_mtime)}</td></tr>
|
||||
</tbody></table>
|
||||
'''
|
||||
desc = item.description
|
||||
info = page.info.get(item.name, 'N/A')
|
||||
meta = page.metadata.get(item.name, 'N/A')
|
||||
img = page.find_preview_file(item.filename)
|
||||
return [text, img, desc, info, meta, gr.update(visible=True)]
|
||||
|
||||
def en_refresh(title):
|
||||
pages = []
|
||||
@@ -416,49 +488,13 @@ def create_ui(container, button, tabname, skip_indexing = False):
|
||||
ui.search.update(value = ui.search.value)
|
||||
return pages
|
||||
|
||||
state_visible = gr.State(value=False) # pylint: disable=abstract-class-instantiated
|
||||
button.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container, button])
|
||||
button_close.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container])
|
||||
button_parent.click(fn=toggle_visibility, inputs=[ui.visible], outputs=[ui.visible, container, button_parent])
|
||||
button_close.click(fn=toggle_visibility, inputs=[ui.visible], outputs=[ui.visible, container])
|
||||
button_refresh.click(_js='getENActivePage', fn=en_refresh, inputs=[ui.search], outputs=ui.pages)
|
||||
ui.state.change(state_change, inputs=[ui.state], outputs=[])
|
||||
ui.button_details.click(show_details, _js="getCardDetails", inputs=ui.details_components, outputs=ui.details_components + [ui.details])
|
||||
return ui
|
||||
|
||||
|
||||
def path_is_parent(parent_path, child_path):
|
||||
parent_path = os.path.abspath(parent_path)
|
||||
child_path = os.path.abspath(child_path)
|
||||
return child_path.startswith(parent_path)
|
||||
|
||||
|
||||
def setup_ui(ui, gallery):
|
||||
|
||||
def save_preview(pagename, index, images, filename):
|
||||
pages = []
|
||||
for page in get_pages():
|
||||
if pagename is None or pagename == '' or pagename == page.title or len(page.html) == 0:
|
||||
page.save_preview(index, images, filename)
|
||||
page.create_page(ui.tabname)
|
||||
pages.append(page.html)
|
||||
return pages
|
||||
|
||||
ui.button_save_preview.click(
|
||||
fn=save_preview,
|
||||
_js="function(t, i, y, z) {return [getENActivePage(), selected_gallery_index(), y, z]}",
|
||||
inputs=[ui.search, ui.preview_target_filename, gallery, ui.preview_target_filename],
|
||||
outputs=ui.pages
|
||||
)
|
||||
|
||||
def save_description(pagename, filename, desc):
|
||||
pages = []
|
||||
for page in get_pages():
|
||||
if pagename is None or pagename == '' or pagename == page.title or len(page.html) == 0:
|
||||
page.save_description(filename, desc)
|
||||
page.create_page(ui.tabname)
|
||||
pages.append(page.html)
|
||||
return pages
|
||||
|
||||
ui.button_save_description.click(
|
||||
fn=save_description,
|
||||
_js="function(t, x, y) { return [getENActivePage(), x, y] }",
|
||||
inputs=[ui.search, ui.description_target_filename, ui.description],
|
||||
outputs=ui.pages
|
||||
)
|
||||
ui.gallery = gallery
|
||||
|
||||
Reference in New Issue
Block a user