mirror of https://github.com/Nevysha/Cozy-Nest.git
v2.4.0 (#168)
parent
f639e770db
commit
e2f49c361f
|
|
@ -1,3 +1,4 @@
|
|||
Cozy-Nest.iml
|
||||
nevyui_settings.json
|
||||
data/images.cache
|
||||
data/images.cache
|
||||
/scripts/cozy_lib/log_enabled
|
||||
|
|
|
|||
148
PATCHNOTE.md
148
PATCHNOTE.md
|
|
@ -3,9 +3,18 @@
|
|||
- Automatic1111's webui 1.3.2 release.
|
||||
- SD Next (Vlad's fork) Version: 4867dafa Fri Jun 23. (Not compatible with latest!)
|
||||
|
||||
## New features in 2.4.0
|
||||
- [x] Dedicated Extra Network component which *should* be more stable and faster.
|
||||
- [x] Compatible with Civitai Helper (and hard requirement to generate civitai.info file)
|
||||
- [x] Search field
|
||||
- [x] NSFW filter
|
||||
- [x] Mark as NSFW
|
||||
- [x] Folder tree view (toggleable)
|
||||
- [x] Multithread image indexer for image browser
|
||||
|
||||
## Minor changes & fixes in 2.3.4
|
||||
- [x] Compatibility with https://github.com/DominikDoom/a1111-sd-webui-tagcomplete (ctrl+space to autocomplete tags in Cozy Prompt)
|
||||
- [x] Synthax color in prompt for wildcard (ie: '__devilkkw/body-1/eyes_iris_colors__')
|
||||
- [x] Synthax color in prompt for wildcard (ie: '\_\_devilkkw/body-1/eyes_iris_colors\_\_')
|
||||
- [x] Synthax color in prompt for attention value (':1.1', ':2.3', ...)
|
||||
- [x] Keybinding to increase or decrease attention value (ctrl+up, ctrl+down)
|
||||
|
||||
|
|
@ -29,140 +38,3 @@
|
|||
- [x] Move prompt tools button ("redo last prompt", ...)
|
||||
- [x] Add a secondary accent color in settings, applied to some elements (open scripts...)
|
||||
- [x] Reworked a lots of padding for a cleaner and more compact view
|
||||
|
||||
## Minor changes & fixes in 2.2.4
|
||||
- [x] Fix image url which was hardcoded to port 7860.
|
||||
- [x] use data_dir args to locate and save settings.
|
||||
- [x] small space optimization.
|
||||
|
||||
## Minor changes & fixes in 2.2.3
|
||||
- [x] Differed Extra Network loading. This mean that the initial loading of Cozy Nest should be faster.
|
||||
|
||||
## New features in 2.2.0
|
||||
- [x] Redo the Extra Network tweaks from scratch. User experiencing issues with previous version should not experience issues anymore.
|
||||
- [x] Enhanced prompt editor with color (in txt2img and img2img) - It can be disabled through settings
|
||||
- [x] Tag system for image browser : you can now add tag to your images and filter them by tag. Tags are saved in exif metadata.
|
||||
- [x] Exif metadata editor : you can now edit exif metadata of your images
|
||||
- [x] You can move img into a separated archive folder (set through settings)
|
||||
- [x] You can hide image from image browser (a tag is added to the image exif)
|
||||
- [x] You can delete images from image browser
|
||||
- [x] Image browser now build a cache of its index to speed up loading time
|
||||
|
||||
## Minor changes & fixes in 2.1.7
|
||||
|
||||
- [x] Quick fix to be able to save settings despite the new ui-config.json save ui system. (pruning the file from Cozy Nest entry on startup)
|
||||
|
||||
## Minor changes & fixes in 2.1.6
|
||||
|
||||
- [x] Some settings were not saved properly
|
||||
|
||||
## Minor changes & fixes in 2.1.5
|
||||
|
||||
- [x] Font color settings added (and auto calculate subdued color)
|
||||
- [x] Cozy Nest should detect more easily if it is running on Automatic1111's webui or SD Next (Vlad's fork)
|
||||
- [x] Few tweaks for SD Next
|
||||
|
||||
## Minor changes & fixes in 2.1.4
|
||||
|
||||
- [x] Add a "clear" button to txt2img and img2img gallery
|
||||
- [x] When background and waves animation are disabled they now remain visible but static
|
||||
- [x] Settings to enable/disable extra networks tweaks
|
||||
- [x] Settings to enable/disable clear gallery button tweaks
|
||||
- [x] Fix CozyNest=No not working
|
||||
|
||||
## Minor changes & fixes in 2.1.3
|
||||
|
||||
- [x] Fix for default "send to" button
|
||||
- [x] Fix image browser search
|
||||
- [x] SFW settings to blur all images in the UI 👀
|
||||
- [x] Removed console spam from image browser
|
||||
- [x] Faster animation for settings, update and side panels
|
||||
- [x] Fix for troubleshot dialog with Vlad's fork
|
||||
|
||||
## Minor changes & fixes in 2.1.2
|
||||
|
||||
- [x] Fix for Vlad's fork compatibility
|
||||
|
||||
## Minor changes & fixes in 2.1.1
|
||||
|
||||
- [x] Fix bug for image with special characters in their name (like "+")
|
||||
|
||||
## New features in 2.1.0
|
||||
|
||||
- [x] Settings panel for image browser
|
||||
- [x] Chose default socket port
|
||||
- [x] Enable/disable auto search for free port
|
||||
- [x] Enable/disable auto fetch for output folder from a1111 settings
|
||||
- [x] Add/Remove custom output folder
|
||||
|
||||
- [x] Code has been refactored to use a module builder (Vite). Refactoring is still in progress in the long run. But it should help to stabilize the code base.
|
||||
|
||||
## Minor changes & fixes in 2.0.9
|
||||
|
||||
- [x] Using the builtin png info to get image generation data in image browser
|
||||
- [x] Add error handling and display for image browser
|
||||
- [x] Fix : prevent error if a tab button made by an extension does not have inner text
|
||||
- [x] Add close button to Cozy Nest update panel
|
||||
|
||||
## Minor changes & fixes in 2.0.8
|
||||
|
||||
- [x] Remove the "click outside to close" behavior of the settings panel. It was causing some issue with others extensions.
|
||||
|
||||
## Minor changes & fixes in 2.0.7
|
||||
|
||||
- [x] Fix : small fix
|
||||
|
||||
## Minor changes & fixes in 2.0.5
|
||||
|
||||
- [x] Fix for menu and quicksettings size. Should be more consistent now, but still buggy with too many quicksettings.
|
||||
|
||||
## Minor changes & fixes in 2.0.4
|
||||
|
||||
- [x] Simplify extra network cards/thumb display - should work better. I know that a lots of work still remain to be done on this part but code need a cleanup before.
|
||||
|
||||
## Minor changes & fixes in 2.0.3
|
||||
|
||||
- [x] "Send to" buttons are opening right side drawer panels rather than swapping tabs #75
|
||||
- [x] Fix: tweak of SDAtom-WebUi-client-queue-ext extension. To enable the tweak you have to add `"extensions": ["SDAtom-WebUi-client-queue-ext"]` to your nevyui_settings.json file
|
||||
|
||||
## Minor changes & fixes in 2.0.2
|
||||
|
||||
- [x] Cozy Nest error popup should now only be displayed if filename contains Cozy Nest
|
||||
- [x] Fix image browser crash if metadata are not formatted as expected (although metadata display may display "Error parsing metadata")
|
||||
- [x] Fix Download image link hidden on hover
|
||||
- [x] Fix selectable options list passing under other elements
|
||||
- [x] Fix: Grid image appear in Image browser after batch gen
|
||||
- [x] Fix: Wave badly displayed
|
||||
- [x] Add setting to disable waves and gradiant bg
|
||||
|
||||
## Minor changes & fixes in 2.0.1
|
||||
|
||||
- [x] Fix: Some user report a missing scrollbar in Extra network tab
|
||||
- [x] Fix: image browser spamming console
|
||||
- [x] Fix: Image Browser socket do not always close properly when reloading the UI
|
||||
- [x] disable image browser via settings (NOW DISABLED BY DEFAULT)
|
||||
- [x] extra network tab fix and perf (a bit)
|
||||
|
||||
|
||||
## New features in 2.0.0
|
||||
|
||||
- [x] Fully integrated Image Browser **IN BETA**. Lots of bugs and missing features. Please be kind with Github issues.
|
||||
- [x] Send to txt2img / img2img / …
|
||||
- [x] Clean memory for image not visible (unload them / replace with dummy div) clean filteredImages and loadedImage Array
|
||||
- [x] manage new image generated
|
||||
- [x] Automatically get image output folder (without grid folder)
|
||||
- [x] Drag and drop image
|
||||
|
||||
## Issues fixed
|
||||
|
||||
- [x] Laggy Extra Network tab opening
|
||||
- [x] crash when loading without setting file saved
|
||||
- [x] Fix Drag and drop image
|
||||
|
||||
## Known Issue
|
||||
|
||||
- [ ] Metadata display in image browser may display "Error parsing metadata"
|
||||
- [ ] Partial compatibility with Firefox and Opera GX
|
||||
- [ ] Most tweak will not support a live window resize
|
||||
- [ ] Some user report a missing scrollbar in Extra network tab
|
||||
- [ ] Some user report an crash when attempting to open Extra Network tab
|
||||
|
|
|
|||
|
|
@ -109,5 +109,6 @@ Feel free to contribute to this project. I'm sure there are a lot of things that
|
|||
I'll try to keep this extension up to date with the latest version of auto1111.
|
||||
|
||||
# Credits
|
||||
* [DominikDoom](https://github.com/DominikDoom/a1111-sd-webui-tagcomplete) used part of is code to retrieve valid extra networks
|
||||
* [anapnoe](https://github.com/anapnoe/stable-diffusion-webui-ux)'s incredible work on its fork of sd-webui
|
||||
* [AUTOMATIC1111](https://github.com/AUTOMATIC1111/stable-diffusion-webui)'s work on sd-webui
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +1,2 @@
|
|||
VITE_CONTEXT=DEV
|
||||
VITE_CONTEXT=DEV
|
||||
PROMPT_LOGGING=0
|
||||
|
|
@ -1 +1,2 @@
|
|||
VITE_CONTEXT=PROD
|
||||
VITE_CONTEXT=PROD
|
||||
PROMPT_LOGGING=0
|
||||
|
|
@ -18,7 +18,11 @@ export const modalTheme = defineMultiStyleConfig({
|
|||
marginLeft: 'auto',
|
||||
transform: 'none',
|
||||
maxWidth: 'fit-content',
|
||||
background: 'none',
|
||||
border: '1px solid var(--ae-input-border-color)',
|
||||
backgroundColor: 'var(--block-background-fill)',
|
||||
color: 'var(--body-text-color)',
|
||||
borderRadius: '0 !important',
|
||||
fontSize: 'var(--body-text-size)',
|
||||
},
|
||||
}),
|
||||
'nevysha-confirm':
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import {Column, Row} from "../main/Utils.jsx";
|
|||
import {ButtonWithConfirmDialog} from "../chakra/ButtonWithConfirmDialog.jsx";
|
||||
import DOM_IDS from "../main/dom_ids.js";
|
||||
import {Range as AceRange} from "ace-builds/src-noconflict/ace";
|
||||
import {CozyLogger} from "../main/CozyLogger.js";
|
||||
import {CozyLoggerPrompt as CozyLogger} from "../main/CozyLogger.js";
|
||||
// ace.config.setModuleUrl(
|
||||
// "ace/mode/json_worker",
|
||||
// 'cozy-nest-client/node_modules/ace-builds/src-noconflict/worker-json.js')
|
||||
|
|
@ -27,7 +27,7 @@ ace.config.setModuleUrl(
|
|||
|
||||
const langTools = ace.require("ace/ext/language_tools");
|
||||
|
||||
export function App({parentId, containerId, tabId}) {
|
||||
export function App({parentId, containerId, tabId, resolve}) {
|
||||
|
||||
let savedHeight = localStorage.getItem(`cozy-prompt-height-${containerId}`);
|
||||
savedHeight = savedHeight ? parseInt(savedHeight) : 200;
|
||||
|
|
@ -255,6 +255,10 @@ export function App({parentId, containerId, tabId}) {
|
|||
enableBasicAutocompletion: true
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
resolve()
|
||||
}, 200);
|
||||
|
||||
}
|
||||
|
||||
// Helper function to increment the item
|
||||
|
|
|
|||
|
|
@ -4,7 +4,17 @@ import {App} from "./App.jsx";
|
|||
import {ChakraProvider} from '@chakra-ui/react'
|
||||
import {theme} from "../chakra/chakra-theme.ts";
|
||||
|
||||
export default function startCozyPrompt(parentId, containerId, tabId) {
|
||||
export default async function startCozyPrompt(parentId, containerId, tabId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
_startCozyPrompt(parentId, containerId, tabId, resolve);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _startCozyPrompt(parentId, containerId, tabId, resolve) {
|
||||
//
|
||||
if (!document.getElementById(parentId)) {
|
||||
setTimeout(() => startCozyPrompt(), 200)
|
||||
|
|
@ -21,7 +31,7 @@ export default function startCozyPrompt(parentId, containerId, tabId) {
|
|||
ReactDOM.createRoot(document.getElementById(containerId)).render(
|
||||
<React.StrictMode>
|
||||
<ChakraProvider theme={theme} >
|
||||
<App containerId={containerId} parentId={parentId} tabId={tabId}/>
|
||||
<App containerId={containerId} parentId={parentId} tabId={tabId} resolve={resolve}/>
|
||||
</ChakraProvider >
|
||||
</React.StrictMode>,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,194 @@
|
|||
import React, {useContext} from "react";
|
||||
import {useEffect} from "react";
|
||||
import {Loading} from "../image-browser/App.jsx";
|
||||
import {Checkbox, Tab, TabList, TabPanel, TabPanels, Tabs} from "@chakra-ui/react";
|
||||
import './CozyExtraNetworks.scss'
|
||||
import {ExtraNetworksCard} from "./ExtraNetworksCard.jsx";
|
||||
import {Column, Row, RowFullWidth} from "../main/Utils.jsx";
|
||||
import {SvgForReact} from "../main/svg_for_react.jsx";
|
||||
import {FolderTreeFilter} from "./FolderTreeFilter.jsx";
|
||||
|
||||
const nevyshaScrollbar = {
|
||||
'&::-webkit-scrollbar': {
|
||||
width: '5px',
|
||||
},
|
||||
'&::-webkit-scrollbar-track': {
|
||||
backgroundColor: 'transparent'
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
backgroundColor: 'var(--ae-primary-color)',
|
||||
borderRadius: '20px',
|
||||
},
|
||||
}
|
||||
|
||||
const indexRef = []
|
||||
|
||||
export function CozyExtraNetworks() {
|
||||
|
||||
const [extraNetworks, setExtraNetworks] = React.useState([])
|
||||
const [folders, setFolders] = React.useState([])
|
||||
const [ready, setReady] = React.useState(false)
|
||||
const [fullyLoaded, setFullyLoaded] = React.useState(false)
|
||||
|
||||
const [searchString, setSearchString] = React.useState('')
|
||||
const [displayFolderFilter, setDisplayFolderFilter] = React.useState(false)
|
||||
const [nsfwFilter, setNsfwFilter] = React.useState(false)
|
||||
|
||||
const [selectedTab, setSelectedTab] = React.useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const response = await fetch('/cozy-nest/extra_networks')
|
||||
if (response.status !== 200) {
|
||||
CozyLogger.error('failed to fetch extra networks', response)
|
||||
return;
|
||||
}
|
||||
const _enJson = await response.json()
|
||||
|
||||
const responseFolders = await fetch('/cozy-nest/extra_networks/folders')
|
||||
if (responseFolders.status !== 200) {
|
||||
CozyLogger.error('failed to fetch extra networks folders', responseFolders)
|
||||
return;
|
||||
}
|
||||
const _folders = await responseFolders.json()
|
||||
|
||||
setFolders(_folders)
|
||||
setExtraNetworks(_enJson)
|
||||
setReady(true)
|
||||
})()
|
||||
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
if (!nsfwFilter) return;
|
||||
if (fullyLoaded) return;
|
||||
|
||||
setReady(false);
|
||||
|
||||
(async () => {
|
||||
const response = await fetch('/cozy-nest/extra_networks/full')
|
||||
if (response.status === 200) {
|
||||
const json = await response.json()
|
||||
setExtraNetworks(json)
|
||||
setReady(true)
|
||||
setFullyLoaded(true)
|
||||
setSelectedTab(indexRef[0])
|
||||
}
|
||||
else {
|
||||
CozyLogger.error('failed to fetch full extra networks info', response)
|
||||
}
|
||||
})()
|
||||
}, [nsfwFilter])
|
||||
|
||||
function buildExtraNetworks() {
|
||||
|
||||
const EnTabs = [];
|
||||
const EnTabPanels = [];
|
||||
|
||||
const style = {
|
||||
border: 'none',
|
||||
height: '100%',
|
||||
borderBottom: '1px solid var(--ae-input-border-color)',
|
||||
borderTop: '1px solid var(--tab-nav-background-color-selected)',
|
||||
};
|
||||
|
||||
Object.keys(extraNetworks).forEach((network, index) => {
|
||||
let tabName = String(network);
|
||||
indexRef.push(network)
|
||||
if (network === 'embeddings') {
|
||||
tabName = 'Textual Inversion'
|
||||
}
|
||||
EnTabs.push(
|
||||
<Tab key={index}>{tabName}</Tab>
|
||||
)
|
||||
EnTabPanels.push(
|
||||
<TabPanel css={nevyshaScrollbar} key={index} style={style}>
|
||||
<div className="CozyExtraNetworksPanels">
|
||||
{extraNetworks[network].map((item, index) => {
|
||||
return (
|
||||
<ExtraNetworksCard
|
||||
key={item.path}
|
||||
item={item}
|
||||
searchString={searchString}
|
||||
selectedFolder={selectedFolder}
|
||||
nsfwFilter={nsfwFilter}/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</TabPanel>
|
||||
)
|
||||
})
|
||||
|
||||
return {EnTabs, EnTabPanels}
|
||||
}
|
||||
|
||||
function onTabSelect(index) {
|
||||
setSelectedTab(indexRef[index])
|
||||
}
|
||||
|
||||
const [selectedFolder, setSelectedFolder] = React.useState(null)
|
||||
function folderSelectHandler({element}) {
|
||||
CozyLogger.debug('folderSelectHandler', {element})
|
||||
|
||||
if (element.name === 'all' || !element.metadata) {
|
||||
setSelectedFolder(null)
|
||||
return
|
||||
}
|
||||
setSelectedFolder(element.metadata.path)
|
||||
|
||||
}
|
||||
|
||||
const Ui = buildExtraNetworks()
|
||||
|
||||
const hasSubFolders = folders[selectedTab] && !folders[selectedTab].empty
|
||||
|
||||
return (
|
||||
<div className="CozyExtraNetworks">
|
||||
{!ready && <Loading label="Loading Extra Networks..."/>}
|
||||
{ready &&
|
||||
<Column style={{width: '100%'}}>
|
||||
<textarea data-testid="textbox"
|
||||
placeholder="Search..."
|
||||
rows="1"
|
||||
spellCheck="false"
|
||||
data-gramm="false"
|
||||
style={{resize: 'none', minHeight: '35px'}}
|
||||
onChange={(e) => setSearchString(e.target.value)}/>
|
||||
<RowFullWidth style={{margin:'3px 0'}}>
|
||||
<Checkbox
|
||||
isChecked={displayFolderFilter}
|
||||
disabled={!hasSubFolders}
|
||||
onChange={(e) => setDisplayFolderFilter(e.target.checked)}
|
||||
>Display folder filter</Checkbox>
|
||||
<div style={{flex:1}}/>
|
||||
<button
|
||||
onClick={() => setNsfwFilter(!nsfwFilter)}
|
||||
title="WARNING : this will take time as it will compute the info of all extra networks"
|
||||
className="btn-settings toggleNsfwFilter"
|
||||
>Toggle sfw filter
|
||||
<span className="sfwFilterInfo">{!nsfwFilter ? SvgForReact.eyeSlash : SvgForReact.eye }</span>
|
||||
</button>
|
||||
</RowFullWidth>
|
||||
<Row style={{height: 'calc(100% - 90px)'}}>
|
||||
{displayFolderFilter &&
|
||||
<FolderTreeFilter
|
||||
hasSubFolders={hasSubFolders}
|
||||
folder={folders[selectedTab]}
|
||||
forNetwork={selectedTab}
|
||||
selectHandler={folderSelectHandler}/>
|
||||
}
|
||||
<Tabs variant='nevysha' onChange={onTabSelect}>
|
||||
<TabList style={{backgroundColor: 'var(--tab-nav-background-color)'}}>
|
||||
{Ui.EnTabs}
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
{Ui.EnTabPanels}
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</Row>
|
||||
</Column>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
#cozy-extra-network-react {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.CozyExtraNetworks {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
|
||||
.chakra-tabs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.chakra-tabs__tab-panels {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.CozyExtraNetworksPanels {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
row-gap: 5px;
|
||||
column-gap: 2px;
|
||||
}
|
||||
.CozyExtraNetworksCard {
|
||||
background-color: var(--ae-input-bg-color);
|
||||
border-radius: 3px;
|
||||
}
|
||||
.CozyExtraNetworksCard:hover {
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
|
||||
.en-preview-wrapper {
|
||||
position: relative;
|
||||
width: var(--extra-network-card-width);
|
||||
height: var(--extra-network-card-height);
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--ae-input-border-color);
|
||||
border-radius: 3px;
|
||||
}
|
||||
.en-preview-thumbnail {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100% !important;
|
||||
object-fit: cover; /* Maintain aspect ratio while covering the entire container */
|
||||
}
|
||||
.en-preview-thumbnail.black {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 15px;
|
||||
}
|
||||
.cozy-en-info {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-index: 2;
|
||||
background-color: var(--block-background-fill);
|
||||
opacity: 0.9;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 4px;
|
||||
}
|
||||
.en-preview-name {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
.cozy-en-actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-evenly;
|
||||
height: 100%;
|
||||
background-color: var(--ae-input-bg-color);
|
||||
opacity: 0.8;
|
||||
z-index: 2;
|
||||
align-content: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
}
|
||||
.cozy-en-actions > button {
|
||||
min-width: 25px;
|
||||
}
|
||||
.cozy-en-actions > button:hover {
|
||||
color: var(--ae-primary-color);
|
||||
}
|
||||
.cozy-en-actions > button > svg {
|
||||
fill: var(--nevysha-font-color);
|
||||
height: 15px;
|
||||
}
|
||||
.cozy-en-actions > button > svg:hover {
|
||||
fill: var(--ae-primary-color);
|
||||
}
|
||||
|
||||
.CozyExtraNetworks .btn-settings {
|
||||
background-color: var(--ae-input-bg-color) !important;
|
||||
width: 150px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
span.sfwFilterInfo {
|
||||
fill: var(--nevysha-font-color);
|
||||
margin-left: 5px;
|
||||
}
|
||||
button.toggleNsfwFilter {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
|
@ -0,0 +1,341 @@
|
|||
import React, {useEffect} from "react";
|
||||
import {SvgForReact} from "../main/svg_for_react.jsx";
|
||||
import {LazyComponent} from "./LazyComponent.jsx";
|
||||
import {ImageUploadModal} from "./ImageUploadModal.jsx";
|
||||
import {Button} from "@chakra-ui/react";
|
||||
import CozyModal from "../main/modal/Module.jsx";
|
||||
|
||||
const CIVITAI_URL = {
|
||||
"modelPage":"https://civitai.com/models/",
|
||||
"modelId": "https://civitai.com/api/v1/models/",
|
||||
"modelVersionId": "https://civitai.com/api/v1/model-versions/",
|
||||
"hash": "https://civitai.com/api/v1/model-versions/by-hash/"
|
||||
}
|
||||
|
||||
function NsfwButton({onClick, nsfw}) {
|
||||
|
||||
const [isHovered, setIsHovered] = React.useState(false)
|
||||
|
||||
function getIcon() {
|
||||
let iconName;
|
||||
if (nsfw) {
|
||||
iconName = isHovered ? "eye" : "eyeSlash"
|
||||
}
|
||||
else {
|
||||
iconName = isHovered ? "eyeSlash" : "eye"
|
||||
}
|
||||
return SvgForReact[iconName];
|
||||
}
|
||||
|
||||
return <button
|
||||
title={nsfw ? "Mark as SFW" : "Mark as NSFW"}
|
||||
onClick={onClick}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
{getIcon()}
|
||||
</button>;
|
||||
}
|
||||
|
||||
export function ExtraNetworksCard({item, searchString, selectedFolder, nsfwFilter}) {
|
||||
|
||||
const [isHovered, setIsHovered] = React.useState(false)
|
||||
const [info, setInfo] = React.useState(item.info || {})
|
||||
const [infoLoaded, setInfoLoaded] = React.useState(item.info !== undefined)
|
||||
const [validInfo, setValidInfo] = React.useState(info !== undefined && info !== null)
|
||||
|
||||
const [matchFilter, setMatchFilter] = React.useState(true)
|
||||
|
||||
const [showFileUpload, setShowFileUpload] = React.useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (infoLoaded || !isHovered) return
|
||||
|
||||
(async () => {
|
||||
const response = await fetch(`/cozy-nest/extra_network?path=${encodeURIComponent(item.path)}`)
|
||||
if (response.status !== 200) {
|
||||
CozyLogger.error('Failed to fetch extra network info', response)
|
||||
return
|
||||
}
|
||||
const info = await response.json()
|
||||
setInfo(info)
|
||||
setInfoLoaded(true)
|
||||
setValidInfo(info !== null)
|
||||
})();
|
||||
|
||||
}, [isHovered])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
if (nsfwFilter
|
||||
&& info
|
||||
&& info.model
|
||||
&& info.model.nsfw) {
|
||||
setMatchFilter(false)
|
||||
return;
|
||||
}
|
||||
|
||||
setMatchFilter(filterCard(searchString, selectedFolder))
|
||||
}, [selectedFolder, searchString, nsfwFilter, info])
|
||||
|
||||
useEffect(() => {
|
||||
//when hiding card, reset hover state
|
||||
if (!matchFilter) {
|
||||
setIsHovered(false)
|
||||
}
|
||||
//when showing card, reset hover state
|
||||
if (showFileUpload) {
|
||||
setIsHovered(false)
|
||||
}
|
||||
}, [matchFilter, showFileUpload])
|
||||
|
||||
function isNsfw() {
|
||||
return info && info.model && info.model.nsfw
|
||||
}
|
||||
|
||||
function filterCard(searchString, selectedFolder) {
|
||||
const hasSelectFolder = selectedFolder && selectedFolder !== ''
|
||||
|
||||
function normalizePath(path) {
|
||||
return path.replace(/[\/\\:]/g, '')
|
||||
}
|
||||
|
||||
if (searchString !== '' || hasSelectFolder) {
|
||||
//only search string
|
||||
if (!hasSelectFolder) {
|
||||
return normalizePath(item.path).includes(searchString)
|
||||
}
|
||||
//only selected folder
|
||||
else if (searchString === '') {
|
||||
return normalizePath(item.path).includes(normalizePath(selectedFolder))
|
||||
}
|
||||
//both
|
||||
return (normalizePath(item.path).includes(searchString)
|
||||
|| normalizePath(item.path).includes(normalizePath(selectedFolder)))
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function addTriggerWordsToPrompt(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (info.trainedWords.length === 0) return
|
||||
|
||||
setAndPropagatePrompt(`${info.trainedWords.join(', ')}, `)
|
||||
}
|
||||
|
||||
function getActiveTextarea(negativePrompt) {
|
||||
const currentTab = get_uiCurrentTabContent().id
|
||||
|
||||
let textarea = null
|
||||
if (currentTab.includes('txt2img')) {
|
||||
if (negativePrompt) {
|
||||
textarea = document.querySelector(`#txt2img_neg_prompt label textarea`)
|
||||
} else
|
||||
textarea = document.querySelector(`#txt2img_prompt label textarea`)
|
||||
} else if (currentTab.includes('img2img')) {
|
||||
if (negativePrompt) {
|
||||
textarea = document.querySelector(`#img2img_neg_prompt label textarea`)
|
||||
} else
|
||||
textarea = document.querySelector(`#img2img_prompt label textarea`)
|
||||
}
|
||||
return textarea;
|
||||
}
|
||||
|
||||
function clearPrompt(negativePrompt) {
|
||||
let textarea = getActiveTextarea(negativePrompt);
|
||||
textarea.value = ''
|
||||
}
|
||||
|
||||
function setAndPropagatePrompt(newValue, negativePrompt) {
|
||||
|
||||
if (!newValue || newValue.length === 0) return
|
||||
|
||||
let textarea = getActiveTextarea(negativePrompt);
|
||||
|
||||
let value = textarea.value
|
||||
if (value.length !== 0) {
|
||||
value = `${textarea.value}\n${newValue}`
|
||||
}
|
||||
else {
|
||||
value = newValue
|
||||
}
|
||||
|
||||
textarea.value = value
|
||||
|
||||
//trigger input event
|
||||
const event = new Event('input')
|
||||
textarea.dispatchEvent(event)
|
||||
}
|
||||
|
||||
function openCivitai(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (!info.modelId) return
|
||||
|
||||
const url = `${CIVITAI_URL.modelPage}/${info.modelId}`
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
|
||||
function usePromptFromPreview(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
// image prompt are in info.images[?].meta.prompt (if any)
|
||||
// go through all images until we find one with a prompt
|
||||
if (info.images) {
|
||||
for (const image of info.images) {
|
||||
if (image.meta && image.meta.prompt) {
|
||||
clearPrompt()
|
||||
setAndPropagatePrompt(`${image.meta.prompt}, `)
|
||||
|
||||
if (image.meta.negativePrompt) {
|
||||
clearPrompt(true)
|
||||
setAndPropagatePrompt(`${image.meta.negativePrompt}, `, true)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CozyModal.showToast('warning', 'Not available', 'No prompt found in preview')
|
||||
}
|
||||
|
||||
function replaceImage(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
setShowFileUpload(!showFileUpload)
|
||||
}
|
||||
|
||||
function loadExtraNetwork(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (item.type === 'ckp') {
|
||||
selectCheckpoint(item.fullName)
|
||||
}
|
||||
else if (item.type === 'ti') {
|
||||
setAndPropagatePrompt(`${item.name}, `)
|
||||
}
|
||||
else if (item.type === 'lora' || item.type === 'lyco' || item.type === 'hypernet') {
|
||||
setAndPropagatePrompt(`<${item.type}:${item.name}:1.00>, `)
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleNSFW(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
//send POST request to toggle nsfw
|
||||
const response = await fetch(`/cozy-nest/extra_network/toggle-nsfw`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
path: item.path,
|
||||
})
|
||||
})
|
||||
if (response.status !== 200) {
|
||||
CozyLogger.error('Failed to toggle nsfw', response)
|
||||
return
|
||||
}
|
||||
const info = await response.json()
|
||||
item.info = info
|
||||
setInfo(info)
|
||||
}
|
||||
|
||||
function onPreviewSaved(previewPath) {
|
||||
//update preview path
|
||||
item.previewPath = previewPath
|
||||
setShowFileUpload(false)
|
||||
}
|
||||
|
||||
const hasTriggerWords = info.trainedWords && info.trainedWords.length > 0;
|
||||
const hasModelId = info.modelId !== undefined;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="CozyExtraNetworksCard"
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
onClick={loadExtraNetwork}
|
||||
title={item.name || item}
|
||||
style={{display: matchFilter ? 'flex' : 'none'}}
|
||||
>
|
||||
{matchFilter && <div className="en-preview-wrapper">
|
||||
{isHovered && infoLoaded && validInfo &&
|
||||
<div className="cozy-en-actions">
|
||||
<button
|
||||
title="Replace preview image"
|
||||
onClick={replaceImage}
|
||||
>
|
||||
{SvgForReact.image}
|
||||
</button>
|
||||
{hasModelId && <button
|
||||
title="Open model in civitai"
|
||||
onClick={openCivitai}
|
||||
>
|
||||
{SvgForReact.link}
|
||||
</button>}
|
||||
{hasTriggerWords && <button
|
||||
title="Add trigger words to prompt"
|
||||
onClick={addTriggerWordsToPrompt}
|
||||
>
|
||||
{SvgForReact.magicWand}
|
||||
</button>}
|
||||
<button
|
||||
title="Use prompt from preview image"
|
||||
onClick={usePromptFromPreview}
|
||||
>
|
||||
{SvgForReact.arrow}
|
||||
</button>
|
||||
<NsfwButton onClick={toggleNSFW} nsfw={isNsfw()}/>
|
||||
</div>
|
||||
}
|
||||
{item.previewPath &&
|
||||
<LazyComponent placeholderClassName="en-preview-thumbnail">
|
||||
<img
|
||||
className="en-preview-thumbnail"
|
||||
src={`./sd_extra_networks/thumb?filename=${encodeURIComponent(item.previewPath)}&mtime=${new Date().getTime()}`}
|
||||
alt={item.name}
|
||||
/>
|
||||
</LazyComponent>
|
||||
}
|
||||
{!item.previewPath &&
|
||||
<div className="en-preview-thumbnail black">
|
||||
No preview
|
||||
</div>
|
||||
}
|
||||
{showFileUpload &&
|
||||
<div style={{zIndex:4}}>
|
||||
<ImageUploadModal
|
||||
visible={showFileUpload}
|
||||
name={item.name}
|
||||
path={item.path}
|
||||
cancel={
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setShowFileUpload(false)
|
||||
}}
|
||||
>Cancel</Button>
|
||||
}
|
||||
callback={onPreviewSaved}
|
||||
>
|
||||
|
||||
</ImageUploadModal>
|
||||
</div>
|
||||
}
|
||||
<div className="cozy-en-info">
|
||||
<div className="en-preview-name">{item.name || item}</div>
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
import React from "react";
|
||||
import { FaRegFolder, FaRegFolderOpen } from "react-icons/fa";
|
||||
import { CiFolderOff } from "react-icons/ci";
|
||||
import TreeView, { flattenTree } from "react-accessible-treeview";
|
||||
import './FolderTreeFilter.scss'
|
||||
|
||||
class StoreClass {
|
||||
constructor() {
|
||||
// this.networks = {
|
||||
// selectedNodes: [],
|
||||
// };
|
||||
this.networks = new Map();
|
||||
|
||||
//load from localstorage
|
||||
const _networks = localStorage.getItem('CozyNest/FolderTreeFilter');
|
||||
if (_networks) {
|
||||
this.networks = new Map(Object.entries(JSON.parse(_networks)));
|
||||
}
|
||||
else {
|
||||
this.networks.set('models', {selectedNodes: []});
|
||||
this.networks.set('embeddings', {selectedNodes: []});
|
||||
this.networks.set('lora', {selectedNodes: []});
|
||||
this.networks.set('hypernetworks', {selectedNodes: []});
|
||||
this.save();
|
||||
}
|
||||
}
|
||||
|
||||
selectNode(network, id) {
|
||||
if (!this.networks.has(network)) {
|
||||
this.networks.set(network, {selectedNodes: []});
|
||||
}
|
||||
|
||||
|
||||
this.networks.get(network).selectedNodes.push(id);
|
||||
this.save();
|
||||
}
|
||||
|
||||
unSelectNode(network, id) {
|
||||
this.networks.get(network).selectedNodes = this.networks.get(network).selectedNodes.filter(_id => _id !== id);
|
||||
this.save();
|
||||
}
|
||||
|
||||
save() {
|
||||
//save in localstorage?
|
||||
// localStorage.setItem('CozyNest/FolderTreeFilter', JSON.stringify(Object.fromEntries(this.networks)));
|
||||
}
|
||||
}
|
||||
const Store = new StoreClass();
|
||||
|
||||
export function FolderTreeFilter({hasSubFolders, folder, selectHandler, forNetwork}) {
|
||||
|
||||
//add a fake 'all' folder as first element of children
|
||||
//check if first element is 'all'. If not, add it
|
||||
const _folder = {...folder};
|
||||
if (hasSubFolders && folder.children[0].name !== 'all') {
|
||||
_folder.children = [{name: 'all', children: []}, ..._folder.children];
|
||||
}
|
||||
|
||||
// load selected nodes from store
|
||||
const selectedNodesName = Store.networks.get(forNetwork)?.selectedNodes || [];
|
||||
|
||||
const data = flattenTree(_folder);
|
||||
|
||||
const selectedNodes
|
||||
= data.filter(node => selectedNodesName.includes(node.name)).map(node => node.id);
|
||||
|
||||
function onNodeSelect({element}) {
|
||||
selectHandler({element})
|
||||
}
|
||||
|
||||
function onExpand(branch) {
|
||||
if (branch.isExpanded) {
|
||||
Store.selectNode(forNetwork, branch.element.name);
|
||||
}
|
||||
else {
|
||||
Store.unSelectNode(forNetwork, branch.element.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasSubFolders) {
|
||||
return (
|
||||
<div className="EmptyFolderTreeFilter"></div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="FolderTreeFilter nevysha nevysha-scrollable">
|
||||
<div className="directory">
|
||||
<TreeView
|
||||
data={data}
|
||||
aria-label="directory tree"
|
||||
onNodeSelect={onNodeSelect}
|
||||
defaultExpandedIds={selectedNodes}
|
||||
onExpand={onExpand}
|
||||
nodeRenderer={({
|
||||
element,
|
||||
isBranch,
|
||||
isExpanded,
|
||||
getNodeProps,
|
||||
level,
|
||||
}) => (
|
||||
<div {...getNodeProps()} style={{ paddingLeft: 20 * (level - 1) }}>
|
||||
{isBranch ? (
|
||||
<FolderIcon isOpen={isExpanded} />
|
||||
) : (
|
||||
<FileIcon filename={element.name} />
|
||||
)}
|
||||
|
||||
{element.name}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const FolderIcon = ({ isOpen }) =>
|
||||
isOpen ? (
|
||||
<FaRegFolderOpen color="e8a87c" className="icon" />
|
||||
) : (
|
||||
<FaRegFolder color="e8a87c" className="icon" />
|
||||
);
|
||||
|
||||
const FileIcon = () => <CiFolderOff color="var(--nevysha-font-color)" className="icon" />
|
||||
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
.FolderTreeFilter {
|
||||
|
||||
min-width: 200px;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
margin-right: 10px;
|
||||
|
||||
.directory {
|
||||
background: #242322;
|
||||
font-family: monospace;
|
||||
color: var(--nevysha-font-color);
|
||||
user-select: none;
|
||||
padding: 10px 0 10px 10px;
|
||||
//margin: 0 8px 0 -8px;
|
||||
}
|
||||
|
||||
.directory .tree,
|
||||
.directory .tree-node,
|
||||
.directory .tree-node-group {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.directory .tree-branch-wrapper,
|
||||
.directory .tree-node__leaf {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.directory .tree-node {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.directory .tree-node:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.directory .tree .tree-node--focused {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.directory .tree .tree-node--selected {
|
||||
background: var(--secondary-accent-color);
|
||||
color: var(--secondary-accent-color-from-luminance)
|
||||
}
|
||||
|
||||
.directory .tree-node__branch {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.directory .icon {
|
||||
vertical-align: middle;
|
||||
padding-right: 5px;
|
||||
font-size: calc(var(--nevysha-text-md) * 1.5);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
.ImageUploadModal {
|
||||
background-color: var(--ae-input-bg-color);
|
||||
color: var(--nevysha-font-color);
|
||||
padding: 15px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid var(--ae-input-border-color);
|
||||
|
||||
.name {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
h1 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
font-size: var(--nevysha-text-lg);
|
||||
}
|
||||
span {
|
||||
margin-bottom: 25px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
font-size: var(--nevysha-text-md);
|
||||
filter: brightness(0.7);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
label {
|
||||
border: 2px dashed var(--secondary-accent-color);
|
||||
|
||||
svg {
|
||||
path {
|
||||
fill: var(--secondary-accent-color);
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
span {
|
||||
color: var(--nevysha-font-color);
|
||||
filter: brightness(0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
|
||||
button {
|
||||
padding: 10px;
|
||||
height: 25px;
|
||||
border-radius: 1px;
|
||||
border: 1px solid var(--ae-input-border-color) !important;
|
||||
background: var(--ae-input-bg-color) !important;
|
||||
color: var(--ae-input-color) !important;
|
||||
font-size: var(--nevysha-text-md);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
import React, {useEffect, useState} from "react";
|
||||
import { FileUploader } from "react-drag-drop-files";
|
||||
|
||||
import "./ImageUpload.scss";
|
||||
import {DialogWrapper} from "../settings/App.jsx";
|
||||
import {RowFullWidth} from "../main/Utils.jsx";
|
||||
import {Button} from "@chakra-ui/react";
|
||||
|
||||
const fileTypes = ["PNG"];
|
||||
|
||||
export function ImageUploadModal({visible, cancel, name, path, callback}) {
|
||||
const [file, setFile] = useState(null);
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
|
||||
const handleChange = (file) => {
|
||||
setFile(file);
|
||||
};
|
||||
|
||||
async function upload() {
|
||||
setIsUploading(true);
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("path", path);
|
||||
const response = await fetch("/cozy-nest/extra_network/preview", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
if (!response.ok) {
|
||||
CozyLogger.error("Failed to upload image", response);
|
||||
return;
|
||||
}
|
||||
callback((await response.json()).previewPath);
|
||||
setIsUploading(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{visible &&
|
||||
<DialogWrapper isVisible={isVisible}>
|
||||
<div className="ImageUploadModal">
|
||||
<div className="name">
|
||||
<h1>Upload preview image</h1>
|
||||
<span>{name}</span>
|
||||
</div>
|
||||
<FileUploader
|
||||
handleChange={handleChange}
|
||||
name="file"
|
||||
types={fileTypes}
|
||||
/>
|
||||
<div className="actions">
|
||||
{cancel}
|
||||
<Button
|
||||
isDisabled={!file && !isUploading}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
upload()
|
||||
}}
|
||||
>{!isUploading ? "Upload" : "Uploading..."}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</DialogWrapper>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import React, {useEffect, useRef, useState} from "react";
|
||||
|
||||
export const LazyComponent = ({children, placeholderClassName}) => {
|
||||
const [isInView, setIsInView] = useState(false);
|
||||
const targetRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
setIsInView(entry.isIntersecting);
|
||||
},
|
||||
{
|
||||
root: null, // use viewport as root
|
||||
rootMargin: '0px',
|
||||
threshold: 0.1, // percentage of element's visibility needed to trigger the callback
|
||||
}
|
||||
);
|
||||
|
||||
if (targetRef.current) {
|
||||
observer.observe(targetRef.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (targetRef.current) {
|
||||
observer.unobserve(targetRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={targetRef}>
|
||||
{isInView ? children : <div className={placeholderClassName}>blank</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import {CozyLogger} from "../main/CozyLogger.js";
|
||||
import {CozyExtraNetworks} from "./CozyExtraNetworks.jsx";
|
||||
import {ChakraProvider} from '@chakra-ui/react'
|
||||
import {theme} from "../chakra/chakra-theme.ts";
|
||||
import {hideNativeUiExtraNetworkElement} from "../main/cozy-utils.js";
|
||||
|
||||
export function startCozyExtraNetwork() {
|
||||
return new Promise((resolve, reject) => {
|
||||
_startExtraNetwork(resolve)
|
||||
})
|
||||
}
|
||||
|
||||
function _startExtraNetwork(resolve) {
|
||||
|
||||
CozyLogger.debug('startExtraNetwork')
|
||||
|
||||
if (!document.getElementById(`cozy-extra-network-react`)) {
|
||||
CozyLogger.debug('waiting for extra network react')
|
||||
setTimeout(() => _startExtraNetwork(), 200)
|
||||
return
|
||||
}
|
||||
resolve()
|
||||
|
||||
hideNativeUiExtraNetworkElement('txt2img')
|
||||
hideNativeUiExtraNetworkElement('img2img')
|
||||
|
||||
ReactDOM.createRoot(document.getElementById(`cozy-extra-network-react`)).render(
|
||||
<React.StrictMode>
|
||||
<ChakraProvider theme={theme} >
|
||||
<CozyExtraNetworks />
|
||||
</ChakraProvider>
|
||||
</React.StrictMode>,
|
||||
)
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import {CozyLogger} from "../main/CozyLogger.js";
|
||||
import DOM_IDS from "../main/dom_ids.js";
|
||||
import CozyNestEventBus from "../CozyNestEventBus.js";
|
||||
import {hideNativeUiExtraNetworkElement} from "../main/cozy-utils.js";
|
||||
|
||||
export const LoaderContext = React.createContext({
|
||||
ready: false,
|
||||
|
|
@ -28,13 +29,6 @@ function observeDivChanges(targetDiv, prefix) {
|
|||
});
|
||||
}
|
||||
|
||||
function hideNativeUiElement(prefix) {
|
||||
const triggerButton = document.querySelector(`button#${DOM_IDS.get('extra_networks_btn')(prefix)}`)
|
||||
triggerButton.style.display = 'none'
|
||||
const tabs = document.querySelector(`div#${prefix}_extra_networks`)
|
||||
tabs.style.display = 'none';
|
||||
}
|
||||
|
||||
async function requireNativeBloc(prefix) {
|
||||
|
||||
const triggerButton = document.querySelector(`button#${DOM_IDS.get('extra_networks_btn')(prefix)}`)
|
||||
|
|
@ -74,7 +68,7 @@ export function LoaderProvider({children, prefix}) {
|
|||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
hideNativeUiElement(prefix)
|
||||
hideNativeUiExtraNetworkElement(prefix)
|
||||
|
||||
CozyNestEventBus.once(`extra_network-open:${prefix}`, (p) => {
|
||||
CozyLogger.debug(`extra network load event received for ${p}`)
|
||||
|
|
|
|||
|
|
@ -282,6 +282,7 @@ function App() {
|
|||
rows="1"
|
||||
spellCheck="false"
|
||||
data-gramm="false"
|
||||
style={{resize: 'none'}}
|
||||
onChange={(e) => setSearchStr(e.target.value)}/>
|
||||
<CozyTagsSelect setActiveTags={setActiveTags} />
|
||||
</Row>
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export function ImagesProvider({ children }: { children: ReactNode[] }) {
|
|||
|
||||
const updateExifInState = (image: Image) => {
|
||||
|
||||
const {metadata: {exif, hash}} = image
|
||||
const {metadata: {exif}, hash} = image
|
||||
const newImages = images.map(image => {
|
||||
if (image.hash === hash) {
|
||||
image.metadata.exif = exif
|
||||
|
|
|
|||
|
|
@ -14,10 +14,12 @@ import startCozyPrompt from "./cozy-prompt/main.jsx";
|
|||
import {startExtraNetwork} from "./extra-network/main.jsx";
|
||||
import {OverrideUiJs} from "./main/override_ui.js";
|
||||
import CozyNestEventBus from "./CozyNestEventBus.js";
|
||||
import {startCozyExtraNetwork} from "./cozy_extra_network/main.jsx";
|
||||
window.CozyTools = {
|
||||
dummyLoraCard,
|
||||
dummyControlNetBloc,
|
||||
dummySubdirs
|
||||
dummySubdirs,
|
||||
stop:() => setTimeout(function(){debugger;}, 5000),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -28,10 +30,9 @@ export default async function cozyNestLoader() {
|
|||
await cozyNestModuleLoader(async () => {
|
||||
startCozyNestSettings();
|
||||
|
||||
|
||||
if (COZY_NEST_CONFIG.enable_cozy_prompt === true) {
|
||||
startCozyPrompt('txt2img_prompt', 'cozy_nest_prompt_txt2img', 'txt2img');
|
||||
startCozyPrompt('img2img_prompt', 'cozy_nest_prompt_img2img', 'img2img');
|
||||
await startCozyPrompt('txt2img_prompt', 'cozy_nest_prompt_txt2img', 'txt2img');
|
||||
await startCozyPrompt('img2img_prompt', 'cozy_nest_prompt_img2img', 'img2img');
|
||||
|
||||
OverrideUiJs.override_confirm_clear_prompt();
|
||||
}
|
||||
|
|
@ -39,6 +40,9 @@ export default async function cozyNestLoader() {
|
|||
await startExtraNetwork('txt2img')
|
||||
await startExtraNetwork('img2img')
|
||||
}
|
||||
if (COZY_NEST_CONFIG.enable_cozy_extra_networks === true) {
|
||||
await startCozyExtraNetwork()
|
||||
}
|
||||
|
||||
startCozyNestImageBrowser();
|
||||
|
||||
|
|
@ -54,19 +58,9 @@ window.cozyNestLoader = cozyNestLoader;
|
|||
return
|
||||
}
|
||||
|
||||
// if (getTheme() === 'dark') {
|
||||
const styleSheet = new CSSStyleSheet();
|
||||
styleSheet.replaceSync(sheet);
|
||||
document.adoptedStyleSheets = [styleSheet];
|
||||
// }
|
||||
// else {
|
||||
// const {latte} = await import('./main/latte.css?inline');
|
||||
// const styleSheet = new CSSStyleSheet();
|
||||
// styleSheet.replaceSync(sheet);
|
||||
// const latteSheet = new CSSStyleSheet();
|
||||
// latteSheet.replaceSync(latte);
|
||||
// document.adoptedStyleSheets = [styleSheet, latteSheet];
|
||||
// }
|
||||
const styleSheet = new CSSStyleSheet();
|
||||
styleSheet.replaceSync(sheet);
|
||||
document.adoptedStyleSheets = [styleSheet];
|
||||
|
||||
|
||||
SimpleTimer.time(COZY_NEST_GRADIO_LOAD_DURATION);
|
||||
|
|
|
|||
|
|
@ -1,43 +1,38 @@
|
|||
export class CozyLogger {
|
||||
|
||||
static _instance = null;
|
||||
class CozyLoggerClass {
|
||||
|
||||
static init(enabled) {
|
||||
if (!CozyLogger._instance) {
|
||||
CozyLogger._instance = new CozyLogger(enabled);
|
||||
}
|
||||
return CozyLogger._instance;
|
||||
return new CozyLoggerClass(enabled);
|
||||
}
|
||||
|
||||
static enable() {
|
||||
CozyLogger._instance.enabled = true;
|
||||
enable() {
|
||||
this.enabled = true;
|
||||
}
|
||||
static disable() {
|
||||
CozyLogger._instance.enabled = false;
|
||||
disable() {
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
static group(name) {
|
||||
if (CozyLogger._instance.enabled) {
|
||||
group(name) {
|
||||
if (this.enabled) {
|
||||
console.group(name);
|
||||
}
|
||||
}
|
||||
static groupEnd() {
|
||||
if (CozyLogger._instance.enabled) {
|
||||
groupEnd() {
|
||||
if (this.enabled) {
|
||||
console.groupEnd();
|
||||
}
|
||||
}
|
||||
|
||||
static debug(...args) {
|
||||
if (CozyLogger._instance.enabled) {
|
||||
debug(...args) {
|
||||
if (this.enabled) {
|
||||
console.log('CozyNest:DEBUG:',...args);
|
||||
}
|
||||
}
|
||||
|
||||
static log(...args) {
|
||||
log(...args) {
|
||||
console.log('CozyNest:',...args);
|
||||
}
|
||||
|
||||
static error(...args) {
|
||||
error(...args) {
|
||||
console.error('CozyNest:',...args);
|
||||
}
|
||||
|
||||
|
|
@ -46,11 +41,8 @@ export class CozyLogger {
|
|||
}
|
||||
}
|
||||
|
||||
if (import.meta.env.VITE_CONTEXT === 'DEV') {
|
||||
CozyLogger.init(true);
|
||||
}
|
||||
else {
|
||||
CozyLogger.init(false);
|
||||
}
|
||||
const isDev = import.meta.env.VITE_CONTEXT === 'DEV'
|
||||
export const CozyLogger = CozyLoggerClass.init(isDev);
|
||||
export const CozyLoggerPrompt = CozyLoggerClass.init(import.meta.env.PROMPT_LOGGING === 1);
|
||||
|
||||
window.CozyLogger = CozyLogger;
|
||||
|
|
@ -16,7 +16,14 @@ export function Row(props) {
|
|||
}
|
||||
|
||||
export const RowFullWidth = (props) => {
|
||||
return <Row {...props} style={{width: '100%', justifyContent: 'space-between', gap: '30px'}}/>
|
||||
|
||||
const style = {width: '100%', justifyContent: 'space-between', gap: '30px'}
|
||||
|
||||
if (props.style) {
|
||||
Object.assign(style, props.style)
|
||||
}
|
||||
|
||||
return <Row {...props} style={style}/>
|
||||
}
|
||||
|
||||
//component to wrap flex column
|
||||
|
|
|
|||
|
|
@ -1043,11 +1043,15 @@ canvas.nevysha {
|
|||
background-color: var(--block-background-fill) !important;
|
||||
width: 75vw;
|
||||
right: 0;
|
||||
height: calc(100% - (100px + var(--menu-top-height)));
|
||||
height: calc(100% - (95px + var(--menu-top-height)));
|
||||
top: calc(75px + var(--menu-top-height));
|
||||
padding-right: 15px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border: 1px solid #3f3f3f;
|
||||
}
|
||||
.nevysha-light .slide-right-browser-panel {
|
||||
border: 1px solid var(--ae-input-border-color);
|
||||
}
|
||||
.extra-network-subdirs {
|
||||
overflow: scroll;
|
||||
|
|
@ -1359,6 +1363,9 @@ textarea.nevysha-image-browser-folder {
|
|||
.markdown-body > ul > li {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.markdown-body em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
|
||||
/*Style to apply when #nevysha_other_tabs is empty*/
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import {CozyLogger} from "./CozyLogger.js";
|
||||
import DOM_IDS from "./dom_ids.js";
|
||||
|
||||
export const getTheme = (modeFromConfig) => {
|
||||
modeFromConfig = modeFromConfig || COZY_NEST_CONFIG.color_mode
|
||||
|
|
@ -96,6 +97,13 @@ export const hasCozyNestNo = () => {
|
|||
return false;
|
||||
}
|
||||
|
||||
export function hideNativeUiExtraNetworkElement(prefix) {
|
||||
const triggerButton = document.querySelector(`button#${DOM_IDS.get('extra_networks_btn')(prefix)}`)
|
||||
triggerButton.style.display = 'none'
|
||||
const tabs = document.querySelector(`div#${prefix}_extra_networks`)
|
||||
tabs.style.display = 'none';
|
||||
}
|
||||
|
||||
//dummy method
|
||||
export const dummyLoraCard = () => {
|
||||
const container = document.querySelector("#txt2img_lora_cards");
|
||||
|
|
|
|||
|
|
@ -1,354 +0,0 @@
|
|||
:root {
|
||||
--ctp-rosewater: #dc8a78;
|
||||
--ctp-flamingo: #dd7878;
|
||||
--ctp-pink: #ea76cb;
|
||||
--ctp-mauve: #8839ef;
|
||||
--ctp-red: #d20f39;
|
||||
--ctp-maroon: #e64553;
|
||||
--ctp-peach: #fe640b;
|
||||
--ctp-yellow: #df8e1d;
|
||||
--ctp-green: #40a02b;
|
||||
--ctp-teal: #179299;
|
||||
--ctp-sky: #04a5e5;
|
||||
--ctp-sapphire: #209fb5;
|
||||
--ctp-blue: #1e66f5;
|
||||
--ctp-lavender: #7287fd;
|
||||
--ctp-text: #4c4f69;
|
||||
--ctp-subtext1: #5c5f77;
|
||||
--ctp-subtext0: #6c6f85;
|
||||
--ctp-overlay2: #7c7f93;
|
||||
--ctp-overlay1: #8c8fa1;
|
||||
--ctp-overlay0: #9ca0b0;
|
||||
--ctp-surface2: #acb0be;
|
||||
--ctp-surface1: #bcc0cc;
|
||||
--ctp-surface0: #ccd0da;
|
||||
--ctp-base: #eff1f5;
|
||||
--ctp-mantle: #e6e9ef;
|
||||
--ctp-crust: #dce0e8;
|
||||
--shadow: #dbdfef;
|
||||
--ctp-accent: var(--ctp-maroon);
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
:root,
|
||||
.dark {
|
||||
--body-background-fill: var(--background-fill-primary);
|
||||
--body-text-color: var(--ctp-subtext0);
|
||||
--color-accent-soft: var(--ctp-surface0);
|
||||
--background-fill-primary: var(--ctp-mantle);
|
||||
--background-fill-secondary: var(--ctp-base);
|
||||
--border-color-accent: var(--ctp-surface0);
|
||||
--border-color-primary: var(--ctp-surface1);
|
||||
--link-text-color-active: var(--ctp-subtext0);
|
||||
--link-text-color: var(--ctp-subtext0);
|
||||
--link-text-color-hover: var(--ctp-accent);
|
||||
--link-text-color-visited: var(--ctp-subtext0);
|
||||
--body-text-color-subdued: var(--ctp-subtext0);
|
||||
--shadow-spread: 1px;
|
||||
--block-background-fill: var(--ctp-mantle);
|
||||
--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(--ctp-text);
|
||||
--block_shadow: None;
|
||||
--block_title_background_fill: None;
|
||||
--block_title_border_color: None;
|
||||
--block_title_border_width: None;
|
||||
--block-title-text-color: var(--ctp-text);
|
||||
--panel-background-fill: var(--background-fill-secondary);
|
||||
--panel-border-color: var(--border-color-primary);
|
||||
--panel_border_width: None;
|
||||
--checkbox-background-color: var(--ctp-surface1);
|
||||
--checkbox-background-color-focus: var(--checkbox-background-color);
|
||||
--checkbox-background-color-hover: var(--checkbox-background-color);
|
||||
--checkbox-background-color-selected: var(--ctp-accent);
|
||||
--checkbox-border-color: var(--ctp-surface0);
|
||||
--checkbox-border-color-focus: var(--ctp-overlay0);
|
||||
--checkbox-border-color-hover: var(--ctp-surface0);
|
||||
--checkbox-border-color-selected: var(--ctp-overlay0);
|
||||
--checkbox-border-width: var(--input-border-width);
|
||||
--checkbox-label-background-fill: var(--ctp-surface0);
|
||||
--checkbox-label-background-fill-hover: var(--ctp-surface0);
|
||||
--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: var(--ctp-red);
|
||||
--input-background-fill: var(--ctp-base);
|
||||
--input-background-fill-focus: var(--input-background-fill);
|
||||
--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: None;
|
||||
--input-shadow-focus: 0 0 0 var(--shadow-spread) var(--neutral-700),
|
||||
var(--shadow-inset);
|
||||
--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: var(--neutral-950);
|
||||
--table-odd-background-fill: var(--neutral-900);
|
||||
--table-row-focus: var(--color-accent-soft);
|
||||
--button-border-width: var(--input-border-width);
|
||||
--button-cancel-background-fill: var(--ctp-red);
|
||||
--button-cancel-background-fill-hover: var(--ctp-red);
|
||||
--button-cancel-border-color: var(--ctp-red);
|
||||
--button-cancel-border-color-hover: var(--button-cancel-border-color);
|
||||
--button-cancel-text-color: var(--ctp-base);
|
||||
--button-cancel-text-color-hover: var(--button-cancel-text-color);
|
||||
--button-primary-background-fill: var(--ctp-accent);
|
||||
--button-primary-background-fill-hover: var(--ctp-accent);
|
||||
--button-primary-border-color: var(--ctp-accent);
|
||||
--button-primary-border-color-hover: var(--button-primary-border-color);
|
||||
--button-primary-text-color: var(--ctp-base);
|
||||
--button-primary-text-color-hover: var(--button-primary-text-color);
|
||||
--button-secondary-background-fill: var(--ctp-surface0);
|
||||
--button-secondary-background-fill-hover: var(--ctp-surface0);
|
||||
--button-secondary-border-color: var(--ctp-surface0);
|
||||
--button-secondary-border-color-hover: var(--button-secondary-border-color);
|
||||
--button-secondary-text-color: var(--ctp-text);
|
||||
--button-secondary-text-color-hover: var(--button-secondary-text-color);
|
||||
}
|
||||
|
||||
.gradio-button:hover {
|
||||
filter: brightness(130%);
|
||||
}
|
||||
|
||||
input[type='range']::-ms-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
input[type='range']::-ms-fill-lower {
|
||||
background: var(--ctp-accent);
|
||||
border-radius: 10px;
|
||||
}
|
||||
input[type='range']::-ms-fill-upper {
|
||||
background: var(--ctp-overlay0);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
input[type='range']:focus::-ms-fill-lower {
|
||||
background: var(--ctp-accent);
|
||||
}
|
||||
input[type='range']:focus::-ms-fill-upper {
|
||||
background: var(--ctp-overlay0);
|
||||
}
|
||||
|
||||
/* Chrome, Safari, Edge, Opera */
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
input[type='number'] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
.gr-box > div > div > input.gr-text-input {
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
.progressDiv .progress {
|
||||
background: var(--ctp-accent);
|
||||
color: var(--shadow);
|
||||
}
|
||||
|
||||
.dark .progressDiv,
|
||||
.progressDiv {
|
||||
background-color: var(--ctp-surface0);
|
||||
}
|
||||
|
||||
input[type='range'] {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
input[type='range'] {
|
||||
color: var(--accent);
|
||||
--thumb-height: 0.5em;
|
||||
--track-height: 0.125em;
|
||||
--track-color: var(--ctp-surface0);
|
||||
--brightness-hover: 130%;
|
||||
--brightness-down: 80%;
|
||||
--clip-edges: 0.125em;
|
||||
}
|
||||
|
||||
input[type='range'].win10-thumb {
|
||||
color: var(--ctp-accent);
|
||||
|
||||
--thumb-height: 0.5em;
|
||||
--thumb-width: 0.5em;
|
||||
--clip-edges: 0.0125em;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
input[type='range'] {
|
||||
color: var(--ctp-accent);
|
||||
--track-color: var(--ctp-surface0);
|
||||
}
|
||||
|
||||
input[type='range'].win10-thumb {
|
||||
color: var(--ctp-accent);
|
||||
}
|
||||
}
|
||||
|
||||
/* === range commons === */
|
||||
input[type='range'] {
|
||||
position: relative;
|
||||
background: #fff0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
input[type='range']:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
input[type='range']:disabled {
|
||||
filter: grayscale(1);
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* === WebKit specific styles === */
|
||||
input[type='range'],
|
||||
input[type='range']::-webkit-slider-runnable-track,
|
||||
input[type='range']::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
transition: all ease 100ms;
|
||||
height: var(--thumb-height);
|
||||
}
|
||||
|
||||
input[type='range']::-webkit-slider-runnable-track,
|
||||
input[type='range']::-webkit-slider-thumb {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
input[type='range']::-webkit-slider-thumb {
|
||||
--thumb-radius: calc((var(--thumb-height) * 0.5) - 1px);
|
||||
--clip-top: calc((var(--thumb-height) - var(--track-height)) * 0.5 - 0.5px);
|
||||
--clip-bottom: calc(var(--thumb-height) - var(--clip-top));
|
||||
--clip-further: calc(100% + 1px);
|
||||
--box-fill: calc(-100vmax - var(--thumb-width, var(--thumb-height))) 0 0
|
||||
100vmax currentColor;
|
||||
|
||||
width: var(--thumb-width, var(--thumb-height));
|
||||
background: linear-gradient(currentColor 0 0) scroll no-repeat left center /
|
||||
50% calc(var(--track-height) + 1px);
|
||||
background-color: currentColor;
|
||||
box-shadow: var(--box-fill);
|
||||
border-radius: var(--thumb-width, var(--thumb-height));
|
||||
|
||||
filter: brightness(100%);
|
||||
clip-path: polygon(
|
||||
100% -1px,
|
||||
var(--clip-edges) -1px,
|
||||
0 var(--clip-top),
|
||||
-100vmax var(--clip-top),
|
||||
-100vmax var(--clip-bottom),
|
||||
0 var(--clip-bottom),
|
||||
var(--clip-edges) 100%,
|
||||
var(--clip-further) var(--clip-further)
|
||||
);
|
||||
}
|
||||
|
||||
input[type='range']:hover::-webkit-slider-thumb {
|
||||
filter: brightness(var(--brightness-hover));
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
input[type='range']:active::-webkit-slider-thumb {
|
||||
filter: brightness(var(--brightness-down));
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
input[type='range']::-webkit-slider-runnable-track {
|
||||
background: linear-gradient(var(--track-color) 0 0) scroll no-repeat center /
|
||||
100% calc(var(--track-height) + 1px);
|
||||
}
|
||||
|
||||
input[type='range']:disabled::-webkit-slider-thumb {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* === Firefox specific styles === */
|
||||
input[type='range'],
|
||||
input[type='range']::-moz-range-track,
|
||||
input[type='range']::-moz-range-thumb {
|
||||
appearance: none;
|
||||
transition: all ease 100ms;
|
||||
height: var(--thumb-height);
|
||||
}
|
||||
|
||||
input[type='range']::-moz-range-track,
|
||||
input[type='range']::-moz-range-thumb,
|
||||
input[type='range']::-moz-range-progress {
|
||||
background: #fff0;
|
||||
}
|
||||
|
||||
input[type='range']::-moz-range-thumb {
|
||||
background: currentColor;
|
||||
border: 0;
|
||||
width: var(--thumb-width, var(--thumb-height));
|
||||
border-radius: var(--thumb-width, var(--thumb-height));
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
input[type='range']:active::-moz-range-thumb {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
input[type='range']::-moz-range-track {
|
||||
width: 100%;
|
||||
background: var(--track-color);
|
||||
}
|
||||
|
||||
input[type='range']::-moz-range-progress {
|
||||
appearance: none;
|
||||
background: currentColor;
|
||||
transition-delay: 30ms;
|
||||
}
|
||||
|
||||
input[type='range']::-moz-range-track,
|
||||
input[type='range']::-moz-range-progress {
|
||||
height: calc(var(--track-height) + 1px);
|
||||
border-radius: var(--track-height);
|
||||
}
|
||||
|
||||
input[type='range']::-moz-range-thumb,
|
||||
input[type='range']::-moz-range-progress {
|
||||
filter: brightness(100%);
|
||||
}
|
||||
|
||||
input[type='range']:hover::-moz-range-thumb,
|
||||
input[type='range']:hover::-moz-range-progress {
|
||||
filter: brightness(var(--brightness-hover));
|
||||
}
|
||||
|
||||
input[type='range']:active::-moz-range-thumb,
|
||||
input[type='range']:active::-moz-range-progress {
|
||||
filter: brightness(var(--brightness-down));
|
||||
}
|
||||
|
||||
input[type='range']:disabled::-moz-range-thumb {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* all of the input range stuff is from this guy*/
|
||||
/* Shout out to them https://codepen.io/ShadowShahriar/pen/zYPPYrQ */
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
import React, {useEffect} from "react";
|
||||
import {EventBus} from "./Module.jsx";
|
||||
import {
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
Button,
|
||||
useDisclosure, useToast
|
||||
} from "@chakra-ui/react";
|
||||
|
||||
export function CozyModalSimple() {
|
||||
|
||||
const listen = 'CozyModalSimple'
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const [text, setText] = React.useState('')
|
||||
|
||||
useEffect(() => {
|
||||
// listen to events on listen
|
||||
|
||||
const _eventFn = ({msg}) => {
|
||||
setText(msg)
|
||||
onOpen()
|
||||
}
|
||||
|
||||
EventBus.on(listen, _eventFn)
|
||||
return () => {
|
||||
// unlisten to events on listen
|
||||
EventBus.off(listen, _eventFn)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>Modal Title</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<p>{text}</p>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button colorScheme='blue' mr={3} onClick={onClose}>
|
||||
Close
|
||||
</Button>
|
||||
<Button variant='ghost'>Secondary Action</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function CozyModalRich() {
|
||||
|
||||
const listen = 'CozyModalRich'
|
||||
|
||||
useEffect(() => {
|
||||
// listen to events on listen
|
||||
|
||||
const _eventFn = (args) => {
|
||||
CozyLogger.debug("CozyModalRoot event", listen, args);
|
||||
}
|
||||
|
||||
EventBus.on(listen, _eventFn)
|
||||
return () => {
|
||||
// unlisten to events on listen
|
||||
EventBus.off(listen, _eventFn)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="CozyModalRich">
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function CozyToast() {
|
||||
const listen = 'CozyToast'
|
||||
const toast = useToast()
|
||||
|
||||
useEffect(() => {
|
||||
// listen to events on listen
|
||||
|
||||
const _eventFn = ({title, msg, status}) => {
|
||||
toast({
|
||||
title: title,
|
||||
description: msg,
|
||||
status: status,
|
||||
duration: 9000,
|
||||
isClosable: true,
|
||||
})
|
||||
}
|
||||
|
||||
EventBus.on(listen, _eventFn)
|
||||
return () => {
|
||||
// unlisten to events on listen
|
||||
EventBus.off(listen, _eventFn)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="CozyToast" />
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import {ChakraProvider} from "@chakra-ui/react";
|
||||
import {theme} from "../../chakra/chakra-theme.ts";
|
||||
import {CozyModalRich, CozyModalSimple, CozyToast} from "./Modal.jsx";
|
||||
import EventEmitter from 'eventemitter3';
|
||||
|
||||
class EventBusClass extends EventEmitter{
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
export const EventBus = new EventBusClass();
|
||||
|
||||
/**
|
||||
* Split Module and Modal.jsx to be able to use hmr
|
||||
*/
|
||||
|
||||
let _ready = false;
|
||||
|
||||
function prepareReactHost() {
|
||||
// insert a div at the end of the body
|
||||
const _hostCozyModalSimple =
|
||||
`<div id="CozyModalSimple"/>`;
|
||||
const _hostCozyModalRich =
|
||||
`<div id="CozyModalRich"/>`;
|
||||
const _hostCozyToast =
|
||||
`<div id="CozyToast"/>`;
|
||||
|
||||
document.body.insertAdjacentHTML("beforeend", _hostCozyModalSimple);
|
||||
document.body.insertAdjacentHTML("beforeend", _hostCozyModalRich);
|
||||
document.body.insertAdjacentHTML("beforeend", _hostCozyToast);
|
||||
|
||||
ReactDOM.createRoot(document.getElementById(`CozyModalSimple`)).render(
|
||||
<React.StrictMode>
|
||||
<ChakraProvider theme={theme} >
|
||||
<CozyModalSimple />
|
||||
</ChakraProvider>
|
||||
</React.StrictMode>,
|
||||
)
|
||||
|
||||
ReactDOM.createRoot(document.getElementById(`CozyModalRich`)).render(
|
||||
<React.StrictMode>
|
||||
<ChakraProvider theme={theme} >
|
||||
<CozyModalRich />
|
||||
</ChakraProvider>
|
||||
</React.StrictMode>,
|
||||
)
|
||||
|
||||
ReactDOM.createRoot(document.getElementById(`CozyToast`)).render(
|
||||
<React.StrictMode>
|
||||
<ChakraProvider theme={theme} >
|
||||
<CozyToast />
|
||||
</ChakraProvider>
|
||||
</React.StrictMode>,
|
||||
)
|
||||
|
||||
_ready = true;
|
||||
}
|
||||
|
||||
let CozyModal = {
|
||||
prepareReactHost,
|
||||
showModalSimple: (msg) => EventBus.emit('CozyModalSimple', {msg}),
|
||||
showModalRich: () => EventBus.emit('CozyModalRich'),
|
||||
showToast: (status, title, msg) => EventBus.emit('CozyToast', {status, title, msg}),
|
||||
};
|
||||
//hook on modal event
|
||||
window.ModalEventBus = EventBus;
|
||||
window.CozyModal = CozyModal;
|
||||
export default CozyModal
|
||||
|
|
@ -30,6 +30,7 @@ import clearGeneratedImage from './tweaks/clear-generated-image.js'
|
|||
import {createAlertDiv, showAlert} from "./tweaks/cozy-alert.js";
|
||||
import DOM_IDS from "./dom_ids.js";
|
||||
import CozyNestEventBus from "../CozyNestEventBus.js";
|
||||
import Modal from './modal/Module.jsx'
|
||||
|
||||
|
||||
const addDraggable = ({prefix}) => {
|
||||
|
|
@ -335,11 +336,11 @@ async function loadVersionData() {
|
|||
|
||||
//regex to replace [x] with a checkmark
|
||||
const regex = /\[x\]/g;
|
||||
remote_patchnote = remote_patchnote.replace(regex, ""); //TODO add icon ?
|
||||
remote_patchnote = remote_patchnote.replace(regex, ""); //TODO NEVYSHA add icon ?
|
||||
|
||||
//regex to replace [ ] with a cross
|
||||
const regex2 = /\[ \]/g;
|
||||
remote_patchnote = remote_patchnote.replace(regex2, ""); //TODO add icon ?
|
||||
remote_patchnote = remote_patchnote.replace(regex2, ""); //TODO NEVYSHA add icon ?
|
||||
|
||||
|
||||
const converter = new showdown.Converter();
|
||||
|
|
@ -609,7 +610,7 @@ function buildRightSlidePanelFor(label, buttonLabel, rightPanBtnWrapper, tab, pr
|
|||
//create a panel to display Cozy Image Browser
|
||||
const cozyImgBrowserPanel =
|
||||
`<div id="${label}_panel" class="nevysha slide-right-browser-panel" style="display: none">
|
||||
<div class="nevysha slide-right-browser-panel-container nevysha-scrollable">
|
||||
<div class="nevysha slide-right-browser-panel-container nevysha-scrollable ${label}_panel_inner">
|
||||
<div class="nevysha" id="${label}-react"/>
|
||||
</div>
|
||||
</div>`;
|
||||
|
|
@ -624,6 +625,11 @@ function buildRightSlidePanelFor(label, buttonLabel, rightPanBtnWrapper, tab, pr
|
|||
if (cozyImgBrowserPanelWidth) {
|
||||
cozyImgBrowserPanelWrapper.style.width = cozyImgBrowserPanelWidth;
|
||||
}
|
||||
else {
|
||||
//get window width
|
||||
const width = window.innerWidth;
|
||||
cozyImgBrowserPanelWrapper.style.width = `${Math.round(width / 2)}px`;
|
||||
}
|
||||
cozyImgBrowserPanelWrapper.appendChild(lineWrapper)
|
||||
|
||||
//add a close button inside the line
|
||||
|
|
@ -685,7 +691,7 @@ function buildRightSlidePanelFor(label, buttonLabel, rightPanBtnWrapper, tab, pr
|
|||
function close() {
|
||||
const panel = document.querySelector(`#${label}_panel`);
|
||||
if (panel.style.display !== 'none') {
|
||||
$(panel).animate({"margin-right": `-=${panel.offsetWidth}`}, 1, () => {
|
||||
$(panel).animate({"margin-right": `-=${panel.offsetWidth}`}, 150, () => {
|
||||
panel.style.display = 'none'
|
||||
});
|
||||
}
|
||||
|
|
@ -726,7 +732,7 @@ function createRightWrapperDiv() {
|
|||
tab.insertAdjacentElement('beforeend', rightPanBtnWrapper);
|
||||
|
||||
if (COZY_NEST_CONFIG.enable_extra_network_tweaks === true) {
|
||||
buildRightSlidePanelFor('cozy-txt2img-extra-network', 'Extra Network'
|
||||
buildRightSlidePanelFor('cozy-txt2img-extra-network', 'Extra Networks'
|
||||
, rightPanBtnWrapper, tab, 'txt2img');
|
||||
document.getElementById('cozy-txt2img-extra-network-react').classList.add('cozy-extra-network')
|
||||
|
||||
|
|
@ -735,6 +741,12 @@ function createRightWrapperDiv() {
|
|||
document.getElementById('cozy-img2img-extra-network-react').classList.add('cozy-extra-network')
|
||||
document.querySelector(`#cozy-img2img-extra-network_right_button`).style.display = 'none';
|
||||
}
|
||||
if (COZY_NEST_CONFIG.enable_cozy_extra_networks === true) {
|
||||
//Cozy Nest reimplementation of extra networks
|
||||
//if both are enabled, we use the Cozy Extra Networks label
|
||||
const buttonLabel = COZY_NEST_CONFIG.enable_extra_network_tweaks ? 'Cozy Extra Networks' : 'Extra Networks';
|
||||
buildRightSlidePanelFor('cozy-extra-network', buttonLabel, rightPanBtnWrapper, tab);
|
||||
}
|
||||
if (COZY_NEST_CONFIG.disable_image_browser !== true) {
|
||||
buildRightSlidePanelFor('cozy-img-browser', 'Cozy Image Browser', rightPanBtnWrapper, tab);
|
||||
}
|
||||
|
|
@ -995,6 +1007,9 @@ const onLoad = (done, error) => {
|
|||
// log time for onLoad execution after gradio has loaded
|
||||
SimpleTimer.time(COZY_NEST_DOM_TWEAK_LOAD_DURATION);
|
||||
|
||||
// load modal module
|
||||
Modal.prepareReactHost();
|
||||
|
||||
// check for gradio theme (vlad's fork)
|
||||
if (document.querySelector('#setting_gradio_theme input')) {
|
||||
const gradioTheme = document.querySelector('#setting_gradio_theme input').value
|
||||
|
|
@ -1038,6 +1053,7 @@ const onLoad = (done, error) => {
|
|||
|
||||
//create a wrapper div on the right for slidable panels
|
||||
createRightWrapperDiv();
|
||||
|
||||
let lastTab = get_uiCurrentTabContent().id;
|
||||
onUiTabChange(() => {
|
||||
CozyLogger.debug(`onUiTabChange newTab:${get_uiCurrentTabContent().id}, lastTab:${lastTab}`);
|
||||
|
|
@ -1136,7 +1152,7 @@ const onLoad = (done, error) => {
|
|||
if (window.location.href.includes('__theme')) {
|
||||
showAlert(
|
||||
"Warning",
|
||||
"The __theme parameter is deprecated. Please use Cozy Nest settings instead.",
|
||||
"The __theme parameter is deprecated for CozyNest. Please remove it from URL and use Cozy Nest settings instead.",
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
const rotate45 = {
|
||||
transform: 'rotate(-45deg)',
|
||||
}
|
||||
const fillRed = {
|
||||
fill: 'red',
|
||||
}
|
||||
|
||||
export const SvgForReact = {
|
||||
link: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z"/></svg>,
|
||||
image: <svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512"><path d="M448 80c8.8 0 16 7.2 16 16V415.8l-5-6.5-136-176c-4.5-5.9-11.6-9.3-19-9.3s-14.4 3.4-19 9.3L202 340.7l-30.5-42.7C167 291.7 159.8 288 152 288s-15 3.7-19.5 10.1l-80 112L48 416.3l0-.3V96c0-8.8 7.2-16 16-16H448zM64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zm80 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>,
|
||||
arrow: <svg style={rotate45} xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512"><path d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l128 128c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 288 480 288c17.7 0 32-14.3 32-32s-14.3-32-32-32l-370.7 0 73.4-73.4c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-128 128z"/></svg>,
|
||||
magicWand: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M234.7 42.7L197 56.8c-3 1.1-5 4-5 7.2s2 6.1 5 7.2l37.7 14.1L248.8 123c1.1 3 4 5 7.2 5s6.1-2 7.2-5l14.1-37.7L315 71.2c3-1.1 5-4 5-7.2s-2-6.1-5-7.2L277.3 42.7 263.2 5c-1.1-3-4-5-7.2-5s-6.1 2-7.2 5L234.7 42.7zM46.1 395.4c-18.7 18.7-18.7 49.1 0 67.9l34.6 34.6c18.7 18.7 49.1 18.7 67.9 0L529.9 116.5c18.7-18.7 18.7-49.1 0-67.9L495.3 14.1c-18.7-18.7-49.1-18.7-67.9 0L46.1 395.4zM484.6 82.6l-105 105-23.3-23.3 105-105 23.3 23.3zM7.5 117.2C3 118.9 0 123.2 0 128s3 9.1 7.5 10.8L64 160l21.2 56.5c1.7 4.5 6 7.5 10.8 7.5s9.1-3 10.8-7.5L128 160l56.5-21.2c4.5-1.7 7.5-6 7.5-10.8s-3-9.1-7.5-10.8L128 96 106.8 39.5C105.1 35 100.8 32 96 32s-9.1 3-10.8 7.5L64 96 7.5 117.2zm352 256c-4.5 1.7-7.5 6-7.5 10.8s3 9.1 7.5 10.8L416 416l21.2 56.5c1.7 4.5 6 7.5 10.8 7.5s9.1-3 10.8-7.5L480 416l56.5-21.2c4.5-1.7 7.5-6 7.5-10.8s-3-9.1-7.5-10.8L480 352l-21.2-56.5c-1.7-4.5-6-7.5-10.8-7.5s-9.1 3-10.8 7.5L416 352l-56.5 21.2z"/></svg>,
|
||||
eye: <svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 576 512"><path d="M288 80c-65.2 0-118.8 29.6-159.9 67.7C89.6 183.5 63 226 49.4 256c13.6 30 40.2 72.5 78.6 108.3C169.2 402.4 222.8 432 288 432s118.8-29.6 159.9-67.7C486.4 328.5 513 286 526.6 256c-13.6-30-40.2-72.5-78.6-108.3C406.8 109.6 353.2 80 288 80zM95.4 112.6C142.5 68.8 207.2 32 288 32s145.5 36.8 192.6 80.6c46.8 43.5 78.1 95.4 93 131.1c3.3 7.9 3.3 16.7 0 24.6c-14.9 35.7-46.2 87.7-93 131.1C433.5 443.2 368.8 480 288 480s-145.5-36.8-192.6-80.6C48.6 356 17.3 304 2.5 268.3c-3.3-7.9-3.3-16.7 0-24.6C17.3 208 48.6 156 95.4 112.6zM288 336c44.2 0 80-35.8 80-80s-35.8-80-80-80c-.7 0-1.3 0-2 0c1.3 5.1 2 10.5 2 16c0 35.3-28.7 64-64 64c-5.5 0-10.9-.7-16-2c0 .7 0 1.3 0 2c0 44.2 35.8 80 80 80zm0-208a128 128 0 1 1 0 256 128 128 0 1 1 0-256z"/></svg>,
|
||||
eyeSlash: <svg style={fillRed} xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 640 512"><path d="M38.8 5.1C28.4-3.1 13.3-1.2 5.1 9.2S-1.2 34.7 9.2 42.9l592 464c10.4 8.2 25.5 6.3 33.7-4.1s6.3-25.5-4.1-33.7L525.6 386.7c39.6-40.6 66.4-86.1 79.9-118.4c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C465.5 68.8 400.8 32 320 32c-68.2 0-125 26.3-169.3 60.8L38.8 5.1zm151 118.3C226 97.7 269.5 80 320 80c65.2 0 118.8 29.6 159.9 67.7C518.4 183.5 545 226 558.6 256c-12.6 28-36.6 66.8-70.9 100.9l-53.8-42.2c9.1-17.6 14.2-37.5 14.2-58.7c0-70.7-57.3-128-128-128c-32.2 0-61.7 11.9-84.2 31.5l-46.1-36.1zM394.9 284.2l-81.5-63.9c4.2-8.5 6.6-18.2 6.6-28.3c0-5.5-.7-10.9-2-16c.7 0 1.3 0 2 0c44.2 0 80 35.8 80 80c0 9.9-1.8 19.4-5.1 28.2zm9.4 130.3C378.8 425.4 350.7 432 320 432c-65.2 0-118.8-29.6-159.9-67.7C121.6 328.5 95 286 81.4 256c8.3-18.4 21.5-41.5 39.4-64.8L83.1 161.5C60.3 191.2 44 220.8 34.5 243.7c-3.3 7.9-3.3 16.7 0 24.6c14.9 35.7 46.2 87.7 93 131.1C174.5 443.2 239.2 480 320 480c47.8 0 89.9-12.9 126.2-32.5l-41.9-33zM192 256c0 70.7 57.3 128 128 128c13.3 0 26.1-2 38.2-5.8L302 334c-23.5-5.4-43.1-21.2-53.7-42.3l-56.1-44.2c-.2 2.8-.3 5.6-.3 8.5z"/></svg>,
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -17,9 +17,12 @@
|
|||
"eventemitter3": "^5.0.1",
|
||||
"jquery": "^3.7.0",
|
||||
"react": "^18.2.0",
|
||||
"react-accessible-treeview": "^2.6.1",
|
||||
"react-ace": "^10.1.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-drag-drop-files": "^2.3.10",
|
||||
"react-icons": "^4.10.1",
|
||||
"react-select": "^5.7.3",
|
||||
"react-spinners": "^0.13.8",
|
||||
"react-use-websocket": "^3.0.0",
|
||||
|
|
@ -34,6 +37,7 @@
|
|||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.3.4",
|
||||
"sass": "^1.63.6",
|
||||
"typescript": "^5.0.4",
|
||||
"vite": "^4.3.2"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
.App {
|
||||
/*TODO calc this*/
|
||||
width: max(800px, 50vw);
|
||||
height: fit-content;
|
||||
min-height: 150px;
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ import {saveCozyNestConfig} from "../main/nevysha-cozy-nest.js";
|
|||
import {ButtonWithConfirmDialog} from "../chakra/ButtonWithConfirmDialog.jsx";
|
||||
|
||||
|
||||
function DialogWrapper({children, isVisible}) {
|
||||
export function DialogWrapper({children, isVisible}) {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure({isOpen: isVisible})
|
||||
const cancelRef = useRef()
|
||||
|
||||
|
|
@ -215,7 +215,7 @@ export function App() {
|
|||
<Tab>Main Settings</Tab>
|
||||
<Tab>Image Browser Settings</Tab>
|
||||
<Tab>Cozy Prompt Settings</Tab>
|
||||
<Tab>Others</Tab>
|
||||
<Tab>Cozy Nest Modules</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanels>
|
||||
|
|
@ -427,14 +427,22 @@ export function App() {
|
|||
isChecked={!config.disable_image_browser}
|
||||
onChange={(e) => setConfig({...config, disable_image_browser: !e.target.checked})}
|
||||
>Enable image browser (Reload UI required)</Checkbox>
|
||||
<Checkbox
|
||||
isChecked={config.enable_extra_network_tweaks}
|
||||
onChange={(e) => setConfig({...config, enable_extra_network_tweaks: e.target.checked})}
|
||||
>Enable extra network tweaks</Checkbox>
|
||||
<Checkbox
|
||||
isChecked={config.enable_cozy_prompt}
|
||||
onChange={(e) => setConfig({...config, enable_cozy_prompt: e.target.checked})}
|
||||
>Enable Cozy Prompt</Checkbox>
|
||||
|
||||
<span style={{marginTop: '25px'}}>Extra Networks</span>
|
||||
<span>You probably only want one of those</span>
|
||||
<Checkbox
|
||||
isChecked={config.enable_extra_network_tweaks}
|
||||
onChange={(e) => setConfig({...config, enable_extra_network_tweaks: e.target.checked})}
|
||||
>Tweaks existing : just move existing component in side panel (will drop support soon)</Checkbox>
|
||||
<Checkbox
|
||||
isChecked={config.enable_cozy_extra_networks}
|
||||
onChange={(e) => setConfig({...config, enable_cozy_extra_networks: e.target.checked})}
|
||||
>Cozy Nest Extra Network new implementation</Checkbox>
|
||||
|
||||
</Column>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
|
||||
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# add the CozyNest extension to the sys.path.
|
||||
sys.path.append(parent_dir)
|
||||
|
||||
|
||||
from scripts.cozynest_image_browser import start_server, start_server_in_dedicated_process
|
||||
|
||||
# call start_server()
|
||||
|
||||
# main
|
||||
|
||||
if __name__ == '__main__':
|
||||
port = 3333
|
||||
|
||||
if len(sys.argv) < 1:
|
||||
print("CozyNest: No images folder specified")
|
||||
exit()
|
||||
|
||||
# get the images folder from arguments
|
||||
# it can be any number of arguments, add all of them in the images_folders list
|
||||
images_folders = []
|
||||
for i in range(1, len(sys.argv)):
|
||||
images_folders.append(sys.argv[i])
|
||||
|
||||
# start_server(images_folders, port)
|
||||
start_server_in_dedicated_process(images_folders, port)
|
||||
|
||||
while True:
|
||||
pass
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
import asyncio
|
||||
import json
|
||||
import socket
|
||||
|
||||
import websockets
|
||||
|
||||
|
||||
async def connect_to_socket():
|
||||
async with websockets.connect('ws://localhost:3333') as websocket:
|
||||
try:
|
||||
while True:
|
||||
# Send data to the server
|
||||
data = json.dumps({
|
||||
'what': 'image_saved',
|
||||
'data': {
|
||||
'filename': "filename",
|
||||
'pnginfo': "gen_params.pnginfo",
|
||||
}
|
||||
}).encode('utf-8')
|
||||
await websocket.send(data)
|
||||
|
||||
# Receive response from the server
|
||||
response = await websocket.recv()
|
||||
print("Received response:", response)
|
||||
websocket.close()
|
||||
break
|
||||
|
||||
except websockets.exceptions.ConnectionClosed:
|
||||
print("Connection to socket closed")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Run the connection coroutine
|
||||
asyncio.run(connect_to_socket())
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def is_log_enabled():
|
||||
# check if the file log_enabled exists (in the same folder)
|
||||
# if it does, then check log_enabled value (as json)
|
||||
try:
|
||||
log_config_file = Path(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'log_enabled'))
|
||||
if log_config_file.is_file():
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
# This is a simple logger class that will be used to log messages stdout
|
||||
# check the config if log is enabled
|
||||
class CozyLoggerClass:
|
||||
LOG_ENABLED = is_log_enabled()
|
||||
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
self.log_enabled = CozyLoggerClass.LOG_ENABLED
|
||||
|
||||
def debug(self, message: str):
|
||||
if self.log_enabled:
|
||||
print(f"[{self.name}:DEBUG] {message}")
|
||||
|
||||
def warning(self, message: str):
|
||||
print(f"[{self.name}:WARNING] {message}")
|
||||
|
||||
def info(self, message: str):
|
||||
print(f"[{self.name}:INFO] {message}")
|
||||
|
||||
|
||||
CozyLogger = CozyLoggerClass("Cozy")
|
||||
if CozyLoggerClass.LOG_ENABLED:
|
||||
CozyLogger.warning("Logger enabled. delete 'log_enabled' file to disable")
|
||||
|
||||
CozyLoggerExtNe = CozyLoggerClass("Cozy:ExtNe")
|
||||
CozyLoggerConfig = CozyLoggerClass("Cozy:Config")
|
||||
CozyLoggerImageBrowser = CozyLoggerClass("Cozy:ImageBrowser")
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
from modules import shared
|
||||
from scripts.cozy_lib import Static
|
||||
from scripts.cozy_lib.tools import output_folder_array
|
||||
from scripts.cozy_lib.CozyLogger import CozyLoggerConfig
|
||||
|
||||
|
||||
class CozyNestConfig:
|
||||
def __init__(self):
|
||||
config = self.get_dict_from_config()
|
||||
self.config = {**CozyNestConfig.get_default_settings(), **config}
|
||||
|
||||
def init(self):
|
||||
if self.config['webui'] == 'unknown' and hasattr(shared, 'get_version'):
|
||||
version = shared.get_version()
|
||||
# check if the 'app' is 'sd.next'
|
||||
if version['app'] == 'sd.next':
|
||||
self.config['webui'] = 'sd.next'
|
||||
self.config['fetch_output_folder_from_a1111_settings'] = False
|
||||
else:
|
||||
self.config['webui'] = 'auto1111'
|
||||
self.save_settings(self.config)
|
||||
|
||||
if self.config['webui'] == 'sd.next':
|
||||
self.config['fetch_output_folder_from_a1111_settings'] = False
|
||||
|
||||
# check if cnib_output_folder is empty and/or need to be fetched from a1111 settings
|
||||
cnib_output_folder = self.config.get('cnib_output_folder')
|
||||
is_empty = cnib_output_folder == []
|
||||
if not cnib_output_folder or is_empty:
|
||||
cnib_output_folder = []
|
||||
|
||||
if self.config.get('fetch_output_folder_from_a1111_settings'):
|
||||
# merge cnib_output_folder output_folder_array()
|
||||
cnib_output_folder = cnib_output_folder + list(set(output_folder_array()) - set(cnib_output_folder))
|
||||
|
||||
self.config['cnib_output_folder'] = cnib_output_folder
|
||||
|
||||
# save the merged settings
|
||||
self.save_settings(self.config)
|
||||
|
||||
def migrate(self):
|
||||
current_version = CozyNestConfig.get_version()
|
||||
current_version_code = CozyNestConfig.normalize_version(current_version)
|
||||
|
||||
local_version = self.config.get('version')
|
||||
if not local_version:
|
||||
local_version = '0.0.0'
|
||||
local_version_code = CozyNestConfig.normalize_version(local_version)
|
||||
|
||||
if local_version_code < current_version_code:
|
||||
CozyLoggerConfig.debug(f"current_version: {current_version} current_version_code: {current_version_code}")
|
||||
CozyLoggerConfig.debug(f"local_version: {local_version} local_version_code: {local_version_code}")
|
||||
if local_version_code < CozyNestConfig.normalize_version('2.4.0'):
|
||||
self.config['enable_extra_network_tweaks'] = False
|
||||
self.config['enable_cozy_extra_networks'] = True
|
||||
|
||||
self.config['version'] = current_version
|
||||
|
||||
self.simple_save_settings()
|
||||
|
||||
@staticmethod
|
||||
def get_version():
|
||||
with open(Static.VERSION_FILENAME, 'r') as f:
|
||||
version = json.loads(f.read())
|
||||
f.close()
|
||||
current_version = version['version']
|
||||
return current_version
|
||||
|
||||
@staticmethod
|
||||
def normalize_version(version):
|
||||
# normalize version ie 2.3.4 => 2003004
|
||||
components = [int(x) for x in version.split(".")]
|
||||
normalized_number = 0
|
||||
for i, component in enumerate(components):
|
||||
normalized_number += component * (100 ** (len(components) - i - 1))
|
||||
|
||||
return normalized_number
|
||||
|
||||
def get(self, key):
|
||||
return self.config.get(key)
|
||||
|
||||
def simple_save_settings(self):
|
||||
# create the file in extensions/Cozy-Nest if it doesn't exist
|
||||
if not os.path.exists(Static.CONFIG_FILENAME):
|
||||
open(Static.CONFIG_FILENAME, 'w').close()
|
||||
# save each settings inside the file
|
||||
with open(Static.CONFIG_FILENAME, 'w') as f:
|
||||
f.write(json.dumps(self.config, indent=2))
|
||||
f.close()
|
||||
|
||||
def save_settings(self, settings):
|
||||
self.config = {
|
||||
# always ensure that default settings for cross version compatibility
|
||||
**CozyNestConfig.get_default_settings(),
|
||||
**self.config,
|
||||
**settings
|
||||
}
|
||||
self.simple_save_settings()
|
||||
|
||||
def get_dict_from_config(self):
|
||||
if not os.path.exists(Static.CONFIG_FILENAME):
|
||||
self.reset_settings()
|
||||
# set version if config file was just created exist
|
||||
self.config['version'] = CozyNestConfig.get_version(),
|
||||
# return default config
|
||||
return self.config
|
||||
|
||||
with open(Static.CONFIG_FILENAME, 'r') as f:
|
||||
self.config = json.loads(f.read())
|
||||
f.close()
|
||||
return self.config
|
||||
|
||||
def reset_settings(self):
|
||||
self.config = CozyNestConfig.get_default_settings()
|
||||
self.simple_save_settings()
|
||||
|
||||
@staticmethod
|
||||
def get_default_settings():
|
||||
return {
|
||||
'main_menu_position': 'top',
|
||||
'accent_generate_button': True,
|
||||
'font_size': 12,
|
||||
'quicksettings_position': 'split',
|
||||
'font_color': '#d4d4d4',
|
||||
'font_color_light': rgb_to_hex(71, 71, 71),
|
||||
'waves_color': rgb_to_hex(94, 26, 145),
|
||||
'bg_gradiant_color': rgb_to_hex(101, 0, 94),
|
||||
'accent_color': '#37b9dd',
|
||||
'secondary_accent_color': '#b67ee1',
|
||||
'card_height': '8',
|
||||
'card_width': '16',
|
||||
'error_popup': True,
|
||||
'disable_image_browser': True,
|
||||
'disable_waves_and_gradiant': False,
|
||||
'server_default_port': 3333,
|
||||
'auto_search_port': True,
|
||||
'auto_start_server': True,
|
||||
'fetch_output_folder_from_a1111_settings': False,
|
||||
'cnib_output_folder': [],
|
||||
'archive_path': '',
|
||||
'sfw_mode': False,
|
||||
'enable_clear_button': True,
|
||||
'enable_extra_network_tweaks': False,
|
||||
'enable_cozy_extra_networks': True,
|
||||
'enable_cozy_prompt': True,
|
||||
'carret_style': 'thin',
|
||||
'save_last_prompt_local_storage': True,
|
||||
'color_mode': 'dark',
|
||||
'log_enabled': False,
|
||||
'webui': 'unknown',
|
||||
}
|
||||
|
||||
|
||||
def rgb_to_hex(r, g, b):
|
||||
return '#{:02x}{:02x}{:02x}'.format(r, g, b)
|
||||
|
||||
|
||||
def hex_to_rgb(_hex):
|
||||
rgb = []
|
||||
for i in (0, 2, 4):
|
||||
decimal = int(_hex[i:i + 2], 16)
|
||||
rgb.append(decimal)
|
||||
|
||||
return tuple(rgb)
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
from pathlib import Path
|
||||
|
||||
EXTENSION_FOLDER = Path(__file__).parent.parent.parent
|
||||
CONFIG_FILENAME = Path(EXTENSION_FOLDER, "nevyui_settings.json")
|
||||
VERSION_FILENAME = Path(EXTENSION_FOLDER, "version_data.json")
|
||||
CACHE_FILENAME = Path(EXTENSION_FOLDER, "data", "images.cache")
|
||||
|
|
@ -0,0 +1,504 @@
|
|||
import glob
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import Response, Request
|
||||
|
||||
from modules import sd_hijack, shared, sd_models
|
||||
from scripts.cozy_lib.CozyLogger import CozyLoggerExtNe
|
||||
|
||||
|
||||
def format_path_array(paths, _type, validator):
|
||||
all_paths = []
|
||||
for path in paths:
|
||||
if validator(path):
|
||||
|
||||
fullName = str(path.name)
|
||||
name = str(path.name)[:str(path.name).rfind('.')]
|
||||
|
||||
previewPath = os.path.join(path.parent, str(name)) + ".preview.png"
|
||||
if not os.path.exists(previewPath):
|
||||
previewPath = None
|
||||
|
||||
all_paths.append({
|
||||
"name": name,
|
||||
"fullName": fullName,
|
||||
"type": _type,
|
||||
"path": str(path),
|
||||
# preview path if it exists os.path.join(path.parent, str(path.name))}.preview.png
|
||||
"previewPath": previewPath
|
||||
})
|
||||
|
||||
return sorted(all_paths, key=lambda x: x['name'].lower())
|
||||
|
||||
|
||||
# gather extra network folders
|
||||
# credit to https://github.com/DominikDoom/a1111-sd-webui-tagcomplete
|
||||
class CozyExtraNetworksClass:
|
||||
def __init__(self):
|
||||
try:
|
||||
from modules.paths import extensions_dir, script_path
|
||||
except ImportError:
|
||||
extensions_dir = None
|
||||
script_path = None
|
||||
|
||||
if extensions_dir is not None and script_path is not None:
|
||||
# Webui root path
|
||||
self.FILE_DIR = Path(script_path)
|
||||
|
||||
# The extension base path
|
||||
self.EXT_PATH = Path(extensions_dir)
|
||||
else:
|
||||
# Webui root path
|
||||
self.FILE_DIR = Path().absolute()
|
||||
# The extension base path
|
||||
self.EXT_PATH = self.FILE_DIR.joinpath('extensions')
|
||||
|
||||
self.EMB_PATH = Path(shared.cmd_opts.embeddings_dir)
|
||||
self.HYP_PATH = Path(shared.cmd_opts.hypernetwork_dir)
|
||||
|
||||
try:
|
||||
self.LORA_PATH = Path(shared.cmd_opts.lora_dir)
|
||||
except AttributeError:
|
||||
self.LORA_PATH = None
|
||||
|
||||
try:
|
||||
self.LYCO_PATH = Path(shared.cmd_opts.lyco_dir)
|
||||
except AttributeError:
|
||||
self.LYCO_PATH = None
|
||||
|
||||
try:
|
||||
ckpt_dir = shared.cmd_opts.ckpt_dir or sd_models.model_path
|
||||
self.MODEL_PATH = Path(ckpt_dir)
|
||||
except AttributeError or TypeError:
|
||||
self.MODEL_PATH = None
|
||||
|
||||
# print all paths
|
||||
CozyLoggerExtNe.debug(f"FILE_DIR: {self.FILE_DIR}")
|
||||
CozyLoggerExtNe.debug(f"EXT_PATH: {self.EXT_PATH}")
|
||||
CozyLoggerExtNe.debug(f"EMB_PATH: {self.EMB_PATH}")
|
||||
CozyLoggerExtNe.debug(f"HYP_PATH: {self.HYP_PATH}")
|
||||
CozyLoggerExtNe.debug(f"LORA_PATH: {self.LORA_PATH}")
|
||||
CozyLoggerExtNe.debug(f"LYCO_PATH: {self.LYCO_PATH}")
|
||||
CozyLoggerExtNe.debug(f"MODEL_PATH: {self.MODEL_PATH}")
|
||||
|
||||
def get_hypernetworks(self):
|
||||
"""Write a list of all hypernetworks"""
|
||||
|
||||
# Get a list of all hypernetworks in the folder
|
||||
hyp_paths = [Path(h) for h in glob.glob(self.HYP_PATH.joinpath("**/*").as_posix(), recursive=True)]
|
||||
|
||||
return format_path_array(hyp_paths, 'hypernet', lambda x: x.suffix in {".pt"})
|
||||
|
||||
def get_lora(self):
|
||||
"""Write a list of all lora"""
|
||||
|
||||
# Get a list of all lora in the folder
|
||||
lora_paths = [Path(lo) for lo in glob.glob(self.LORA_PATH.joinpath("**/*").as_posix(), recursive=True)]
|
||||
|
||||
return format_path_array(lora_paths, 'lora', lambda x: x.suffix in {".safetensors", ".ckpt", ".pt"})
|
||||
|
||||
def get_lyco(self):
|
||||
"""Write a list of all LyCORIS/LOHA from https://github.com/KohakuBlueleaf/a1111-sd-webui-lycoris"""
|
||||
|
||||
# Get a list of all LyCORIS in the folder
|
||||
lyco_paths = [Path(ly) for ly in glob.glob(self.LYCO_PATH.joinpath("**/*").as_posix(), recursive=True)]
|
||||
|
||||
return format_path_array(lyco_paths, 'lyco', lambda x: x.suffix in {".safetensors", ".ckpt", ".pt"})
|
||||
|
||||
def get_models(self):
|
||||
models_paths = [Path(m) for m in glob.glob(self.MODEL_PATH.joinpath("**/*").as_posix(), recursive=True)]
|
||||
# all_models = [str(m.name) for m in models_paths if m.suffix in {".ckpt", ".safetensors"}]
|
||||
return format_path_array(models_paths, 'ckp', lambda x: x.suffix in {".ckpt", ".safetensors"})
|
||||
|
||||
def get_embeddings(self):
|
||||
"""Write a list of all embeddings with their version"""
|
||||
|
||||
# Version constants
|
||||
V1_SHAPE = 768
|
||||
V2_SHAPE = 1024
|
||||
emb_v1 = []
|
||||
emb_v2 = []
|
||||
results = []
|
||||
|
||||
try:
|
||||
# Get embedding dict from sd_hijack to separate v1/v2 embeddings
|
||||
emb_type_a = sd_hijack.model_hijack.embedding_db.word_embeddings
|
||||
emb_type_b = sd_hijack.model_hijack.embedding_db.skipped_embeddings
|
||||
# Get the shape of the first item in the dict
|
||||
emb_a_shape = -1
|
||||
emb_b_shape = -1
|
||||
if len(emb_type_a) > 0:
|
||||
emb_a_shape = next(iter(emb_type_a.items()))[1].shape
|
||||
if len(emb_type_b) > 0:
|
||||
emb_b_shape = next(iter(emb_type_b.items()))[1].shape
|
||||
|
||||
# Add embeddings to the correct list
|
||||
if emb_a_shape == V1_SHAPE:
|
||||
emb_v1 = list(emb_type_a.keys())
|
||||
elif emb_a_shape == V2_SHAPE:
|
||||
emb_v2 = list(emb_type_a.keys())
|
||||
|
||||
if emb_b_shape == V1_SHAPE:
|
||||
emb_v1 = list(emb_type_b.keys())
|
||||
elif emb_b_shape == V2_SHAPE:
|
||||
emb_v2 = list(emb_type_b.keys())
|
||||
|
||||
for e in emb_v1:
|
||||
emb_path = os.path.join(self.EMB_PATH, e)
|
||||
previewPath = f"{emb_path}.preview.png"
|
||||
|
||||
results.append({
|
||||
"name": e,
|
||||
"version": "v1",
|
||||
"type": "ti",
|
||||
"path": f"{emb_path}.pt",
|
||||
"previewPath": previewPath if os.path.isfile(previewPath) else None,
|
||||
"parentFolder": os.path.join(self.EMB_PATH, e)
|
||||
})
|
||||
|
||||
for e in emb_v2:
|
||||
emb_path = os.path.join(self.EMB_PATH, e)
|
||||
previewPath = f"{emb_path}.preview.png"
|
||||
results.append({
|
||||
"name": e,
|
||||
"version": "v2",
|
||||
"type": "ti",
|
||||
"path": f"{emb_path}.pt",
|
||||
"previewPath": previewPath if os.path.isfile(previewPath) else None,
|
||||
"parentFolder": os.path.join(self.EMB_PATH, e)
|
||||
})
|
||||
|
||||
results = sorted(results, key=lambda x: x["name"].lower())
|
||||
except AttributeError:
|
||||
print(
|
||||
"tag_autocomplete_helper: Old webui version or unrecognized model shape, using fallback for embedding completion.")
|
||||
# Get a list of all embeddings in the folder
|
||||
all_embeds = [str(e.relative_to(self.EMB_PATH)) for e in self.EMB_PATH.rglob("*") if
|
||||
e.suffix in {".bin", ".pt", ".png", '.webp', '.jxl', '.avif'}]
|
||||
# Remove files with a size of 0
|
||||
all_embeds = [e for e in all_embeds if self.EMB_PATH.joinpath(e).stat().st_size > 0]
|
||||
# Remove file extensions
|
||||
all_embeds = [e[:e.rfind('.')] for e in all_embeds]
|
||||
results = [e + "," for e in all_embeds]
|
||||
|
||||
return results
|
||||
|
||||
def create_api_route(self, app):
|
||||
@app.get("/cozy-nest/valid_extra_networks")
|
||||
def valid_extra_networks():
|
||||
valid = {}
|
||||
if self.MODEL_PATH is not None:
|
||||
valid["MODEL_PATH"] = self.MODEL_PATH
|
||||
|
||||
if self.EMB_PATH is not None:
|
||||
valid["EMB_PATH"] = self.EMB_PATH
|
||||
|
||||
if self.HYP_PATH is not None:
|
||||
valid["HYP_PATH"] = self.HYP_PATH
|
||||
|
||||
if self.LORA_PATH is not None:
|
||||
valid["LORA_PATH"] = self.LORA_PATH
|
||||
|
||||
if self.LYCO_PATH is not None:
|
||||
valid["LYCO_PATH"] = self.LYCO_PATH
|
||||
|
||||
return valid
|
||||
|
||||
@app.get("/cozy-nest/extra_networks")
|
||||
def extra_networks():
|
||||
# get all extra networks by walking through all directories recursively
|
||||
|
||||
result = {}
|
||||
|
||||
if self.MODEL_PATH is not None:
|
||||
model = self.get_models()
|
||||
result["models"] = model
|
||||
|
||||
if self.EMB_PATH is not None:
|
||||
emb = self.get_embeddings()
|
||||
result["embeddings"] = emb
|
||||
|
||||
if self.HYP_PATH is not None:
|
||||
hyp = self.get_hypernetworks()
|
||||
result["hypernetworks"] = hyp
|
||||
|
||||
if self.LORA_PATH is not None:
|
||||
lora = self.get_lora()
|
||||
result["lora"] = lora
|
||||
|
||||
if self.LYCO_PATH is not None:
|
||||
lyco = self.get_lyco()
|
||||
result["lyco"] = lyco
|
||||
|
||||
return result
|
||||
|
||||
@app.get("/cozy-nest/extra_networks/folders")
|
||||
def extra_networks_folder():
|
||||
|
||||
folder_tree = {}
|
||||
|
||||
if self.MODEL_PATH is not None:
|
||||
models = self.get_models()
|
||||
folder_tree['models'] = build_main_folder_tree_for(self.MODEL_PATH, models)
|
||||
|
||||
if self.EMB_PATH is not None:
|
||||
emb = self.get_embeddings()
|
||||
folder_tree['embeddings'] = build_main_folder_tree_for(self.EMB_PATH, emb)
|
||||
|
||||
if self.HYP_PATH is not None:
|
||||
hyp = self.get_hypernetworks()
|
||||
folder_tree['hypernetworks'] = build_main_folder_tree_for(self.HYP_PATH, hyp)
|
||||
|
||||
if self.LORA_PATH is not None:
|
||||
lora = self.get_lora()
|
||||
folder_tree['lora'] = build_main_folder_tree_for(self.LORA_PATH, lora)
|
||||
|
||||
if self.LYCO_PATH is not None:
|
||||
lyco = self.get_lyco()
|
||||
folder_tree['lyco'] = build_main_folder_tree_for(self.LYCO_PATH, lyco)
|
||||
|
||||
return folder_tree
|
||||
|
||||
@app.get("/cozy-nest/extra_networks/full")
|
||||
def extra_networks():
|
||||
# get all extra networks by walking through all directories recursively
|
||||
|
||||
result = {}
|
||||
|
||||
if self.MODEL_PATH is not None:
|
||||
model = self.get_models()
|
||||
|
||||
# for each model, get the info
|
||||
for m in model:
|
||||
try:
|
||||
info = get_info(m["path"])
|
||||
m["info"] = info
|
||||
except InfoUnavailableException:
|
||||
m["info"] = {
|
||||
"empty": True
|
||||
}
|
||||
|
||||
result["models"] = model
|
||||
|
||||
if self.EMB_PATH is not None:
|
||||
emb = self.get_embeddings()
|
||||
|
||||
# for each embedding, get the info
|
||||
for e in emb:
|
||||
try:
|
||||
info = get_info(e["path"])
|
||||
e["info"] = info
|
||||
except InfoUnavailableException:
|
||||
e["info"] = {
|
||||
"empty": True
|
||||
}
|
||||
|
||||
result["embeddings"] = emb
|
||||
|
||||
if self.HYP_PATH is not None:
|
||||
hyp = self.get_hypernetworks()
|
||||
|
||||
# for each hypernetwork, get the info
|
||||
for h in hyp:
|
||||
try:
|
||||
info = get_info(h["path"])
|
||||
h["info"] = info
|
||||
except InfoUnavailableException:
|
||||
h["info"] = {
|
||||
"empty": True
|
||||
}
|
||||
|
||||
result["hypernetworks"] = hyp
|
||||
|
||||
if self.LORA_PATH is not None:
|
||||
lora = self.get_lora()
|
||||
|
||||
# for each lora, get the info
|
||||
for lo in lora:
|
||||
try:
|
||||
info = get_info(lo["path"])
|
||||
lo["info"] = info
|
||||
except InfoUnavailableException:
|
||||
lo["info"] = {
|
||||
"empty": True
|
||||
}
|
||||
|
||||
result["lora"] = lora
|
||||
|
||||
if self.LYCO_PATH is not None:
|
||||
lyco = self.get_lyco()
|
||||
|
||||
# for each lyco, get the info
|
||||
for ly in lyco:
|
||||
try:
|
||||
info = get_info(ly["path"])
|
||||
ly["info"] = info
|
||||
except InfoUnavailableException:
|
||||
ly["info"] = {
|
||||
"empty": True
|
||||
}
|
||||
|
||||
result["lyco"] = lyco
|
||||
|
||||
return result
|
||||
|
||||
@app.get("/cozy-nest/extra_network/")
|
||||
def extra_network(path: str):
|
||||
try:
|
||||
info = get_info(path)
|
||||
return info
|
||||
except InfoUnavailableException as e:
|
||||
return Response(status_code=e.code, content=e.message)
|
||||
|
||||
@app.post("/cozy-nest/extra_network/preview")
|
||||
async def extra_network_preview(request: Request):
|
||||
# path and file are in body as FormData
|
||||
try:
|
||||
form = await request.form()
|
||||
path = form["path"]
|
||||
upload_file = form["file"]
|
||||
except Exception:
|
||||
return Response(status_code=405, content="Invalid request body")
|
||||
if path is None or upload_file is None:
|
||||
return Response(status_code=405, content="Invalid request body")
|
||||
|
||||
try:
|
||||
file_type = upload_file.content_type[upload_file.content_type.rfind("/") + 1:]
|
||||
valid = ["png"]
|
||||
if file_type not in valid:
|
||||
return Response(status_code=405, content="Invalid file type")
|
||||
|
||||
path = Path(f"{str(path)[:str(path).rfind('.')]}.preview.{file_type}")
|
||||
# save the file
|
||||
with open(path, "wb") as buffer:
|
||||
shutil.copyfileobj(upload_file.file, buffer)
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return Response(status_code=500, content="Failed to create file")
|
||||
|
||||
return Response(status_code=200, content=json.dumps({
|
||||
"previewPath": f"{path}"
|
||||
}))
|
||||
|
||||
@app.post("/cozy-nest/extra_network/toggle-nsfw")
|
||||
async def extra_network_info(request: Request):
|
||||
try:
|
||||
request_json = await request.json()
|
||||
path = request_json["path"]
|
||||
except Exception:
|
||||
return Response(status_code=405, content="Invalid request body")
|
||||
|
||||
if path is None:
|
||||
return Response(status_code=405, content="Invalid request body")
|
||||
|
||||
try:
|
||||
info = get_info(path)
|
||||
except InfoUnavailableException:
|
||||
info = {}
|
||||
info_file = get_civitai_info_path(path)
|
||||
with open(info_file, 'w') as f:
|
||||
json.dump(info, f)
|
||||
|
||||
# nsfw data is there : info.model.nsfw. change the value to false or true
|
||||
# and create each layer if it does not exist
|
||||
if "model" not in info or "nsfw" not in info["model"]:
|
||||
if "model" not in info:
|
||||
info["model"] = {}
|
||||
if "nsfw" not in info["model"]:
|
||||
# since default is considered false set it to true
|
||||
info["model"]["nsfw"] = True
|
||||
else:
|
||||
info["model"]["nsfw"] = not info["model"]["nsfw"]
|
||||
|
||||
# save the info
|
||||
info_file = get_civitai_info_path(path)
|
||||
with open(info_file, 'w') as f:
|
||||
json.dump(info, f)
|
||||
|
||||
return info
|
||||
|
||||
|
||||
class InfoUnavailableException(Exception):
|
||||
# add a code attribute to the exception
|
||||
def __init__(self, message, code):
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
self.code = code
|
||||
|
||||
|
||||
def get_info(path: str):
|
||||
path = get_civitai_info_path(path)
|
||||
if not path.exists():
|
||||
raise InfoUnavailableException("Info file not found", 404)
|
||||
|
||||
with open(path, 'r') as f:
|
||||
try:
|
||||
info = json.load(f)
|
||||
except Exception:
|
||||
raise InfoUnavailableException("Could not read info file", 500)
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def get_civitai_info_path(path):
|
||||
return Path(path[:path.rfind('.')] + '.civitai.info')
|
||||
|
||||
|
||||
def build_main_folder_tree_for(_main_path, main_items):
|
||||
# walk through all models and build the folder tree structure from self.MODEL_PATH downwards
|
||||
models_folder_tree = {
|
||||
"name": "all",
|
||||
"empty": True,
|
||||
"children": []
|
||||
}
|
||||
for m in main_items:
|
||||
# split the path into its parts
|
||||
rel_path = Path(m["path"]).relative_to(_main_path)
|
||||
path_parts = str(rel_path).split(os.sep)
|
||||
if len(path_parts) == 1:
|
||||
# if the model is in the root folder, skip it
|
||||
continue
|
||||
|
||||
models_folder_tree["empty"] = False
|
||||
|
||||
# remove the last part, which is the filename
|
||||
path_parts.pop(-1)
|
||||
|
||||
# get the folder tree
|
||||
models_folder_tree = add_folder_to_tree(_main_path, models_folder_tree, path_parts)
|
||||
return models_folder_tree
|
||||
|
||||
|
||||
def add_folder_to_tree(full_path_to_leaf, folder_tree, path_parts):
|
||||
# if there are no more parts, we are done
|
||||
if len(path_parts) == 0:
|
||||
return folder_tree
|
||||
|
||||
# get the first part of the path
|
||||
part = path_parts.pop(0)
|
||||
full_path_to_leaf = Path(full_path_to_leaf, part)
|
||||
|
||||
# check if the part is already in the folder tree
|
||||
for child in folder_tree["children"]:
|
||||
if child["name"] == part:
|
||||
# if it is, add the rest of the path to the child
|
||||
add_folder_to_tree(full_path_to_leaf, child, path_parts)
|
||||
return folder_tree
|
||||
|
||||
# if the part is not in the folder tree, add it
|
||||
folder_tree["children"].append({
|
||||
"name": part,
|
||||
"metadata": {
|
||||
"path": str(full_path_to_leaf),
|
||||
},
|
||||
"children": [],
|
||||
})
|
||||
|
||||
# add the rest of the path to the new child
|
||||
add_folder_to_tree(full_path_to_leaf, folder_tree["children"][-1], path_parts)
|
||||
|
||||
return folder_tree
|
||||
|
|
@ -1,18 +1,10 @@
|
|||
import asyncio
|
||||
import json
|
||||
import multiprocessing
|
||||
import os
|
||||
import threading
|
||||
|
||||
from PIL import Image
|
||||
from PIL.ExifTags import TAGS
|
||||
from modules import script_callbacks
|
||||
import modules.extras
|
||||
import modules.images
|
||||
import websockets
|
||||
from websockets.server import serve
|
||||
|
||||
from scripts import tools
|
||||
from scripts.cozy_lib import tools
|
||||
|
||||
|
||||
async def start_server(images_folders, server_port, stopper):
|
||||
|
|
@ -21,11 +13,9 @@ async def start_server(images_folders, server_port, stopper):
|
|||
CLIENTS = set()
|
||||
|
||||
async def handle_client(websocket, path):
|
||||
|
||||
try:
|
||||
CLIENTS.add(websocket)
|
||||
while True:
|
||||
|
||||
if stopper.is_set():
|
||||
print(f"CozyNestSocket: Stopping socket server on localhost:{server_port}...")
|
||||
break
|
||||
|
|
@ -38,7 +28,8 @@ async def start_server(images_folders, server_port, stopper):
|
|||
try:
|
||||
res = await process(data)
|
||||
except Exception as e:
|
||||
print(f"CozyNestSocket: Error while processing data: {e}")
|
||||
print(f"CozyNestSocket: Error while processing data: {data}")
|
||||
print(e)
|
||||
res = json.dumps({
|
||||
'what': 'error',
|
||||
'data': 'None',
|
||||
|
|
@ -0,0 +1,217 @@
|
|||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from modules import shared, scripts
|
||||
from scripts.cozy_lib import Static
|
||||
from scripts.cozy_lib.CozyLogger import CozyLoggerImageBrowser as Logger
|
||||
|
||||
|
||||
def output_folder_array():
|
||||
outdir_txt2img_samples = shared.opts.data['outdir_txt2img_samples']
|
||||
outdir_img2img_samples = shared.opts.data['outdir_img2img_samples']
|
||||
outdir_extras_samples = shared.opts.data['outdir_extras_samples']
|
||||
base_dir = scripts.basedir()
|
||||
# check if outdir_txt2img_samples is a relative path
|
||||
if not os.path.isabs(outdir_txt2img_samples):
|
||||
outdir_txt2img_samples = os.path.normpath(os.path.join(base_dir, outdir_txt2img_samples))
|
||||
if not os.path.isabs(outdir_img2img_samples):
|
||||
outdir_img2img_samples = os.path.normpath(os.path.join(base_dir, outdir_img2img_samples))
|
||||
if not os.path.isabs(outdir_extras_samples):
|
||||
outdir_extras_samples = os.path.normpath(os.path.join(base_dir, outdir_extras_samples))
|
||||
images_folders = [
|
||||
outdir_txt2img_samples,
|
||||
outdir_img2img_samples,
|
||||
outdir_extras_samples,
|
||||
]
|
||||
return images_folders
|
||||
|
||||
|
||||
def get_image_exif(path: str):
|
||||
try:
|
||||
image = Image.open(path)
|
||||
image.load()
|
||||
|
||||
src_info = image.text or {}
|
||||
image.close()
|
||||
|
||||
return src_info
|
||||
except:
|
||||
return {}
|
||||
|
||||
|
||||
def calculate_sha256(file_path):
|
||||
# Create a SHA-256 hash object
|
||||
sha256_hash = hashlib.sha256()
|
||||
|
||||
# Open the file in binary mode
|
||||
with open(file_path, 'rb') as file:
|
||||
# Read the file in chunks to avoid loading the entire file into memory
|
||||
for chunk in iter(lambda: file.read(4096), b''):
|
||||
# Update the hash object with the current chunk
|
||||
sha256_hash.update(chunk)
|
||||
|
||||
# Get the hexadecimal representation of the hash digest
|
||||
sha256_hex = sha256_hash.hexdigest()
|
||||
|
||||
return sha256_hex
|
||||
|
||||
|
||||
def get_exif(path):
|
||||
# info = image.info
|
||||
exif = get_image_exif(path)
|
||||
|
||||
# get the image sha256 hash
|
||||
sha256_hex = calculate_sha256(path)
|
||||
|
||||
img = {
|
||||
'path': path,
|
||||
'hash': sha256_hex,
|
||||
'metadata': {
|
||||
'date': os.path.getmtime(path),
|
||||
'exif': exif,
|
||||
}
|
||||
}
|
||||
return img
|
||||
|
||||
|
||||
def update_img_data(path):
|
||||
# get the corresponding image in the cache, update its metadata and save it back to the cache in the same position
|
||||
with open(Static.CACHE_FILENAME, 'r') as f:
|
||||
cache = json.loads(f.read())
|
||||
for img in cache['images']:
|
||||
if img['path'] == path:
|
||||
exif = get_image_exif(path)
|
||||
img['metadata'] = {
|
||||
'date': os.path.getmtime(path),
|
||||
'exif': exif,
|
||||
}
|
||||
break
|
||||
with open(Static.CACHE_FILENAME, 'w') as fw:
|
||||
fw.write(json.dumps(cache, indent=4))
|
||||
|
||||
|
||||
def delete_img_data(path):
|
||||
# get the corresponding image in the cache and remove it
|
||||
with open(Static.CACHE_FILENAME, 'r') as f:
|
||||
cache = json.loads(f.read())
|
||||
for img in cache['images']:
|
||||
if img['path'] == path:
|
||||
cache['images'].remove(img)
|
||||
break
|
||||
with open(Static.CACHE_FILENAME, 'w') as fw:
|
||||
fw.write(json.dumps(cache))
|
||||
|
||||
|
||||
def delete_index():
|
||||
# delete the cache file
|
||||
if os.path.exists(Static.CACHE_FILENAME):
|
||||
os.remove(Static.CACHE_FILENAME)
|
||||
|
||||
|
||||
def scrap_image_folders(images_folders):
|
||||
|
||||
# if the cache file exists, read it and return the data
|
||||
if os.path.exists(Static.CACHE_FILENAME):
|
||||
with open(Static.CACHE_FILENAME, 'r') as f:
|
||||
return json.loads(f.read())
|
||||
|
||||
# scrape the images folder recursively
|
||||
Logger.debug('Scraping images folders...')
|
||||
# TODO NEVYSHA store images as hash=>data in index
|
||||
|
||||
# gather all the images paths
|
||||
images_path = []
|
||||
for images_folder in images_folders:
|
||||
for root, dirs, files in os.walk(images_folder):
|
||||
for file in files:
|
||||
if file.endswith(".png") or file.endswith(".jpg") or file.endswith(".jpeg"):
|
||||
images_path.append(os.path.join(root, file))
|
||||
|
||||
Logger.info(f"Creating images index for {len(images_path)} images...")
|
||||
|
||||
# get the exif data for each image
|
||||
|
||||
# split the images_path list into chunks and process each chunk in a separate thread
|
||||
thread_count = os.cpu_count()
|
||||
Logger.debug(f"Using {thread_count} threads to process the images...")
|
||||
split_count = (len(images_path) + 1) // thread_count
|
||||
splited = [images_path[i * split_count:(i + 1) * split_count] for i in range(thread_count)]
|
||||
|
||||
images = []
|
||||
start_time = time.time()
|
||||
|
||||
def process_chunk(_chunk):
|
||||
for i, path in enumerate(_chunk):
|
||||
# get exif data
|
||||
img = get_exif(path)
|
||||
images.append(img)
|
||||
|
||||
# create a thread for each chunk
|
||||
threads = []
|
||||
for chunk in splited:
|
||||
t = threading.Thread(target=process_chunk, args=(chunk,))
|
||||
t.start()
|
||||
threads.append(t)
|
||||
|
||||
# wait for all the threads to finish
|
||||
# loop to check if the threads are done
|
||||
while True:
|
||||
elapsed_time = time.time() - start_time
|
||||
if not any([t.is_alive() for t in threads]):
|
||||
display_progress_bar(1, elapsed_time)
|
||||
break
|
||||
|
||||
# Calculate progress and elapsed time
|
||||
i = len(images)
|
||||
progress = (i + 1) / len(images_path)
|
||||
|
||||
# Display progress bar with elapsed time and estimated time remaining
|
||||
# only each ten images or if it's the last image
|
||||
if i == len(images_path) - 1:
|
||||
display_progress_bar(1, elapsed_time)
|
||||
else:
|
||||
display_progress_bar(progress, elapsed_time)
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
# sort the images by date (newest first) metadata.date
|
||||
images.sort(key=lambda x: x['metadata']['date'], reverse=True)
|
||||
|
||||
# send the images to the client
|
||||
data = {
|
||||
'what': 'images',
|
||||
'images': images
|
||||
}
|
||||
|
||||
if not os.path.exists(Static.CACHE_FILENAME):
|
||||
open(Static.CACHE_FILENAME, 'w').close()
|
||||
|
||||
with open(Static.CACHE_FILENAME, 'w') as f:
|
||||
f.write(json.dumps(data))
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def new_image(_new_img_data):
|
||||
# Add the image to the cache
|
||||
with open(Static.CACHE_FILENAME, 'r') as fr:
|
||||
cache = json.loads(fr.read())
|
||||
cache['images'].insert(0, _new_img_data)
|
||||
with open(Static.CACHE_FILENAME, 'w') as fw:
|
||||
fw.write(json.dumps(cache))
|
||||
|
||||
|
||||
def display_progress_bar(progress, elapsed_time):
|
||||
bar_length = 40
|
||||
filled_length = int(bar_length * progress)
|
||||
bar = '█' * filled_length + '-' * (bar_length - filled_length)
|
||||
percentage = progress * 100
|
||||
progress_bar = f'[Cozy:ImageBrowser:INFO] Indexing: |{bar}| {percentage:.1f}% Complete | Elapsed: {elapsed_time:.2f}s'
|
||||
sys.stdout.write('\r' + progress_bar)
|
||||
sys.stdout.flush()
|
||||
|
|
@ -6,155 +6,22 @@ import socket
|
|||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
from typing import Any
|
||||
|
||||
import gradio as gr
|
||||
import modules
|
||||
from PIL import Image
|
||||
from PIL.PngImagePlugin import PngInfo
|
||||
from typing import Any
|
||||
from fastapi import FastAPI, Response, Request
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
import websockets
|
||||
from modules import script_callbacks, shared, call_queue, scripts
|
||||
|
||||
from scripts import tools
|
||||
from scripts.cozynest_image_browser import start_server
|
||||
|
||||
|
||||
def rgb_to_hex(r, g, b):
|
||||
return '#{:02x}{:02x}{:02x}'.format(r, g, b)
|
||||
|
||||
|
||||
def hex_to_rgb(hex):
|
||||
rgb = []
|
||||
for i in (0, 2, 4):
|
||||
decimal = int(hex[i:i + 2], 16)
|
||||
rgb.append(decimal)
|
||||
|
||||
return tuple(rgb)
|
||||
|
||||
|
||||
# check parent folder name (2 level above) to ensure compatibility after repo rename
|
||||
EXTENSION_TECHNICAL_NAME = os.path.basename(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
CONFIG_FILENAME = f"extensions/{EXTENSION_TECHNICAL_NAME}/nevyui_settings.json"
|
||||
CONFIG_FILENAME = os.path.join(shared.cmd_opts.data_dir, CONFIG_FILENAME)
|
||||
|
||||
|
||||
def gradio_save_settings(main_menu_position,
|
||||
quicksettings_position,
|
||||
accent_generate_button,
|
||||
font_size,
|
||||
font_color,
|
||||
font_color_light,
|
||||
waves_color,
|
||||
bg_gradiant_color,
|
||||
accent_color,
|
||||
card_height,
|
||||
card_width,
|
||||
error_popup,
|
||||
disable_image_browser,
|
||||
disable_waves_and_gradiant,
|
||||
server_default_port,
|
||||
auto_search_port,
|
||||
auto_start_server,
|
||||
fetch_output_folder_from_a1111_settings,
|
||||
archive_path,
|
||||
sfw_mode,
|
||||
enable_clear_button,
|
||||
enable_extra_network_tweaks,
|
||||
):
|
||||
settings = {
|
||||
'main_menu_position': main_menu_position,
|
||||
'quicksettings_position': quicksettings_position,
|
||||
'accent_generate_button': accent_generate_button,
|
||||
'font_size': font_size,
|
||||
'font_color': font_color,
|
||||
'font_color_light': font_color_light,
|
||||
'waves_color': waves_color,
|
||||
'bg_gradiant_color': bg_gradiant_color,
|
||||
'accent_color': accent_color,
|
||||
'card_height': card_height,
|
||||
'card_width': card_width,
|
||||
'error_popup': error_popup,
|
||||
'disable_image_browser': disable_image_browser,
|
||||
'disable_waves_and_gradiant': disable_waves_and_gradiant,
|
||||
'server_default_port': server_default_port,
|
||||
'auto_search_port': auto_search_port,
|
||||
'auto_start_server': auto_start_server,
|
||||
'fetch_output_folder_from_a1111_settings': fetch_output_folder_from_a1111_settings,
|
||||
'sfw_mode': sfw_mode,
|
||||
'enable_clear_button': enable_clear_button,
|
||||
'enable_extra_network_tweaks': enable_extra_network_tweaks,
|
||||
'archive_path': archive_path,
|
||||
}
|
||||
|
||||
current_config = get_dict_from_config()
|
||||
|
||||
settings = {**current_config, **settings}
|
||||
|
||||
save_settings(settings)
|
||||
|
||||
|
||||
def save_settings(settings):
|
||||
# create the file in extensions/Cozy-Nest if it doesn't exist
|
||||
if not os.path.exists(CONFIG_FILENAME):
|
||||
open(CONFIG_FILENAME, 'w').close()
|
||||
# save each settings inside the file
|
||||
with open(CONFIG_FILENAME, 'w') as f:
|
||||
f.write(json.dumps(settings, indent=2))
|
||||
f.close()
|
||||
|
||||
|
||||
def get_dict_from_config():
|
||||
if not os.path.exists(CONFIG_FILENAME):
|
||||
reset_settings()
|
||||
# return default config
|
||||
return get_default_settings()
|
||||
|
||||
with open(CONFIG_FILENAME, 'r') as f:
|
||||
config = json.loads(f.read())
|
||||
f.close()
|
||||
return config
|
||||
|
||||
|
||||
def get_default_settings():
|
||||
return {
|
||||
'main_menu_position': 'top',
|
||||
'accent_generate_button': True,
|
||||
'font_size': 12,
|
||||
'quicksettings_position': 'split',
|
||||
'font_color': '#d4d4d4',
|
||||
'font_color_light': rgb_to_hex(71, 71, 71),
|
||||
'waves_color': rgb_to_hex(94, 26, 145),
|
||||
'bg_gradiant_color': rgb_to_hex(101, 0, 94),
|
||||
'accent_color': '#37b9dd',
|
||||
'secondary_accent_color': '#b67ee1',
|
||||
'card_height': '8',
|
||||
'card_width': '16',
|
||||
'error_popup': True,
|
||||
'disable_image_browser': True,
|
||||
'disable_waves_and_gradiant': False,
|
||||
'server_default_port': 3333,
|
||||
'auto_search_port': True,
|
||||
'auto_start_server': True,
|
||||
'fetch_output_folder_from_a1111_settings': False,
|
||||
'cnib_output_folder': [],
|
||||
'archive_path': '',
|
||||
'sfw_mode': False,
|
||||
'enable_clear_button': True,
|
||||
'enable_extra_network_tweaks': True,
|
||||
'enable_cozy_prompt': True,
|
||||
'carret_style': 'thin',
|
||||
'save_last_prompt_local_storage': True,
|
||||
'color_mode': 'dark',
|
||||
'webui': 'unknown'
|
||||
}
|
||||
|
||||
|
||||
def reset_settings():
|
||||
save_settings(
|
||||
get_default_settings())
|
||||
from scripts.cozy_lib import tools
|
||||
from scripts.cozy_lib.CozyLogger import CozyLogger
|
||||
from scripts.cozy_lib.CozyNestConfig import CozyNestConfig
|
||||
from scripts.cozy_lib.cozynest_image_browser import start_server
|
||||
from scripts.cozy_lib.tools import output_folder_array
|
||||
|
||||
|
||||
def request_restart():
|
||||
|
|
@ -216,26 +83,6 @@ def serv_img_browser_socket(server_port=3333, auto_search_port=True, cnib_output
|
|||
print(e)
|
||||
|
||||
|
||||
def output_folder_array():
|
||||
outdir_txt2img_samples = shared.opts.data['outdir_txt2img_samples']
|
||||
outdir_img2img_samples = shared.opts.data['outdir_img2img_samples']
|
||||
outdir_extras_samples = shared.opts.data['outdir_extras_samples']
|
||||
base_dir = scripts.basedir()
|
||||
# check if outdir_txt2img_samples is a relative path
|
||||
if not os.path.isabs(outdir_txt2img_samples):
|
||||
outdir_txt2img_samples = os.path.normpath(os.path.join(base_dir, outdir_txt2img_samples))
|
||||
if not os.path.isabs(outdir_img2img_samples):
|
||||
outdir_img2img_samples = os.path.normpath(os.path.join(base_dir, outdir_img2img_samples))
|
||||
if not os.path.isabs(outdir_extras_samples):
|
||||
outdir_extras_samples = os.path.normpath(os.path.join(base_dir, outdir_extras_samples))
|
||||
images_folders = [
|
||||
outdir_txt2img_samples,
|
||||
outdir_img2img_samples,
|
||||
outdir_extras_samples,
|
||||
]
|
||||
return images_folders
|
||||
|
||||
|
||||
def start_server_in_dedicated_process(_images_folders, server_port):
|
||||
def run_server():
|
||||
asyncio.run(start_server(_images_folders, server_port, stopper))
|
||||
|
|
@ -284,38 +131,12 @@ _server_port = None
|
|||
|
||||
def on_ui_tabs():
|
||||
global _server_port
|
||||
# shared options
|
||||
config = get_dict_from_config()
|
||||
# merge default settings with user settings
|
||||
config = {**get_default_settings(), **config}
|
||||
|
||||
if config['webui'] == 'unknown' and hasattr(shared, 'get_version'):
|
||||
version = shared.get_version()
|
||||
# check if the 'app' is 'sd.next'
|
||||
if version['app'] == 'sd.next':
|
||||
config['webui'] = 'sd.next'
|
||||
config['fetch_output_folder_from_a1111_settings'] = False
|
||||
else:
|
||||
config['webui'] = 'auto1111'
|
||||
save_settings(config)
|
||||
config = CozyNestConfig()
|
||||
config.init()
|
||||
config.migrate()
|
||||
|
||||
if config['webui'] == 'sd.next':
|
||||
config['fetch_output_folder_from_a1111_settings'] = False
|
||||
|
||||
# check if cnib_output_folder is empty and/or need to be fetched from a1111 settings
|
||||
cnib_output_folder = config.get('cnib_output_folder')
|
||||
is_empty = cnib_output_folder == []
|
||||
if not cnib_output_folder or is_empty:
|
||||
cnib_output_folder = []
|
||||
|
||||
if config.get('fetch_output_folder_from_a1111_settings'):
|
||||
# merge cnib_output_folder output_folder_array()
|
||||
cnib_output_folder = cnib_output_folder + list(set(output_folder_array()) - set(cnib_output_folder))
|
||||
|
||||
config['cnib_output_folder'] = cnib_output_folder
|
||||
|
||||
# save the merged settings
|
||||
save_settings(config)
|
||||
CozyLogger.info(f"version: {config.get('version')}")
|
||||
|
||||
# check if the user has disabled the image browser
|
||||
disable_image_browser_value = config.get('disable_image_browser')
|
||||
|
|
@ -346,12 +167,12 @@ def on_ui_tabs():
|
|||
if not any([path.startswith(folder) for folder in images_folders]):
|
||||
return
|
||||
|
||||
data = tools.get_exif(path)
|
||||
tools.new_image(data)
|
||||
_new_img_data = tools.get_exif(path)
|
||||
tools.new_image(_new_img_data)
|
||||
|
||||
asyncio.run(send_to_socket({
|
||||
'what': 'image_saved',
|
||||
'data': data,
|
||||
'data': _new_img_data,
|
||||
}, server_port))
|
||||
|
||||
if not disable_image_browser_value:
|
||||
|
|
@ -389,7 +210,7 @@ async def send_to_socket(data, server_port):
|
|||
await websocket.close()
|
||||
break
|
||||
|
||||
except websockets.exceptions.ConnectionClosed:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
|
|
@ -405,19 +226,16 @@ def cozy_nest_api(_: Any, app: FastAPI, **kwargs):
|
|||
# Access POST parameters
|
||||
data = await request.json()
|
||||
|
||||
# shared options
|
||||
config = get_dict_from_config()
|
||||
# merge default settings with user settings
|
||||
config = {**get_default_settings(), **config,
|
||||
**data}
|
||||
config = CozyNestConfig()
|
||||
|
||||
save_settings(config)
|
||||
config.save_settings(data)
|
||||
|
||||
return {"message": "Config saved successfully"}
|
||||
|
||||
@app.delete("/cozy-nest/config")
|
||||
async def delete_config():
|
||||
reset_settings()
|
||||
config = CozyNestConfig()
|
||||
config.reset_settings()
|
||||
return {"message": "Config deleted successfully"}
|
||||
|
||||
@app.get("/cozy-nest/reloadui")
|
||||
|
|
@ -457,7 +275,7 @@ def cozy_nest_api(_: Any, app: FastAPI, **kwargs):
|
|||
async def delete_index():
|
||||
global _server_port
|
||||
|
||||
config = get_dict_from_config()
|
||||
config = CozyNestConfig()
|
||||
cnib_output_folder = config.get('cnib_output_folder')
|
||||
if cnib_output_folder and cnib_output_folder != "":
|
||||
tools.delete_index()
|
||||
|
|
@ -487,7 +305,7 @@ def cozy_nest_api(_: Any, app: FastAPI, **kwargs):
|
|||
# do nothing for now
|
||||
return Response(status_code=501, content="unimplemented")
|
||||
|
||||
config = get_dict_from_config()
|
||||
config = CozyNestConfig()
|
||||
archive_path = config.get('archive_path')
|
||||
if not archive_path or archive_path == "":
|
||||
# return {"message": "archive path not set"}
|
||||
|
|
@ -536,6 +354,13 @@ def cozy_nest_api(_: Any, app: FastAPI, **kwargs):
|
|||
pass
|
||||
|
||||
|
||||
def init_extra_networks(_: Any, app: FastAPI, **kwargs):
|
||||
from scripts.cozy_lib.cozy_extra_network import CozyExtraNetworksClass
|
||||
|
||||
CozyExtraNetworks = CozyExtraNetworksClass()
|
||||
CozyExtraNetworks.create_api_route(app)
|
||||
|
||||
|
||||
script_callbacks.on_ui_tabs(on_ui_tabs)
|
||||
script_callbacks.on_app_started(cozy_nest_api)
|
||||
script_callbacks.on_app_started(init_extra_networks)
|
||||
|
|
|
|||
138
scripts/tools.py
138
scripts/tools.py
|
|
@ -1,138 +0,0 @@
|
|||
import hashlib
|
||||
import json
|
||||
import os
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def get_image_exif(path: str):
|
||||
try:
|
||||
image = Image.open(path)
|
||||
image.load()
|
||||
|
||||
src_info = image.text or {}
|
||||
image.close()
|
||||
|
||||
return src_info
|
||||
except:
|
||||
return {}
|
||||
|
||||
|
||||
def calculate_sha256(file_path):
|
||||
# Create a SHA-256 hash object
|
||||
sha256_hash = hashlib.sha256()
|
||||
|
||||
# Open the file in binary mode
|
||||
with open(file_path, 'rb') as file:
|
||||
# Read the file in chunks to avoid loading the entire file into memory
|
||||
for chunk in iter(lambda: file.read(4096), b''):
|
||||
# Update the hash object with the current chunk
|
||||
sha256_hash.update(chunk)
|
||||
|
||||
# Get the hexadecimal representation of the hash digest
|
||||
sha256_hex = sha256_hash.hexdigest()
|
||||
|
||||
return sha256_hex
|
||||
|
||||
|
||||
def get_exif(path):
|
||||
# info = image.info
|
||||
exif = get_image_exif(path)
|
||||
|
||||
# get the image sha256 hash
|
||||
sha256_hex = calculate_sha256(path)
|
||||
|
||||
img = {
|
||||
'path': path,
|
||||
'hash': sha256_hex,
|
||||
'metadata': {
|
||||
'date': os.path.getmtime(path),
|
||||
'exif': exif,
|
||||
}
|
||||
}
|
||||
return img
|
||||
|
||||
|
||||
def update_img_data(path):
|
||||
# get the corresponding image in the cache, update its metadata and save it back to the cache in the same position
|
||||
with open(CACHE_FILENAME, 'r') as f:
|
||||
cache = json.loads(f.read())
|
||||
for img in cache['images']:
|
||||
if img['path'] == path:
|
||||
exif = get_image_exif(path)
|
||||
img['metadata'] = {
|
||||
'date': os.path.getmtime(path),
|
||||
'exif': exif,
|
||||
}
|
||||
break
|
||||
with open(CACHE_FILENAME, 'w') as fw:
|
||||
fw.write(json.dumps(cache, indent=4))
|
||||
|
||||
|
||||
def delete_img_data(path):
|
||||
# get the corresponding image in the cache and remove it
|
||||
with open(CACHE_FILENAME, 'r') as f:
|
||||
cache = json.loads(f.read())
|
||||
for img in cache['images']:
|
||||
if img['path'] == path:
|
||||
cache['images'].remove(img)
|
||||
break
|
||||
with open(CACHE_FILENAME, 'w') as fw:
|
||||
fw.write(json.dumps(cache))
|
||||
|
||||
|
||||
def delete_index():
|
||||
# delete the cache file
|
||||
if os.path.exists(CACHE_FILENAME):
|
||||
os.remove(CACHE_FILENAME)
|
||||
|
||||
|
||||
EXTENSION_TECHNICAL_NAME = os.path.basename(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
# TODO use db instead of cache file
|
||||
CACHE_FILENAME = f"extensions/{EXTENSION_TECHNICAL_NAME}/data/images.cache"
|
||||
|
||||
|
||||
def scrap_image_folders(images_folders):
|
||||
|
||||
# if the cache file exists, read it and return the data
|
||||
if os.path.exists(CACHE_FILENAME):
|
||||
with open(CACHE_FILENAME, 'r') as f:
|
||||
return json.loads(f.read())
|
||||
|
||||
# scrape the images folder recursively
|
||||
# TODO store images as hash=>data in index
|
||||
images = []
|
||||
for images_folder in images_folders:
|
||||
for root, dirs, files in os.walk(images_folder):
|
||||
for file in files:
|
||||
if file.endswith(".png") or file.endswith(".jpg") or file.endswith(".jpeg"):
|
||||
# get exif data
|
||||
img = get_exif(os.path.join(root, file))
|
||||
images.append(img)
|
||||
|
||||
# sort the images by date (newest first) metadata.date
|
||||
images.sort(key=lambda x: x['metadata']['date'], reverse=True)
|
||||
|
||||
# send the images to the client
|
||||
data = {
|
||||
'what': 'images',
|
||||
'images': images
|
||||
}
|
||||
|
||||
if not os.path.exists(CACHE_FILENAME):
|
||||
open(CACHE_FILENAME, 'w').close()
|
||||
|
||||
with open(CACHE_FILENAME, 'w') as f:
|
||||
f.write(json.dumps(data))
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def new_image(data):
|
||||
# Add the image to the cache
|
||||
with open(CACHE_FILENAME, 'r') as f:
|
||||
cache = json.loads(f.read())
|
||||
cache['images'].insert(0, data)
|
||||
with open(CACHE_FILENAME, 'w') as f:
|
||||
f.write(json.dumps(cache))
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"version": "2.3.4"
|
||||
"version": "2.4.0"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue