mirror of https://github.com/Nevysha/Cozy-Nest.git
2.2.0 (#147)
parent
ec0a01901b
commit
b01770f28b
|
|
@ -1 +1,3 @@
|
||||||
Cozy-Nest.iml
|
Cozy-Nest.iml
|
||||||
|
nevyui_settings.json
|
||||||
|
data/images.cache
|
||||||
12
PATCHNOTE.md
12
PATCHNOTE.md
|
|
@ -1,9 +1,19 @@
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
- From Automatic1111's webui `5ab7f213` commit to the 1.2.1 release.
|
- Automatic1111's webui 1.3.1 release.
|
||||||
- SD.Next (Vlad's fork) from commit `beff89ba`
|
- SD.Next (Vlad's fork) from commit `beff89ba`
|
||||||
- Will work best on latest version of both as I'm only testing on latest version.
|
- Will work best on latest version of both as I'm only testing on latest version.
|
||||||
|
|
||||||
|
## 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
|
## 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)
|
- [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)
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,15 @@ Cozy Nest is a UI extension for Automatic's sd-webui. Inspired by [anapnoe](http
|
||||||
## Features:
|
## Features:
|
||||||
- [x] Fully integrated Image Browser **IN BETA**. Lots of bugs and missing features. Please be kind with Github issues.
|
- [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] Send to txt2img / img2img / …
|
||||||
|
- [x] Search
|
||||||
|
- [x] Tag your images and filter by tag
|
||||||
|
- [x] Edit exif metadata
|
||||||
|
- [x] Archive, hide or delete images
|
||||||
- [x] Clean memory for image not visible (unload them / replace with dummy div) clean filteredImages and loadedImage Array
|
- [x] Clean memory for image not visible (unload them / replace with dummy div) clean filteredImages and loadedImage Array
|
||||||
- [x] manage new image generated
|
- [x] manage new image generated
|
||||||
- [x] Automatically get image output folder (without grid folder)
|
- [x] Automatically get image output folder (without grid folder)
|
||||||
- [x] Drag and drop image
|
- [x] Drag and drop image
|
||||||
|
- [x] Enhanced prompt editor with color (in txt2img and img2img) - It can be disabled through settings
|
||||||
- [x] Resizable panels
|
- [x] Resizable panels
|
||||||
- [x] Full Screen Inpainting
|
- [x] Full Screen Inpainting
|
||||||
- [x] Customizable tab menu position (top, left, centered)
|
- [x] Customizable tab menu position (top, left, centered)
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,14 @@
|
||||||
|
module.exports = {
|
||||||
|
env: { browser: true, es2020: true },
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:react-hooks/recommended',
|
||||||
|
],
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
|
||||||
|
plugins: ['react-refresh'],
|
||||||
|
rules: {
|
||||||
|
'react-refresh/only-export-components': 'warn',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -29,6 +29,6 @@ npm run dev
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd Cozy-Nest/cozy-nest-client
|
cd Cozy-Nest/cozy-nest-client
|
||||||
# check output folder in vite.config.js. It is hard coded atm.
|
# check output folder in vite.config.ts. It is hard coded atm.
|
||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
import React, {useRef} from 'react'
|
||||||
|
import {useDisclosure} from "@chakra-ui/react";
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogBody,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogOverlay,
|
||||||
|
AlertDialogCloseButton,
|
||||||
|
Button,
|
||||||
|
} from '@chakra-ui/react'
|
||||||
|
|
||||||
|
export function ButtonWithConfirmDialog({message, confirmLabel, cancelLabel, onConfirm, style}) {
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||||
|
const cancelRef = useRef()
|
||||||
|
|
||||||
|
if (style) {
|
||||||
|
style = {...style, width: '100%'}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
style = {width: '100%'}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
className="btn-settings"
|
||||||
|
style={style}
|
||||||
|
onClick={onOpen}
|
||||||
|
>{confirmLabel}</button>
|
||||||
|
|
||||||
|
<AlertDialog
|
||||||
|
isOpen={isOpen}
|
||||||
|
leastDestructiveRef={cancelRef}
|
||||||
|
onClose={onClose}
|
||||||
|
variant="nevysha-confirm"
|
||||||
|
>
|
||||||
|
<AlertDialogOverlay>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader fontSize='lg' fontWeight='bold'>
|
||||||
|
Confirm Action
|
||||||
|
</AlertDialogHeader>
|
||||||
|
|
||||||
|
<AlertDialogBody>
|
||||||
|
{message}
|
||||||
|
</AlertDialogBody>
|
||||||
|
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<button
|
||||||
|
className="btn-settings"
|
||||||
|
ref={cancelRef}
|
||||||
|
onClick={onClose}>
|
||||||
|
{cancelLabel}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn-settings"
|
||||||
|
onClick={() => {
|
||||||
|
onClose();
|
||||||
|
onConfirm();
|
||||||
|
}}>
|
||||||
|
{confirmLabel}
|
||||||
|
</button>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialogOverlay>
|
||||||
|
</AlertDialog>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
import {createMultiStyleConfigHelpers} from "@chakra-ui/react";
|
||||||
|
import {checkboxAnatomy, radioAnatomy} from "@chakra-ui/anatomy";
|
||||||
|
|
||||||
|
const { definePartsStyle, defineMultiStyleConfig } =
|
||||||
|
createMultiStyleConfigHelpers(checkboxAnatomy.keys)
|
||||||
|
|
||||||
|
export const checkboxTheme = defineMultiStyleConfig({
|
||||||
|
defaultProps: {
|
||||||
|
variant: 'nevysha'
|
||||||
|
},
|
||||||
|
variants: { nevysha: definePartsStyle({
|
||||||
|
control: {
|
||||||
|
boxShadow: 'var(--input-shadow)',
|
||||||
|
border: '1px solid var(--ae-input-border-color) !important',
|
||||||
|
borderRadius: 'var(--checkbox-border-radius)',
|
||||||
|
backgroundColor: 'var(--checkbox-background-color)',
|
||||||
|
lineHeight: 'var(--line-sm)',
|
||||||
|
}
|
||||||
|
}) },
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
definePartsStyle: radioDefinePartsStyle,
|
||||||
|
defineMultiStyleConfig: radioDefineMultiStyleConfig
|
||||||
|
} = createMultiStyleConfigHelpers(radioAnatomy.keys);
|
||||||
|
|
||||||
|
export const radioTheme = radioDefineMultiStyleConfig({
|
||||||
|
defaultProps: {
|
||||||
|
variant: 'nevysha'
|
||||||
|
},
|
||||||
|
variants: { nevysha: radioDefinePartsStyle({
|
||||||
|
control: {
|
||||||
|
boxShadow: 'var(--input-shadow)',
|
||||||
|
border: '1px solid var(--ae-input-border-color) !important',
|
||||||
|
// borderRadius: 'var(--checkbox-border-radius)',
|
||||||
|
backgroundColor: 'var(--checkbox-background-color)',
|
||||||
|
lineHeight: 'var(--line-sm)',
|
||||||
|
}
|
||||||
|
}) },
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
import {createMultiStyleConfigHelpers} from "@chakra-ui/react";
|
||||||
|
import {inputAnatomy, numberInputAnatomy} from "@chakra-ui/anatomy";
|
||||||
|
|
||||||
|
let { definePartsStyle, defineMultiStyleConfig } =
|
||||||
|
createMultiStyleConfigHelpers(inputAnatomy.keys)
|
||||||
|
|
||||||
|
export const inputTheme = defineMultiStyleConfig({
|
||||||
|
defaultProps: {
|
||||||
|
variant: 'nevysha'
|
||||||
|
},
|
||||||
|
variants: { nevysha: definePartsStyle({
|
||||||
|
field: {
|
||||||
|
outline: 'none!important',
|
||||||
|
boxShadow: 'var(--input-shadow)',
|
||||||
|
border: '1px solid var(--ae-input-border-color) !important',
|
||||||
|
borderRadius: '0 !important',
|
||||||
|
backgroundColor: 'var(--input-background-fill) !important',
|
||||||
|
padding: 'var(--input-padding) !important',
|
||||||
|
width: '100%',
|
||||||
|
color: 'var(--body-text-color)',
|
||||||
|
fontSize: 'var(--input-text-size)',
|
||||||
|
lineHeight: 'var(--line-sm)',
|
||||||
|
fontFamily: 'monospace !important',
|
||||||
|
}
|
||||||
|
}) },
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
definePartsStyle: numberInputDefinePartsStyle,
|
||||||
|
defineMultiStyleConfig: numberInputDefineMultiStyleConfig
|
||||||
|
} = createMultiStyleConfigHelpers(numberInputAnatomy.keys);
|
||||||
|
|
||||||
|
export const numberInputTheme = numberInputDefineMultiStyleConfig({
|
||||||
|
defaultProps: {
|
||||||
|
variant: 'nevysha'
|
||||||
|
},
|
||||||
|
variants: { nevysha: numberInputDefinePartsStyle({
|
||||||
|
field: {
|
||||||
|
outline: 'none!important',
|
||||||
|
boxShadow: 'var(--input-shadow)',
|
||||||
|
border: '1px solid var(--ae-input-border-color) !important',
|
||||||
|
borderRadius: '0 !important',
|
||||||
|
backgroundColor: 'var(--input-background-fill) !important',
|
||||||
|
padding: 'var(--input-padding) !important',
|
||||||
|
width: '100%',
|
||||||
|
color: 'var(--body-text-color)',
|
||||||
|
fontSize: 'var(--input-text-size) !important',
|
||||||
|
lineHeight: 'var(--line-sm)',
|
||||||
|
fontFamily: 'monospace !important',
|
||||||
|
},
|
||||||
|
stepperGroup: {
|
||||||
|
border: '1px solid var(--ae-input-border-color) !important',
|
||||||
|
borderTop: 'none !important',
|
||||||
|
borderRight: 'none !important',
|
||||||
|
},
|
||||||
|
stepper: {
|
||||||
|
color: 'var(--body-text-color)',
|
||||||
|
borderTop: 'none !important',
|
||||||
|
borderLeft: 'none !important',
|
||||||
|
}
|
||||||
|
}) },
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
import {createMultiStyleConfigHelpers} from "@chakra-ui/react";
|
||||||
|
import {modalAnatomy} from "@chakra-ui/anatomy";
|
||||||
|
|
||||||
|
let { definePartsStyle, defineMultiStyleConfig } =
|
||||||
|
createMultiStyleConfigHelpers(modalAnatomy.keys)
|
||||||
|
|
||||||
|
export const modalTheme = defineMultiStyleConfig({
|
||||||
|
defaultProps: {
|
||||||
|
variant: 'nevysha'
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
nevysha:
|
||||||
|
definePartsStyle({
|
||||||
|
dialog: {
|
||||||
|
opacity: 1,
|
||||||
|
width: '800px',
|
||||||
|
marginRight: 'auto',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
transform: 'none',
|
||||||
|
maxWidth: 'fit-content',
|
||||||
|
background: 'none',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
'nevysha-confirm':
|
||||||
|
definePartsStyle({
|
||||||
|
dialog: {
|
||||||
|
opacity: 1,
|
||||||
|
width: '800px',
|
||||||
|
marginRight: 'auto',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
transform: 'none',
|
||||||
|
maxWidth: 'fit-content',
|
||||||
|
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)',
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
display: 'flex !important',
|
||||||
|
gap: '5px',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
import {createMultiStyleConfigHelpers} from "@chakra-ui/react";
|
||||||
|
import {tabsAnatomy} from "@chakra-ui/anatomy";
|
||||||
|
|
||||||
|
const { definePartsStyle, defineMultiStyleConfig } =
|
||||||
|
createMultiStyleConfigHelpers(tabsAnatomy.keys)
|
||||||
|
|
||||||
|
export const tabsTheme = defineMultiStyleConfig({
|
||||||
|
defaultProps: {
|
||||||
|
variant: 'nevysha'
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
nevysha: definePartsStyle({
|
||||||
|
tab: {
|
||||||
|
marginBottom: '-1px',
|
||||||
|
border: '1px solid transparent',
|
||||||
|
borderColor: 'transparent',
|
||||||
|
borderBottom: 'none',
|
||||||
|
borderRadius: '0 !important',
|
||||||
|
padding: 'var(--size-1) var(--size-4) !important',
|
||||||
|
color: 'var(--body-text-color-subdued) !important',
|
||||||
|
fontWeight: 'var(--section-header-text-weight) !important',
|
||||||
|
fontSize: 'var(--section-header-text-size) !important',
|
||||||
|
borderTop: '2px solid transparent !important',
|
||||||
|
_selected: {
|
||||||
|
borderTop: '2px solid var(--ae-primary-color) !important',
|
||||||
|
backgroundColor: 'var(--tab-nav-background-color-selected) !important',
|
||||||
|
color: 'var(--body-text-color) !important',
|
||||||
|
},
|
||||||
|
_focus: {
|
||||||
|
outline: 'none'
|
||||||
|
},
|
||||||
|
_hover: {
|
||||||
|
outline: 'none',
|
||||||
|
borderRight: '1px solid transparent',
|
||||||
|
borderLeft: '1px solid transparent',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tabpanel: {
|
||||||
|
border: '1px solid var(--border-color-primary)',
|
||||||
|
borderTop: 'none',
|
||||||
|
borderBottomRightRadius: 'var(--container-radius)',
|
||||||
|
borderBottomLeftRadius: 'var(--container-radius)',
|
||||||
|
padding: 'var(--block-padding)',
|
||||||
|
backgroundColor: 'var(--tab-nav-background-color-selected) !important',
|
||||||
|
gap: '20px',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '500px',
|
||||||
|
overflowY: 'auto',
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
nevyshaExtraNetwork: definePartsStyle({
|
||||||
|
root: {
|
||||||
|
height: '100% !important',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column'
|
||||||
|
},
|
||||||
|
tabpanels: {
|
||||||
|
height: '100% !important',
|
||||||
|
},
|
||||||
|
tab: {
|
||||||
|
marginBottom: '-1px',
|
||||||
|
border: '1px solid transparent',
|
||||||
|
borderColor: 'transparent',
|
||||||
|
borderBottom: 'none',
|
||||||
|
borderRadius: '0 !important',
|
||||||
|
padding: 'var(--size-1) var(--size-4) !important',
|
||||||
|
color: 'var(--body-text-color-subdued) !important',
|
||||||
|
fontWeight: 'var(--section-header-text-weight) !important',
|
||||||
|
fontSize: 'var(--section-header-text-size) !important',
|
||||||
|
borderTop: '2px solid transparent !important',
|
||||||
|
_selected: {
|
||||||
|
borderTop: '2px solid var(--ae-primary-color) !important',
|
||||||
|
backgroundColor: 'var(--tab-nav-background-color-selected) !important',
|
||||||
|
color: 'var(--body-text-color) !important',
|
||||||
|
},
|
||||||
|
_focus: {
|
||||||
|
outline: 'none'
|
||||||
|
},
|
||||||
|
_hover: {
|
||||||
|
outline: 'none',
|
||||||
|
borderRight: '1px solid transparent',
|
||||||
|
borderLeft: '1px solid transparent',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tabpanel: {
|
||||||
|
border: '1px solid var(--border-color-primary)',
|
||||||
|
borderTop: 'none',
|
||||||
|
borderBottomRightRadius: 'var(--container-radius)',
|
||||||
|
borderBottomLeftRadius: 'var(--container-radius)',
|
||||||
|
padding: 'var(--block-padding)',
|
||||||
|
backgroundColor: 'var(--tab-nav-background-color-selected) !important',
|
||||||
|
gap: '20px',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100%',
|
||||||
|
overflowY: 'auto',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { extendTheme } from '@chakra-ui/react'
|
||||||
|
|
||||||
|
|
||||||
|
import {inputTheme, numberInputTheme} from "./Input.theme";
|
||||||
|
import {tabsTheme} from "./Tabs.theme";
|
||||||
|
import {checkboxTheme, radioTheme} from "./Checkbox.theme";
|
||||||
|
import {modalTheme} from "./Modal.theme";
|
||||||
|
|
||||||
|
export const theme = extendTheme({
|
||||||
|
fontSizes: {
|
||||||
|
md: 'var(--body-text-size)',
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Input: inputTheme,
|
||||||
|
Tabs: tabsTheme,
|
||||||
|
Checkbox: checkboxTheme,
|
||||||
|
NumberInput: numberInputTheme,
|
||||||
|
Radio: radioTheme,
|
||||||
|
Modal: modalTheme,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,160 @@
|
||||||
|
import React, {useEffect, useRef, useState} from "react";
|
||||||
|
|
||||||
|
import 'ace-builds'
|
||||||
|
// ace.config.setModuleUrl(
|
||||||
|
// "ace/mode/json_worker",
|
||||||
|
// 'cozy-nest-client/node_modules/ace-builds/src-noconflict/worker-json.js')
|
||||||
|
ace.config.setModuleUrl(
|
||||||
|
"ace/mode/prompt_highlight_rules",
|
||||||
|
"cozy-nest-client/cozy-prompt/prompt_highlight_rules.js");
|
||||||
|
ace.config.setModuleUrl(
|
||||||
|
"ace/mode/prompt",
|
||||||
|
"cozy-nest-client/cozy-prompt/mode-prompt.js");
|
||||||
|
|
||||||
|
import AceEditor from "react-ace";
|
||||||
|
import "./prompt_highlight_rules.js";
|
||||||
|
import "./mode-prompt.js";
|
||||||
|
import "ace-builds/src-noconflict/theme-github_dark";
|
||||||
|
import "ace-builds/src-noconflict/ext-language_tools";
|
||||||
|
|
||||||
|
import './CozyPrompt.css'
|
||||||
|
import useExternalTextareaObserver from "./useExternalTextareaObserver.js";
|
||||||
|
import {Button} from "../image-browser/App.jsx";
|
||||||
|
import {Row} from "../main/Utils.jsx";
|
||||||
|
|
||||||
|
export function App({parentId, containerId}) {
|
||||||
|
|
||||||
|
let savedHeight = localStorage.getItem(`cozy-prompt-height-${containerId}`);
|
||||||
|
savedHeight = savedHeight ? parseInt(savedHeight) : 200;
|
||||||
|
|
||||||
|
const nativeTextarea = document.querySelector(`#${parentId} label textarea`);
|
||||||
|
|
||||||
|
const [nativeIsVisible, setNativeIsVisible] = useState(false);
|
||||||
|
const nativeTextareaValue = useExternalTextareaObserver(`#${parentId} label textarea`);
|
||||||
|
|
||||||
|
const [prompt, setPrompt] = useState('');
|
||||||
|
const editor = useRef();
|
||||||
|
|
||||||
|
const [height, setHeight] = useState(savedHeight);
|
||||||
|
const [dragging, setDragging] = useState(false);
|
||||||
|
const [startY, setStartY] = useState(0);
|
||||||
|
|
||||||
|
const propagate = () => {
|
||||||
|
nativeTextarea.value
|
||||||
|
= prompt
|
||||||
|
|
||||||
|
const event = new Event('input')
|
||||||
|
nativeTextarea.dispatchEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
const handlePromptChange = (event) => {
|
||||||
|
setPrompt(event.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
nativeTextarea.addEventListener('change', handlePromptChange)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
nativeTextarea.removeEventListener('change', handlePromptChange)
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPrompt(nativeTextareaValue)
|
||||||
|
}, [nativeTextareaValue]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!nativeIsVisible) {
|
||||||
|
nativeTextarea.style.display = 'none';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
nativeTextarea.style.display = 'block';
|
||||||
|
//margin-top: 40px;
|
||||||
|
nativeTextarea.style.marginTop = '40px';
|
||||||
|
}
|
||||||
|
}, [nativeIsVisible]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleGlobalMouseUp = () => {
|
||||||
|
if (dragging) {
|
||||||
|
setDragging(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleGlobalMouseMove = (event) => {
|
||||||
|
if (dragging) {
|
||||||
|
const newHeight = height + event.clientY - startY;
|
||||||
|
setHeight(newHeight);
|
||||||
|
localStorage.setItem(`cozy-prompt-height-${containerId}`, String(newHeight));
|
||||||
|
setStartY(event.clientY);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('mouseup', handleGlobalMouseUp);
|
||||||
|
window.addEventListener('mousemove', handleGlobalMouseMove);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('mouseup', handleGlobalMouseUp);
|
||||||
|
window.removeEventListener('mousemove', handleGlobalMouseMove);
|
||||||
|
};
|
||||||
|
}, [dragging]);
|
||||||
|
|
||||||
|
const handleMouseDown = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
setDragging(true);
|
||||||
|
setStartY(event.clientY);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleNative = () => {
|
||||||
|
setNativeIsVisible(!nativeIsVisible);
|
||||||
|
}
|
||||||
|
|
||||||
|
function prettify() {
|
||||||
|
setPrompt(prompt.replaceAll('),', '),\n'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLoadEditor(editor) {
|
||||||
|
editor.renderer.setPadding(10);
|
||||||
|
editor.renderer.setScrollMargin(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="CozyPrompt"
|
||||||
|
style={{ height: `${height}px` }}
|
||||||
|
>
|
||||||
|
<AceEditor
|
||||||
|
ref={editor}
|
||||||
|
onLoad={onLoadEditor}
|
||||||
|
mode="prompt"
|
||||||
|
theme="github_dark"
|
||||||
|
showPrintMargin={false}
|
||||||
|
onChange={setPrompt}
|
||||||
|
onBlur={propagate}
|
||||||
|
value={prompt}
|
||||||
|
name="ace-prompt-editor"
|
||||||
|
editorProps={{ $blockScrolling: true }}
|
||||||
|
style={{width: "100%", height: "100%"}}
|
||||||
|
setOptions={{
|
||||||
|
animatedScroll: true,
|
||||||
|
enableSnippets: true,
|
||||||
|
cursorStyle: "smooth",
|
||||||
|
behavioursEnabled: true,
|
||||||
|
wrapBehavioursEnabled: true,
|
||||||
|
autoScrollEditorIntoView: true,
|
||||||
|
wrap: true,
|
||||||
|
fontSize: "15px",
|
||||||
|
fontFamily: "monospace",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
className="CozyPrompt__resize-handle"
|
||||||
|
/>
|
||||||
|
<Row>
|
||||||
|
<Button onClick={prettify}>Prettify</Button>
|
||||||
|
<Button onClick={toggleNative}>{nativeIsVisible ? "Hide" : "Show"} native textarea</Button>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
.CozyPrompt {
|
||||||
|
min-height: 100px;
|
||||||
|
max-height: 800px;
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid var(--ae-input-border-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CozyPrompt .container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.CozyPrompt .ace_cursor-layer {
|
||||||
|
z-index: 900;
|
||||||
|
}
|
||||||
|
.CozyPrompt .ace_cursor-layer .ace_cursor {
|
||||||
|
z-index: 901;
|
||||||
|
background-color: var(--ae-primary-color);
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
@keyframes blink-ace-animate {
|
||||||
|
from, to { opacity: 0.5; }
|
||||||
|
60% { opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink-ace-animate-smooth {
|
||||||
|
from, to { opacity: 0.5; }
|
||||||
|
45% { opacity: 0.5; }
|
||||||
|
60% { opacity: 0; }
|
||||||
|
85% { opacity: 0; }
|
||||||
|
}
|
||||||
|
.CozyPrompt .ace_active-line {
|
||||||
|
background: #ffffff0d !important;
|
||||||
|
}
|
||||||
|
.CozyPrompt {
|
||||||
|
/*padding: 10px;*/
|
||||||
|
background-color: var(--input-background-fill) !important;
|
||||||
|
}
|
||||||
|
.CozyPrompt .ace_editor {
|
||||||
|
background-color: var(--input-background-fill) !important;
|
||||||
|
}
|
||||||
|
.CozyPrompt .ace_scrollbar::-webkit-scrollbar {
|
||||||
|
width: 5px;
|
||||||
|
}
|
||||||
|
.CozyPrompt .ace_scrollbar::-webkit-scrollbar-track {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
.CozyPrompt .ace_scrollbar::-webkit-scrollbar-thumb {
|
||||||
|
background-color: var(--ae-primary-color);
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
.CozyPrompt__resize-handle {
|
||||||
|
width: 100%;
|
||||||
|
height: 5px;
|
||||||
|
background-color: var(--ae-input-border-color);
|
||||||
|
cursor: row-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Editor color*/
|
||||||
|
.ace_text-layer {
|
||||||
|
color: var(--body-text-color);
|
||||||
|
font-family: monospace !important;
|
||||||
|
}
|
||||||
|
.ace_open-bracket, .ace_close-bracket {
|
||||||
|
color: var(--ae-primary-color);
|
||||||
|
font-weight: bold;;
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
.ace_open-bracket-0, .ace_close-bracket-0 {
|
||||||
|
color: violet;
|
||||||
|
}
|
||||||
|
.ace_open-bracket-1, .ace_close-bracket-1 {
|
||||||
|
color: var(--ae-primary-color);
|
||||||
|
}
|
||||||
|
.ace_open-bracket-2, .ace_close-bracket-2 {
|
||||||
|
color: hotpink;
|
||||||
|
}
|
||||||
|
.ace_open-bracket-3, .ace_close-bracket-3 {
|
||||||
|
color: greenyellow;
|
||||||
|
}
|
||||||
|
.ace_open-bracket-4, .ace_close-bracket-4 {
|
||||||
|
color: #fa662a;
|
||||||
|
}
|
||||||
|
.ace_token {
|
||||||
|
color: #cccc96;
|
||||||
|
font-weight: bold;
|
||||||
|
/*margin: 0 3px;*/
|
||||||
|
}
|
||||||
|
.ace_lora-begin, .ace_lora-end, .ace_lora-inner {
|
||||||
|
color: #c444d5;
|
||||||
|
|
||||||
|
}
|
||||||
|
.ace_lora-begin, .ace_lora-end {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.1em;
|
||||||
|
filter: brightness(1.2);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import React from "react";
|
||||||
|
import ReactDOM from 'react-dom/client'
|
||||||
|
import {App} from "./App.jsx";
|
||||||
|
|
||||||
|
export default function startCozyPrompt(parentId, containerId) {
|
||||||
|
//
|
||||||
|
if (!document.getElementById(parentId)) {
|
||||||
|
setTimeout(() => startCozyPrompt(), 200)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const settingsDiv = document.createElement("div");
|
||||||
|
settingsDiv.id = containerId;
|
||||||
|
settingsDiv.style = 'display: flex; height: fit-content; width: 100%;'
|
||||||
|
|
||||||
|
document.getElementById(parentId)
|
||||||
|
.insertBefore(settingsDiv, document.getElementById(parentId).firstChild);
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById(containerId)).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App containerId={containerId} parentId={parentId}/>
|
||||||
|
</React.StrictMode>,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import {CozyLogger} from "../main/CozyLogger.js";
|
||||||
|
|
||||||
|
ace.define("ace/mode/prompt", ["require", "exports", "module", "ace/lib/oop", "ace/mode/text"], function (require, exports, module) {
|
||||||
|
const oop = require("ace/lib/oop");
|
||||||
|
const TextMode = require("ace/mode/text").Mode;
|
||||||
|
const CustomHighlightRules = require("ace/mode/prompt_highlight_rules").CustomHighlightRules;
|
||||||
|
|
||||||
|
// Define the CustomMode constructor
|
||||||
|
function CustomMode() {
|
||||||
|
this.HighlightRules = CustomHighlightRules;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inherit from the base TextMode
|
||||||
|
oop.inherits(CustomMode, TextMode);
|
||||||
|
|
||||||
|
// Set the mode's name
|
||||||
|
CustomMode.prototype.$id = "ace/mode/prompt";
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
CozyLogger.debug(`what is this:`,this, arguments);
|
||||||
|
}).call(CustomMode.prototype);
|
||||||
|
|
||||||
|
// Export the mode
|
||||||
|
exports.Mode = CustomMode;
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
ace.define("ace/mode/prompt_highlight_rules", ["require", "exports", "module", "ace/lib/oop", "ace/mode/text_highlight_rules"], function (require, exports, module) {
|
||||||
|
const oop = require("ace/lib/oop");
|
||||||
|
const TextHighlightRules = require("ace/mode/text_highlight_rules").TextHighlightRules;
|
||||||
|
|
||||||
|
// Define the CustomHighlightRules constructor
|
||||||
|
function CustomHighlightRules() {
|
||||||
|
// Create an instance of the base TextHighlightRules
|
||||||
|
TextHighlightRules.call(this);
|
||||||
|
|
||||||
|
// Define the regex patterns for different token types
|
||||||
|
|
||||||
|
const openBracket = /[\(\[\{]/;
|
||||||
|
const closeBracket = /[\)\]\}]/;
|
||||||
|
|
||||||
|
let bracketLevel = 0;
|
||||||
|
|
||||||
|
this.$rules = {
|
||||||
|
start: [
|
||||||
|
{
|
||||||
|
token: () => {
|
||||||
|
bracketLevel++;
|
||||||
|
return `open-bracket.open-bracket-${(bracketLevel) % 4}`;
|
||||||
|
},
|
||||||
|
next: "inner",
|
||||||
|
regex: openBracket
|
||||||
|
},
|
||||||
|
{
|
||||||
|
token: () => {
|
||||||
|
bracketLevel--;
|
||||||
|
return `close-bracket.close-bracket-${(bracketLevel+1) % 4}`;
|
||||||
|
},
|
||||||
|
regex: closeBracket,
|
||||||
|
next: "start"
|
||||||
|
},
|
||||||
|
{ regex: /<lora:/, token: "lora-begin", next: "lora" },
|
||||||
|
{ regex: /[,|:]/, token: "token" },
|
||||||
|
{ regex: /\w+/, token: "text" },
|
||||||
|
],
|
||||||
|
lora: [
|
||||||
|
{ regex: '>', token: "lora-end", next: "start" },
|
||||||
|
{ regex: /\w+/, token: "lora-inner" },
|
||||||
|
],
|
||||||
|
inner: [
|
||||||
|
{
|
||||||
|
token: () => {
|
||||||
|
bracketLevel++;
|
||||||
|
return `open-bracket.open-bracket-${(bracketLevel) % 4}`;
|
||||||
|
},
|
||||||
|
regex: openBracket,
|
||||||
|
next: "inner"
|
||||||
|
},
|
||||||
|
{ regex: /[,|:]/, token: "token" },
|
||||||
|
{ regex: /\w+/, token: "inner-bracket" },
|
||||||
|
{
|
||||||
|
token: () => {
|
||||||
|
bracketLevel--;
|
||||||
|
return `close-bracket.close-bracket-${(bracketLevel+1) % 4}`;
|
||||||
|
},
|
||||||
|
regex: closeBracket,
|
||||||
|
next: "start"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inherit from the base TextHighlightRules
|
||||||
|
oop.inherits(CustomHighlightRules, TextHighlightRules);
|
||||||
|
|
||||||
|
// Export the highlight rules
|
||||||
|
exports.CustomHighlightRules = CustomHighlightRules;
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import {CozyLogger} from "../main/CozyLogger.js";
|
||||||
|
|
||||||
|
const useExternalTextareaObserver = (textareaSelector) => {
|
||||||
|
const [value, setValue] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const observerCallback = (mutationsList) => {
|
||||||
|
for (const mutation of mutationsList) {
|
||||||
|
if (mutation.type === 'attributes') {
|
||||||
|
const externalTextarea = document.querySelector(textareaSelector);
|
||||||
|
setValue(externalTextarea.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const observerOptions = {
|
||||||
|
attributes: true,
|
||||||
|
characterData: true,
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const observer = new MutationObserver(observerCallback);
|
||||||
|
const externalTextarea = document.querySelector(textareaSelector);
|
||||||
|
|
||||||
|
if (externalTextarea) {
|
||||||
|
observer.observe(externalTextarea, observerOptions);
|
||||||
|
setValue(externalTextarea.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
observer.disconnect();
|
||||||
|
};
|
||||||
|
}, [textareaSelector]);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useExternalTextareaObserver;
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
// type for image
|
||||||
|
export type Image = {
|
||||||
|
path: string,
|
||||||
|
hash: string,
|
||||||
|
metadata: {
|
||||||
|
date: number
|
||||||
|
exif: {
|
||||||
|
parameters: string,
|
||||||
|
'cozy-nest-tags'?: string
|
||||||
|
'cozy-nest-hidden'?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
.main-btn-label {
|
||||||
|
writing-mode: vertical-rl;
|
||||||
|
width: 25px;
|
||||||
|
min-width: 25px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cozy-extra-network {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: calc(100% - 20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cozy-extra-network > div > .tabs > .tabitem {
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cozy-extra-network > div > .tabs {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#extra_networks_wrapper {
|
||||||
|
position: fixed;
|
||||||
|
top: calc(75px + var(--menu-top-height));
|
||||||
|
height: calc(100% - (100px + var(--menu-top-height)));
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
z-index: 9999;
|
||||||
|
padding-right: 15px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
background-color: var(--block-background-fill) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ExtraNetworkTab {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.ExtraNetworkTab::-webkit-scrollbar {
|
||||||
|
width: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ExtraNetworkTab::-webkit-scrollbar-track {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ExtraNetworkTab::-webkit-scrollbar-thumb {
|
||||||
|
background-color: var(--ae-primary-color);
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cozy-extra-network > div > .tabs > .tabitem::-webkit-scrollbar {
|
||||||
|
width: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cozy-extra-network > div > .tabs > .tabitem::-webkit-scrollbar-track {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cozy-extra-network > div > .tabs > .tabitem::-webkit-scrollbar-thumb {
|
||||||
|
background-color: var(--ae-primary-color);
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import React, {useEffect} from "react";
|
||||||
|
import './ExtraNetworks.css'
|
||||||
|
import {LoaderContext} from "./LoaderContext.jsx";
|
||||||
|
import {CozyLogger} from "../main/CozyLogger.js";
|
||||||
|
|
||||||
|
let extraNetworksParent = null;
|
||||||
|
|
||||||
|
export function ExtraNetworks({prefix}) {
|
||||||
|
|
||||||
|
const ref = React.useRef(null)
|
||||||
|
const {ready} = React.useContext(LoaderContext)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (ready) {
|
||||||
|
loadNativeElements()
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
unLoad()
|
||||||
|
}
|
||||||
|
}, [ready])
|
||||||
|
|
||||||
|
function loadNativeElements() {
|
||||||
|
if (!ref.current) return
|
||||||
|
|
||||||
|
CozyLogger.debug('loading native elements', prefix)
|
||||||
|
|
||||||
|
const tabs = document.querySelector(`#${prefix}_extra_tabs`)
|
||||||
|
extraNetworksParent = tabs.parentNode
|
||||||
|
|
||||||
|
ref.current.appendChild(tabs)
|
||||||
|
}
|
||||||
|
|
||||||
|
function unLoad() {
|
||||||
|
if (!ref.current || !extraNetworksParent) return
|
||||||
|
|
||||||
|
CozyLogger.debug('unloading native elements', prefix)
|
||||||
|
|
||||||
|
const tabs = document.querySelector(`#${prefix}_extra_tabs`)
|
||||||
|
|
||||||
|
extraNetworksParent.appendChild(tabs)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref} style={{height:'100%'}} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {CozyLogger} from "../main/CozyLogger.js";
|
||||||
|
|
||||||
|
export const LoaderContext = React.createContext({
|
||||||
|
ready: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
function observeDivChanges(targetDiv) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let timer; // Holds the timeout reference
|
||||||
|
|
||||||
|
const observer = new MutationObserver((mutationsList, observer) => {
|
||||||
|
clearTimeout(timer); // Clear previous timeout
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
observer.disconnect(); // Stop observing mutations
|
||||||
|
resolve(); // Resolve the Promise
|
||||||
|
}, 200);
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(targetDiv, { attributes: true, childList: true, subtree: true });
|
||||||
|
|
||||||
|
// If the initial state of the div is already unchanged, resolve the Promise immediately
|
||||||
|
if (!targetDiv.hasChildNodes() && !targetDiv.attributes.length) {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function requireNativeBloc(prefix) {
|
||||||
|
|
||||||
|
const triggerButton = document.querySelector(`button#${prefix}_extra_networks`)
|
||||||
|
|
||||||
|
CozyLogger.debug('triggering extra network', prefix)
|
||||||
|
|
||||||
|
triggerButton.style.display = 'none'
|
||||||
|
|
||||||
|
triggerButton.click()
|
||||||
|
|
||||||
|
const tabs = document.querySelector(`#${prefix}_extra_tabs`)
|
||||||
|
|
||||||
|
//setup a mutation observer to detect when the tabs are added
|
||||||
|
await observeDivChanges(tabs)
|
||||||
|
triggerButton.click()
|
||||||
|
CozyLogger.debug('tabs loaded', prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
//we use a local not hook to avoid async issues and double call
|
||||||
|
const states = {}
|
||||||
|
|
||||||
|
export function LoaderProvider({children, prefix, resolve}) {
|
||||||
|
|
||||||
|
const [ready, setReady] = React.useState(false)
|
||||||
|
|
||||||
|
if (!states[prefix]) {
|
||||||
|
states[prefix] = {
|
||||||
|
loaded: false,
|
||||||
|
loading: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const {ready, loading} = states[prefix];
|
||||||
|
if (ready || loading) return;
|
||||||
|
|
||||||
|
states[prefix] = {
|
||||||
|
loaded: false,
|
||||||
|
loading: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
await requireNativeBloc(prefix)
|
||||||
|
states[prefix] = {
|
||||||
|
loaded: true,
|
||||||
|
loading: false,
|
||||||
|
}
|
||||||
|
setReady(true)
|
||||||
|
resolve()
|
||||||
|
})()
|
||||||
|
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const value = {
|
||||||
|
ready,
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LoaderContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</LoaderContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom/client'
|
||||||
|
|
||||||
|
import {ExtraNetworks} from "./ExtraNetworks.jsx";
|
||||||
|
import {LoaderProvider} from "./LoaderContext.jsx";
|
||||||
|
import {CozyLogger} from "../main/CozyLogger.js";
|
||||||
|
|
||||||
|
export function startExtraNetwork(prefix) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
_startExtraNetwork(prefix, resolve)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function _startExtraNetwork(prefix, resolve) {
|
||||||
|
|
||||||
|
CozyLogger.debug('startExtraNetwork', prefix)
|
||||||
|
|
||||||
|
if (!document.getElementById(`cozy-${prefix}-extra-network-react`)) {
|
||||||
|
CozyLogger.debug('waiting for extra network react', prefix)
|
||||||
|
setTimeout(() => _startExtraNetwork(), 200)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById(`cozy-${prefix}-extra-network-react`)).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<LoaderProvider prefix={prefix} resolve={resolve}>
|
||||||
|
<ExtraNetworks prefix={prefix} />
|
||||||
|
</LoaderProvider>
|
||||||
|
</React.StrictMode>,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -9,20 +9,13 @@
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
.flex-column {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.flex-row {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
.browser {
|
.browser {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
/*overflow-y: scroll;*/
|
/*overflow-y: scroll;*/
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
overflow: auto !important;
|
overflow: auto !important;
|
||||||
|
align-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hackyOffPageElement {
|
.hackyOffPageElement {
|
||||||
|
|
@ -62,6 +55,7 @@
|
||||||
border: 1px solid var(--ae-input-border-color);
|
border: 1px solid var(--ae-input-border-color);
|
||||||
background: var(--ae-input-bg-color);
|
background: var(--ae-input-bg-color);
|
||||||
color: var(--ae-input-color);
|
color: var(--ae-input-color);
|
||||||
|
gap: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.infoModal {
|
.infoModal {
|
||||||
|
|
@ -123,74 +117,63 @@ textarea {
|
||||||
/* margin-bottom: 6px; */
|
/* margin-bottom: 6px; */
|
||||||
position: absolute;
|
position: absolute;
|
||||||
color: #e32f4e;
|
color: #e32f4e;
|
||||||
/* left: 5px; */
|
left: 2px;
|
||||||
top: 5px;
|
top: 2px;
|
||||||
}:root {
|
|
||||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
||||||
line-height: 1.5;
|
|
||||||
font-weight: 400;
|
|
||||||
|
|
||||||
color-scheme: light dark;
|
|
||||||
color: rgba(255, 255, 255, 0.87);
|
|
||||||
background-color: #242424;
|
|
||||||
|
|
||||||
font-synthesis: none;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
.cnib-button {
|
||||||
font-weight: 500;
|
width: 100%;
|
||||||
color: #646cff;
|
|
||||||
text-decoration: inherit;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color: #535bf2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
input[type="radio"] {
|
||||||
margin: 0;
|
display: inline-block;
|
||||||
display: flex;
|
flex-shrink: 0;
|
||||||
place-items: center;
|
vertical-align: middle;
|
||||||
min-width: 320px;
|
appearance: none;
|
||||||
min-height: 100vh;
|
border-width: 1px;
|
||||||
|
border-color: #6b7280;
|
||||||
|
background-origin: border-box;
|
||||||
|
padding: 0;
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
color: #2563eb;
|
||||||
|
user-select: none;
|
||||||
|
--ring-color: transparent;
|
||||||
|
position: relative;
|
||||||
|
box-shadow: var(--checkbox-shadow);
|
||||||
|
border: var(--checkbox-border-width) solid var(--checkbox-border-color);
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
background-color: var(--checkbox-background-color) !important;
|
||||||
|
line-height: var(--line-sm);
|
||||||
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
.cozy-radio-label {
|
||||||
font-size: 3.2em;
|
text-overflow: clip;
|
||||||
line-height: 1.1;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
.cozy-websocket-status {
|
||||||
border-radius: 8px;
|
justify-content: end;
|
||||||
border: 1px solid transparent;
|
display: flex;
|
||||||
padding: 0.6em 1.2em;
|
flex-direction: column;
|
||||||
font-size: 1em;
|
width: fit-content;
|
||||||
font-weight: 500;
|
align-items: flex-end;
|
||||||
font-family: inherit;
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: border-color 0.25s;
|
|
||||||
}
|
}
|
||||||
button:hover {
|
.cozy-websocket-status > span {
|
||||||
border-color: #646cff;
|
text-overflow: clip;
|
||||||
}
|
white-space: nowrap;
|
||||||
button:focus,
|
|
||||||
button:focus-visible {
|
|
||||||
outline: 4px auto -webkit-focus-ring-color;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
.cozy-nest-loading {
|
||||||
:root {
|
position: absolute;
|
||||||
color: #213547;
|
width: 100%;
|
||||||
background-color: #ffffff;
|
height: 100%;
|
||||||
}
|
display: flex;
|
||||||
a:hover {
|
align-content: center;
|
||||||
color: #747bff;
|
justify-content: center;
|
||||||
}
|
align-items: center;
|
||||||
button {
|
flex-direction: column;
|
||||||
background-color: #f9f9f9;
|
gap: 25px;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,287 @@
|
||||||
|
import React, {useEffect, useState, useCallback, useContext} from 'react'
|
||||||
|
import './App.css'
|
||||||
|
import useWebSocket, { ReadyState } from 'react-use-websocket';
|
||||||
|
import Browser from "./Browser.jsx";
|
||||||
|
import {MockImageBrowser} from "./MockImageBrowser.jsx";
|
||||||
|
import {CozyLogger} from "../main/CozyLogger.js";
|
||||||
|
import Loader from "react-spinners/HashLoader";
|
||||||
|
import {ImagesContext} from "./ImagesContext.tsx";
|
||||||
|
import {Column, Row} from "../main/Utils.jsx";
|
||||||
|
import {CozyTagsSelect} from "./CozyTags.jsx";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export function Button(props) {
|
||||||
|
return <button
|
||||||
|
{...props}
|
||||||
|
className="nevysha lg primary gradio-button btn cnib-button"
|
||||||
|
>{props.children}</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function Loading(props) {
|
||||||
|
|
||||||
|
const config = JSON.parse(localStorage.getItem('COZY_NEST_CONFIG'))
|
||||||
|
|
||||||
|
const color = config['accent_color'] || '#36d7b7'
|
||||||
|
const label = props.label || ''
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='cozy-nest-loading'>
|
||||||
|
<div>{label}</div>
|
||||||
|
<Loader color={color} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
|
||||||
|
const config = JSON.parse(localStorage.getItem('COZY_NEST_CONFIG'))
|
||||||
|
const disable_image_browser =
|
||||||
|
config['disable_image_browser']
|
||||||
|
|
||||||
|
const serverPort = (() => {
|
||||||
|
try {
|
||||||
|
return config.server_default_port
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
CozyLogger.debug('cnib_socket_server_port not found in main gradio app')
|
||||||
|
return 3333;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (disable_image_browser) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MockImageBrowser/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
images,
|
||||||
|
setImages,
|
||||||
|
setFilteredImages,
|
||||||
|
setTags,
|
||||||
|
} = useContext(ImagesContext)
|
||||||
|
|
||||||
|
const [socketUrl, setSocketUrl] = useState(`ws://localhost:${serverPort}`);
|
||||||
|
const [, setMessageHistory] = useState([]);
|
||||||
|
const [activeTags, setActiveTags] = useState([])
|
||||||
|
const [searchStr, setSearchStr] = useState('');
|
||||||
|
const [emptyFetch, setEmptyFetch] = useState(false);
|
||||||
|
const [visibilityFilter, setVisibilityFilter] = useState('radio-hide-hidden');
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
|
|
||||||
|
const { sendMessage, lastMessage, readyState, getWebSocket }
|
||||||
|
= useWebSocket(
|
||||||
|
socketUrl,
|
||||||
|
{
|
||||||
|
shouldReconnect: () => disable_image_browser,
|
||||||
|
reconnectAttempts: 10,
|
||||||
|
reconnectInterval: 3000,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
//cheat to get the websocket object
|
||||||
|
window.ws = getWebSocket();
|
||||||
|
|
||||||
|
const askForImages = useCallback(() => sendMessage(
|
||||||
|
JSON.stringify({what: "images"})), [sendMessage]
|
||||||
|
);
|
||||||
|
|
||||||
|
const reconnect = () => {
|
||||||
|
|
||||||
|
if (readyState === ReadyState.OPEN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// force a dummy url change
|
||||||
|
setSocketUrl(socketUrl + '?t=' + Date.now())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyActiveFilter() {
|
||||||
|
return images
|
||||||
|
.filter(image => {
|
||||||
|
if (visibilityFilter === 'radio-hide-hidden') {
|
||||||
|
return image.metadata.exif['cozy-nest-hidden'] !== 'True';
|
||||||
|
} else if (visibilityFilter === 'radio-only-hidden') {
|
||||||
|
return !(!image.metadata.exif['cozy-nest-hidden'] || image.metadata.exif['cozy-nest-hidden'] !== 'True');
|
||||||
|
} else return true;
|
||||||
|
})
|
||||||
|
.filter(image => {
|
||||||
|
if (activeTags.length === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (image.metadata.exif['cozy-nest-tags']) {
|
||||||
|
const imgTags = image.metadata.exif['cozy-nest-tags'].split(',')
|
||||||
|
const intersection = imgTags.filter(tag => activeTags.includes(tag))
|
||||||
|
return intersection.length > 0;
|
||||||
|
}
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//get images from server and set state
|
||||||
|
useEffect(() => {
|
||||||
|
if (lastMessage !== null) {
|
||||||
|
const data = JSON.parse(lastMessage.data)
|
||||||
|
if (data.error) {
|
||||||
|
if (window.errorPipe) {
|
||||||
|
window.errorPipe(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.what === 'images') {
|
||||||
|
if (data.images.length === 0) {
|
||||||
|
CozyLogger.debug('Received empty images array from socket')
|
||||||
|
//disable images fetch loop
|
||||||
|
setEmptyFetch(true)
|
||||||
|
}
|
||||||
|
setImages(data.images)
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
if (data.what === 'dispatch_on_image_saved') {
|
||||||
|
//add at the beginning of the array
|
||||||
|
setImages(prev => [data.data, ...prev])
|
||||||
|
}
|
||||||
|
if (data.what === 'dispatch_on_index_built') {
|
||||||
|
setImages([...data.data])
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
setMessageHistory((prev) => prev.concat(lastMessage));
|
||||||
|
}
|
||||||
|
}, [lastMessage, setMessageHistory]);
|
||||||
|
|
||||||
|
//if images is empty, load images
|
||||||
|
useEffect(() => {
|
||||||
|
if (images.length === 0 && readyState === ReadyState.OPEN && !emptyFetch) {
|
||||||
|
askForImages()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setFilteredImages(applyActiveFilter())
|
||||||
|
}
|
||||||
|
}, [images, readyState])
|
||||||
|
|
||||||
|
//if searchStr is not empty, filter images
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
if (searchStr !== '') {
|
||||||
|
const _filteredImages = applyActiveFilter().filter(image => {
|
||||||
|
return JSON.stringify(image.metadata.exif).includes(searchStr);
|
||||||
|
})
|
||||||
|
setFilteredImages(_filteredImages)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setFilteredImages(applyActiveFilter())
|
||||||
|
}
|
||||||
|
}, [searchStr, visibilityFilter, activeTags, images])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const _tags = []
|
||||||
|
images.forEach(image => {
|
||||||
|
if (image.metadata.exif['cozy-nest-tags']) {
|
||||||
|
const imgTags = image.metadata.exif['cozy-nest-tags'].split(',')
|
||||||
|
_tags.push(...imgTags)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setTags([..._tags])
|
||||||
|
}, [images, visibilityFilter])
|
||||||
|
|
||||||
|
const connectionStatus = {
|
||||||
|
[ReadyState.CONNECTING]: 'Connecting',
|
||||||
|
[ReadyState.OPEN]: 'Connected',
|
||||||
|
[ReadyState.CLOSING]: 'Closing',
|
||||||
|
[ReadyState.CLOSED]: 'Closed',
|
||||||
|
[ReadyState.UNINSTANTIATED]: 'Uninstantiated',
|
||||||
|
}[readyState];
|
||||||
|
|
||||||
|
const connexionStatusStyle = {
|
||||||
|
color:
|
||||||
|
readyState === ReadyState.OPEN
|
||||||
|
? 'green'
|
||||||
|
: readyState === ReadyState.CONNECTING
|
||||||
|
? 'orange'
|
||||||
|
: 'red',
|
||||||
|
};
|
||||||
|
|
||||||
|
const rebuildIndex = async () => {
|
||||||
|
//fetch to @app.delete("/cozy-nest/index")
|
||||||
|
const res = await fetch('/cozy-nest/index', {
|
||||||
|
method: 'DELETE',
|
||||||
|
})
|
||||||
|
if (res.ok) {
|
||||||
|
setImages([])
|
||||||
|
setIsLoading(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Column>
|
||||||
|
<Row>
|
||||||
|
<Row>
|
||||||
|
<h1 className="cnib-title"><span className="beta-emphasis">beta</span></h1>
|
||||||
|
<button
|
||||||
|
className="nevysha lg primary gradio-button btn"
|
||||||
|
style={{width: '100px'}}
|
||||||
|
onClick={rebuildIndex}
|
||||||
|
>
|
||||||
|
Rebuild Index
|
||||||
|
</button>
|
||||||
|
</Row>
|
||||||
|
<Row style={{width: 'auto'}} className='cozy-websocket-status'>
|
||||||
|
<span>WebSocket status <span className="connexionStatus" style={connexionStatusStyle}>{connectionStatus}</span></span>
|
||||||
|
{readyState !== ReadyState.OPEN && <button
|
||||||
|
className="nevysha lg primary gradio-button btn"
|
||||||
|
style={{marginLeft: '20px', width: '100px'}}
|
||||||
|
onClick={reconnect}
|
||||||
|
>
|
||||||
|
Connect
|
||||||
|
</button>}
|
||||||
|
</Row>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Row style={{gap:'10px', marginRight: '2px', width:'fit-content'}} onChange={(e) => setVisibilityFilter(e.target.id)}>
|
||||||
|
{/*radio button for filter : Hide hidden, All, Only hidden*/}
|
||||||
|
<Row style={{width: 'auto', alignItems: 'center'}}>
|
||||||
|
<input type="radio" id="radio-hide-hidden" name="radio-filter" value="all" defaultChecked/>
|
||||||
|
<label className="cozy-radio-label" htmlFor="radio-hide-hidden">Hide hidden</label>
|
||||||
|
</Row>
|
||||||
|
<Row style={{width: 'auto', alignItems: 'center'}}>
|
||||||
|
<input type="radio" id="radio-all" name="radio-filter" value="all"/>
|
||||||
|
<label className="cozy-radio-label" htmlFor="radio-all">All</label>
|
||||||
|
</Row>
|
||||||
|
<Row style={{width: 'auto', alignItems: 'center'}}>
|
||||||
|
<input type="radio" id="radio-only-hidden" name="radio-filter" value="hidden"/>
|
||||||
|
<label className="cozy-radio-label" htmlFor="radio-only-hidden">Only hidden</label>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
</Row>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<textarea data-testid="textbox"
|
||||||
|
placeholder="Search anything : Tags, Prompt, Size, Model, ..."
|
||||||
|
rows="1"
|
||||||
|
spellCheck="false"
|
||||||
|
data-gramm="false"
|
||||||
|
onChange={(e) => setSearchStr(e.target.value)}/>
|
||||||
|
<CozyTagsSelect setActiveTags={setActiveTags} />
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
</Column>
|
||||||
|
{!isLoading &&
|
||||||
|
|
||||||
|
<Browser />
|
||||||
|
|
||||||
|
}
|
||||||
|
{isLoading && <Loading label="building Index..."/>}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
import React, {useContext, useEffect, useRef, useState} from "react";
|
||||||
|
import CozyImage from "./CozyImage.jsx";
|
||||||
|
import {ImagesContext} from "./ImagesContext.tsx";
|
||||||
|
|
||||||
|
const _LAZY_LOAD_MARGIN = 300
|
||||||
|
|
||||||
|
export default function Browser(props) {
|
||||||
|
|
||||||
|
const {images, filteredImages} = useContext(ImagesContext)
|
||||||
|
|
||||||
|
const _me = useRef(null)
|
||||||
|
const [page, setPage] = useState(0)
|
||||||
|
const [imagesLoaded, setImagesLoaded] = useState([])
|
||||||
|
|
||||||
|
const [viewPort, setViewPort] = useState({
|
||||||
|
top: 0,
|
||||||
|
bottom: window.innerHeight + _LAZY_LOAD_MARGIN
|
||||||
|
})
|
||||||
|
|
||||||
|
//when imagesRef changes, reset imagesLoaded
|
||||||
|
useEffect(() => {
|
||||||
|
setImagesLoaded([...filteredImages.slice(0, Math.min(page*20+20, filteredImages.length))])
|
||||||
|
}, [filteredImages, images])
|
||||||
|
|
||||||
|
|
||||||
|
//load 20 images on mount when imagesRef is set
|
||||||
|
if (filteredImages.length > 0 && imagesLoaded.length === 0) {
|
||||||
|
setImagesLoaded(filteredImages.slice(0, Math.min(20, filteredImages.length)))
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollHandler = () => {
|
||||||
|
maybeLoadMore()
|
||||||
|
|
||||||
|
let _page = Math.floor(imagesLoaded.length / 20)
|
||||||
|
if (_page !== page) {
|
||||||
|
setPage(_page)
|
||||||
|
}
|
||||||
|
|
||||||
|
const _viewPort = {
|
||||||
|
top: (_me.current.scrollTop - _LAZY_LOAD_MARGIN) > 0 ? (_me.current.scrollTop - _LAZY_LOAD_MARGIN) : 0,
|
||||||
|
bottom: _me.current.scrollTop + _me.current.clientHeight + _LAZY_LOAD_MARGIN
|
||||||
|
}
|
||||||
|
setViewPort(_viewPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
const maybeLoadMore = () => {
|
||||||
|
//check if loadMoreThreshold is visible
|
||||||
|
const loadMoreThreshold = document.getElementById("loadMoreThreshold")
|
||||||
|
if (loadMoreThreshold.getBoundingClientRect().top < window.innerHeight) {
|
||||||
|
//load 20 more images
|
||||||
|
setImagesLoaded(filteredImages.slice(0, page*20+20))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="browser nevysha nevysha-scrollable" onScroll={() => scrollHandler()} ref={_me}>
|
||||||
|
{imagesLoaded.map((image, index) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CozyImage
|
||||||
|
key={image.hash}
|
||||||
|
imageHash={image.hash}
|
||||||
|
index={index}
|
||||||
|
viewPort={viewPort}/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
<div id="loadMoreThreshold" className="hackyOffPageElement"/>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
import React, {useContext, useEffect, useState} from "react";
|
||||||
|
import {CozyLogger} from "../main/CozyLogger.js";
|
||||||
|
import {Button} from "./App.jsx";
|
||||||
|
import {Column, Row} from "../main/Utils.jsx";
|
||||||
|
|
||||||
|
import './editor/ExifEditor.css'
|
||||||
|
import Exif from "./editor/ExifEditor.jsx";
|
||||||
|
import {ImagesContext} from "./ImagesContext.tsx";
|
||||||
|
import {ButtonWithConfirmDialog} from "../chakra/ButtonWithConfirmDialog.jsx";
|
||||||
|
|
||||||
|
|
||||||
|
function SendTo({imageHash}) {
|
||||||
|
|
||||||
|
const {images, getImage} = useContext(ImagesContext)
|
||||||
|
|
||||||
|
const [image, setImage] = useState(
|
||||||
|
getImage(imageHash)
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setImage(getImage(imageHash));
|
||||||
|
}, [images, imageHash]);
|
||||||
|
|
||||||
|
const sendToPipe = (e, where) => {
|
||||||
|
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
if (window.sendToPipe) {
|
||||||
|
let _img = {src: `/cozy-nest/image?path=${image.path}`}
|
||||||
|
window.sendToPipe(where, _img)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Row>
|
||||||
|
<Button onClick={(e) => sendToPipe(e, 'txt2img')}
|
||||||
|
>txt2img</Button>
|
||||||
|
<Button onClick={(e) => sendToPipe(e, 'img2img')}
|
||||||
|
>img2img</Button>
|
||||||
|
<Button onClick={(e) => sendToPipe(e, 'inpainting')}
|
||||||
|
>inpainting</Button>
|
||||||
|
</Row>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Controls({imageHash}) {
|
||||||
|
|
||||||
|
const {images, deleteImg, updateExifInState, getImage} = useContext(ImagesContext)
|
||||||
|
|
||||||
|
const [showExifEditor, setShowExifEditor] = useState(false);
|
||||||
|
const [exif, setExif] = useState({});
|
||||||
|
const [isHidden, setIsHidden] = useState(false);
|
||||||
|
|
||||||
|
const [image, setImage] = useState(
|
||||||
|
getImage(imageHash)
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setImage(getImage(imageHash));
|
||||||
|
}, [images, imageHash]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!image || !image.path) return
|
||||||
|
setExif(image.metadata.exif)
|
||||||
|
}, [image, imageHash])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!exif) return;
|
||||||
|
|
||||||
|
setIsHidden(exif['cozy-nest-hidden'] === 'True')
|
||||||
|
|
||||||
|
}, [exif, image, images])
|
||||||
|
|
||||||
|
const editExif = async () => {
|
||||||
|
setShowExifEditor(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const hideImg = async () => {
|
||||||
|
const path = image.path
|
||||||
|
|
||||||
|
exif['cozy-nest-hidden'] = "True"
|
||||||
|
setExif(exif)
|
||||||
|
|
||||||
|
await Exif.save(path, exif)
|
||||||
|
updateExifInState(image)
|
||||||
|
}
|
||||||
|
const unhideImg = async () => {
|
||||||
|
const path = image.path
|
||||||
|
|
||||||
|
exif['cozy-nest-hidden'] = "False"
|
||||||
|
setExif(exif)
|
||||||
|
|
||||||
|
await Exif.save(path, exif)
|
||||||
|
updateExifInState(image)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ExifEditor = Exif.ExifEditor
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column style={{height: "100%", justifyContent: "space-between"}}>
|
||||||
|
<SendTo imageHash={imageHash}/>
|
||||||
|
<Column>
|
||||||
|
<Row>
|
||||||
|
{showExifEditor &&
|
||||||
|
<ExifEditor imageHash={imageHash} exif={exif} visible={showExifEditor}
|
||||||
|
onClose={() => setShowExifEditor(false)}/>
|
||||||
|
}
|
||||||
|
<Button onClick={editExif}>Edit Exif</Button>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
{!isHidden && <Button onClick={hideImg}>Hide</Button>}
|
||||||
|
{isHidden && <Button onClick={unhideImg}>Show</Button>}
|
||||||
|
<Button onClick={() => deleteImg('archive', image)}>Move to archive</Button>
|
||||||
|
<ButtonWithConfirmDialog
|
||||||
|
style={{height: '100%'}}
|
||||||
|
message='This action cannot be undone. Are you sure?'
|
||||||
|
confirmLabel='Delete'
|
||||||
|
cancelLabel="Cancel"
|
||||||
|
onConfirm={() => deleteImg('delete', image)}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
</Column>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
//base url without port
|
||||||
|
import React, {useContext, useEffect, useRef, useState} from "react";
|
||||||
|
import {CozyImageInfo} from "./CozyImageInfo.jsx";
|
||||||
|
import {ImagesContext} from "./ImagesContext.tsx";
|
||||||
|
|
||||||
|
const baseUrl = window.location.href.split(":")[0] + ":" + window.location.href.split(":")[1]
|
||||||
|
const gradioPort = 7860
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default function CozyImage({viewPort, imageHash, index}) {
|
||||||
|
|
||||||
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
const imgRef = useRef(null);
|
||||||
|
const _me = useRef(null);
|
||||||
|
const {getImage} = useContext(ImagesContext)
|
||||||
|
const [onScreen, setOnScreen] = useState(false);
|
||||||
|
|
||||||
|
const [image, setImage] = useState(
|
||||||
|
getImage(imageHash)
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setImage(getImage(imageHash));
|
||||||
|
}, [imageHash]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
const top = _me.current.offsetTop
|
||||||
|
const isOnScreen =
|
||||||
|
top >= viewPort.top && top <= (viewPort.bottom + _me.current.offsetHeight) ||
|
||||||
|
(top + _me.current.offsetHeight) >= viewPort.top && (top + _me.current.offsetHeight) <= viewPort.bottom
|
||||||
|
|
||||||
|
if (isOnScreen) {
|
||||||
|
setOnScreen(true)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setOnScreen(false)
|
||||||
|
}
|
||||||
|
}, [viewPort])
|
||||||
|
|
||||||
|
function toggleModal() {
|
||||||
|
setShowModal(!showModal)
|
||||||
|
}
|
||||||
|
function openModal() {
|
||||||
|
if (showModal) return
|
||||||
|
setShowModal(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSrc() {
|
||||||
|
// url encode path
|
||||||
|
const sanitizedPath = encodeURIComponent(image.path)
|
||||||
|
return `${baseUrl}:${gradioPort}/cozy-nest/image?path=${sanitizedPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
<div id={`img_${index}`} className="image" ref={_me}>
|
||||||
|
{onScreen ? (<>
|
||||||
|
<div className="image-wrapper" onClick={openModal}>
|
||||||
|
<img
|
||||||
|
className="cozy-nest-thumbnail"
|
||||||
|
src={getSrc()}
|
||||||
|
alt="image"
|
||||||
|
ref={imgRef}/>
|
||||||
|
</div>
|
||||||
|
<CozyImageInfo verbose={false} imageHash={imageHash}/>
|
||||||
|
{showModal && <div className="infoModal">
|
||||||
|
<div className="image-wrapper">
|
||||||
|
<img
|
||||||
|
className="cozy-nest-thumbnail"
|
||||||
|
src={getSrc()}
|
||||||
|
alt="image"/>
|
||||||
|
</div>
|
||||||
|
<CozyImageInfo verbose={true} imageHash={imageHash} closeModal={toggleModal} />
|
||||||
|
</div>}
|
||||||
|
</>) : (<div className="image image-placeholder"/>)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
import {Row} from "../main/Utils.jsx";
|
||||||
|
import {Controls} from "./Controls.jsx";
|
||||||
|
import React, {useContext, useEffect, useState} from "react";
|
||||||
|
import Exif from "./editor/ExifEditor.jsx";
|
||||||
|
import {ImagesContext} from "./ImagesContext.tsx";
|
||||||
|
import {CozyLogger} from "../main/CozyLogger.js";
|
||||||
|
import {CozyTags} from "./CozyTags.jsx";
|
||||||
|
|
||||||
|
const tryCatch = (fn) => {
|
||||||
|
try {
|
||||||
|
return fn()
|
||||||
|
} catch (ignored) {
|
||||||
|
return 'Error parsing metadata'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CozyImageInfo({verbose, imageHash, closeModal}) {
|
||||||
|
|
||||||
|
const {images, updateExifInState, getImage} = useContext(ImagesContext)
|
||||||
|
|
||||||
|
const [image, setImage] = useState(
|
||||||
|
getImage(imageHash)
|
||||||
|
);
|
||||||
|
|
||||||
|
const [formattedExif, setFormattedExif] = useState({
|
||||||
|
date: 0,
|
||||||
|
model: '',
|
||||||
|
size: '',
|
||||||
|
seed: '',
|
||||||
|
steps: '',
|
||||||
|
sampler: '',
|
||||||
|
modelHash: '',
|
||||||
|
formattedAll: ''
|
||||||
|
})
|
||||||
|
const isVerbose = verbose;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setImage(getImage(imageHash));
|
||||||
|
}, [images, imageHash]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!image) return
|
||||||
|
if (!image.metadata) return
|
||||||
|
if (!image.metadata.exif) return
|
||||||
|
if (!image.metadata.exif.parameters) return
|
||||||
|
|
||||||
|
setFormattedExif({
|
||||||
|
date: tryCatch(() => new Date(image.metadata.date * 1000).toISOString().replace(/T/, ' ').replace(/\..+/, '')),
|
||||||
|
model: tryCatch(() => image.metadata.exif.parameters.split("Model: ")[1].split(",")[0]),
|
||||||
|
size: tryCatch(() => image.metadata.exif.parameters.split("Size: ")[1].split(",")[0]),
|
||||||
|
seed: tryCatch(() => image.metadata.exif.parameters.split("Seed: ")[1].split(",")[0]),
|
||||||
|
steps: tryCatch(() => image.metadata.exif.parameters.split("Steps: ")[1].split(",")[0]),
|
||||||
|
sampler: tryCatch(() => image.metadata.exif.parameters.split("Sampler: ")[1].split(",")[0]),
|
||||||
|
modelHash: tryCatch(() => image.metadata.exif.parameters.split("Model hash: ")[1].split(",")[0]),
|
||||||
|
formattedAll: tryCatch(() => image.metadata.exif.parameters.replace(/\n/g, "<br>"))
|
||||||
|
})
|
||||||
|
}, [image])
|
||||||
|
|
||||||
|
const close = async () => {
|
||||||
|
closeModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!image) return (<div className="image-info">No image selected</div>)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="image-info">
|
||||||
|
{isVerbose &&
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
className="nevysha lg primary gradio-button btn"
|
||||||
|
onClick={close}>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
<CozyTags imageHash={imageHash} isFull={isVerbose}/>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>Date: </td><td>{formattedExif?.date}</td></tr>
|
||||||
|
<tr><td>Model: </td><td>{formattedExif?.model}</td></tr>
|
||||||
|
{isVerbose && <tr>
|
||||||
|
<td>Model Hash:</td>
|
||||||
|
<td>{formattedExif?.modelHash}</td>
|
||||||
|
</tr>}
|
||||||
|
<tr><td>Size: </td><td>{formattedExif?.size}</td></tr>
|
||||||
|
<tr><td>Seed: </td><td>{formattedExif?.seed}</td></tr>
|
||||||
|
<tr><td>Steps: </td><td>{formattedExif?.steps}</td></tr>
|
||||||
|
<tr><td>Sampler: </td><td>{formattedExif?.sampler}</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{isVerbose && <div className="blocInfo" dangerouslySetInnerHTML={{__html: formattedExif?.formattedAll}}/>}
|
||||||
|
<Controls imageHash={imageHash}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,154 @@
|
||||||
|
import React, {useCallback, useContext, useEffect, useRef, useState} from 'react';
|
||||||
|
import {ImagesContext} from "./ImagesContext";
|
||||||
|
import makeAnimated from 'react-select/animated';
|
||||||
|
import CreatableSelect from "react-select/creatable";
|
||||||
|
import Select from 'react-select';
|
||||||
|
import {saveExif} from "./editor/ExifEditor.jsx";
|
||||||
|
import {CozyLogger} from "../main/CozyLogger.js";
|
||||||
|
|
||||||
|
|
||||||
|
const animatedComponents = makeAnimated();
|
||||||
|
|
||||||
|
// StylesConfig
|
||||||
|
const styles = {
|
||||||
|
container: (state) => ({
|
||||||
|
...state,
|
||||||
|
width: '100%',
|
||||||
|
}),
|
||||||
|
control: (state) => ({
|
||||||
|
...state,
|
||||||
|
borderRadius:0,
|
||||||
|
border: '1px solid var(--border-color-primary)',
|
||||||
|
background: 'var(--input-background-fill)',
|
||||||
|
width: '100%',
|
||||||
|
}),
|
||||||
|
option: (state) => ({
|
||||||
|
...state,
|
||||||
|
borderRadius:0,
|
||||||
|
color: 'var(--body-text-color)',
|
||||||
|
background: 'var(--background-fill-primary)',
|
||||||
|
'&:hover': {
|
||||||
|
background: 'var(--ae-primary-color)'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
menu: (state) => ({
|
||||||
|
...state,
|
||||||
|
borderRadius:0,
|
||||||
|
background: 'var(--background-fill-primary)',
|
||||||
|
border: '1px solid var(--ae-input-border-color) !important'
|
||||||
|
}),
|
||||||
|
multiValue: (state) => ({
|
||||||
|
...state,
|
||||||
|
borderRadius:0,
|
||||||
|
background: 'var(--background-fill-primary)',
|
||||||
|
color: 'var(--nevysha-font-color)',
|
||||||
|
}),
|
||||||
|
multiValueLabel: (styles) => ({
|
||||||
|
...styles,
|
||||||
|
color: 'var(--nevysha-font-color)',
|
||||||
|
}),
|
||||||
|
multiValueRemove: (styles) => ({
|
||||||
|
...styles,
|
||||||
|
':hover': {
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
indicatorContainer: (styles) => ({
|
||||||
|
...styles,
|
||||||
|
color: 'var(--nevysha-font-color)',
|
||||||
|
padding: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function CozyTags({imageHash, isFull}) {
|
||||||
|
|
||||||
|
const {getImage, tags, setTags} = useContext(ImagesContext)
|
||||||
|
|
||||||
|
const [image, setImage] = useState(
|
||||||
|
getImage(imageHash)
|
||||||
|
);
|
||||||
|
|
||||||
|
const [imgTags, setImgTags] = useState([])
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const exifTags = image && image.metadata && image.metadata.exif && image.metadata.exif['cozy-nest-tags']
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
if (!image) return
|
||||||
|
|
||||||
|
if (image.metadata.exif['cozy-nest-tags']) {
|
||||||
|
const _imgTags = image.metadata.exif['cozy-nest-tags'].split(',')
|
||||||
|
setImgTags([..._imgTags])
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setImgTags([])
|
||||||
|
}
|
||||||
|
}, [imageHash,exifTags])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setImage(getImage(imageHash))
|
||||||
|
}, [imageHash])
|
||||||
|
|
||||||
|
const handleCreate = (inputValue) => {
|
||||||
|
setIsLoading(true);
|
||||||
|
setTags([...tags, inputValue])
|
||||||
|
|
||||||
|
//remove duplicates [...imgTags, inputValue]
|
||||||
|
const _imgTags = [...new Set([...imgTags, inputValue])]
|
||||||
|
|
||||||
|
setImgTags([..._imgTags])
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
handleSave([..._imgTags]).then(_ => _)
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (newValue) => {
|
||||||
|
const _newValue = [...new Set(newValue.map(tag => tag.value))]
|
||||||
|
setImgTags(_newValue);
|
||||||
|
handleSave(_newValue).then(_ => _)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSave = async (newTags) => {
|
||||||
|
|
||||||
|
const exif = image.metadata.exif
|
||||||
|
exif['cozy-nest-tags'] = newTags.join(',')
|
||||||
|
CozyLogger.debug('Saving tags', exif['cozy-nest-tags'])
|
||||||
|
await saveExif(image.path, exif)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CreatableSelect
|
||||||
|
placeholder={'Tags...'}
|
||||||
|
styles={styles}
|
||||||
|
isMulti
|
||||||
|
options={tags.map(tag => ({value: tag, label: tag}))}
|
||||||
|
onCreateOption={handleCreate}
|
||||||
|
isDisabled={isLoading}
|
||||||
|
isLoading={isLoading}
|
||||||
|
value={imgTags.map(tag => ({value: tag, label: tag}))}
|
||||||
|
onChange={(tags) => handleChange(tags)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CozyTagsSelect({setActiveTags}) {
|
||||||
|
const {tags} = useContext(ImagesContext)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
options={tags.map(tag => ({value: tag, label: tag}))}
|
||||||
|
components={animatedComponents}
|
||||||
|
isMulti
|
||||||
|
placeholder={'Tags...'}
|
||||||
|
styles={styles}
|
||||||
|
onChange={(tags) => setActiveTags(tags.map(tag => tag.value))}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
import React, {createContext, ReactNode, useEffect, useState} from 'react';
|
||||||
|
import {Image} from "../cozy-types";
|
||||||
|
// @ts-ignore
|
||||||
|
import {CozyLogger} from "../main/CozyLogger";
|
||||||
|
|
||||||
|
interface ImagesContextType {
|
||||||
|
images: Image[];
|
||||||
|
setImages: React.Dispatch<React.SetStateAction<Image[]>>;
|
||||||
|
filteredImages: Image[];
|
||||||
|
setFilteredImages: React.Dispatch<React.SetStateAction<Image[]>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ImagesContext = createContext<ImagesContextType>({
|
||||||
|
images: [],
|
||||||
|
setImages: () => {},
|
||||||
|
filteredImages: [],
|
||||||
|
setFilteredImages: () => {}
|
||||||
|
});
|
||||||
|
|
||||||
|
export function ImagesProvider({ children }: { children: ReactNode[] }) {
|
||||||
|
const [images, setImages] = useState<Image[]>([]);
|
||||||
|
const [filteredImages, setFilteredImages] = useState<Image[]>([]);
|
||||||
|
const [tags, setTags] = useState<string[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
const noDuplicates = [...new Set(tags)];
|
||||||
|
if (noDuplicates.length !== tags.length) {
|
||||||
|
setTags(noDuplicates)
|
||||||
|
}
|
||||||
|
}, [tags])
|
||||||
|
|
||||||
|
const deleteImg = async (what: string, image: Image) => {
|
||||||
|
|
||||||
|
const {path} = image
|
||||||
|
|
||||||
|
function removeFromImages() {
|
||||||
|
//remove from images
|
||||||
|
const newImages = images.filter(image => image.path !== decodeURIComponent(path))
|
||||||
|
setImages([...newImages])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (what === 'delete') {
|
||||||
|
const response = await fetch(`/cozy-nest/image?path=${path}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const json = await response.json()
|
||||||
|
CozyLogger.debug('json', json)
|
||||||
|
if (response.ok) {
|
||||||
|
removeFromImages()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (what === 'archive') {
|
||||||
|
const response = await fetch(`/cozy-nest/image?path=${path}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({archive: true})
|
||||||
|
})
|
||||||
|
const json = await response.json()
|
||||||
|
CozyLogger.debug('json', json)
|
||||||
|
if (response.ok) {
|
||||||
|
removeFromImages()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateExifInState = (image: Image) => {
|
||||||
|
|
||||||
|
const {metadata: {exif, hash}} = image
|
||||||
|
const newImages = images.map(image => {
|
||||||
|
if (image.hash === hash) {
|
||||||
|
image.metadata.exif = exif
|
||||||
|
}
|
||||||
|
return image
|
||||||
|
})
|
||||||
|
setImages([...newImages])
|
||||||
|
}
|
||||||
|
|
||||||
|
const getImage = (hash: string) => {
|
||||||
|
return images.find(image => image.hash === hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = {
|
||||||
|
images,
|
||||||
|
setImages,
|
||||||
|
filteredImages,
|
||||||
|
setFilteredImages,
|
||||||
|
deleteImg,
|
||||||
|
updateExifInState,
|
||||||
|
getImage,
|
||||||
|
tags,
|
||||||
|
setTags
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ImagesContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</ImagesContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import {ReadyState} from "react-use-websocket";
|
||||||
|
import React from 'react'
|
||||||
|
import Browser from "./Browser.jsx";
|
||||||
|
import {Column, Row} from "../main/Utils.jsx";
|
||||||
|
|
||||||
|
export function MockImageBrowser() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Column>
|
||||||
|
<h1 className="cnib-title">Cozy Nest Image Browser <span className="beta-emphasis">beta</span></h1>
|
||||||
|
<Row>
|
||||||
|
<span>The WebSocket is currently <span className="connexionStatus" style={{color:'red'}}>Closed</span></span>
|
||||||
|
<button
|
||||||
|
className="nevysha lg primary gradio-button btn"
|
||||||
|
style={{marginLeft: '20px', width: '410px'}}
|
||||||
|
disabled={true}
|
||||||
|
>
|
||||||
|
Image browser is disabled. To enable it, go to the CozyNest settings.
|
||||||
|
</button>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<textarea data-testid="textbox"
|
||||||
|
placeholder="Search anything : Prompt, Size, Model, ..."
|
||||||
|
rows="1"
|
||||||
|
spellCheck="false"
|
||||||
|
data-gramm="false"
|
||||||
|
disabled={true}/>
|
||||||
|
|
||||||
|
|
||||||
|
</Column>
|
||||||
|
<Browser key={0} imagesRef={[]}/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
.ExifEditor.backdrop {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
z-index: 101;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ExifEditor > .container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 1015px;
|
||||||
|
min-height: 715px;
|
||||||
|
background-color: var(--ae-input-bg-color);
|
||||||
|
border: 1px solid var(--ae-input-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ExifEditor > .container h1 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
padding: 5px 0 5px 0;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--ae-input-bg-color);
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 3px solid var(--nevysha-font-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Ace override*/
|
||||||
|
.ExifEditor .ace_cursor-layer {
|
||||||
|
z-index: 900;
|
||||||
|
}
|
||||||
|
.ExifEditor .ace_cursor-layer .ace_cursor {
|
||||||
|
z-index: 901;
|
||||||
|
background-color: var(--ae-primary-color);
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
@keyframes blink-ace-animate {
|
||||||
|
from, to { opacity: 0.5; }
|
||||||
|
60% { opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink-ace-animate-smooth {
|
||||||
|
from, to { opacity: 0.5; }
|
||||||
|
45% { opacity: 0.5; }
|
||||||
|
60% { opacity: 0; }
|
||||||
|
85% { opacity: 0; }
|
||||||
|
}
|
||||||
|
.ExifEditor .ace_active-line {
|
||||||
|
background: #ffffff0d;
|
||||||
|
}
|
||||||
|
.ExifEditor .ace_editor {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
import React, {useContext, useEffect, useRef, useState} from "react";
|
||||||
|
import {Button} from "../App.jsx";
|
||||||
|
|
||||||
|
import 'ace-builds'
|
||||||
|
ace.config.setModuleUrl("ace/mode/json_worker", 'cozy-nest-client/node_modules/ace-builds/src-noconflict/worker-json.js')
|
||||||
|
|
||||||
|
import AceEditor from "react-ace";
|
||||||
|
import "ace-builds/src-noconflict/mode-json";
|
||||||
|
import "ace-builds/src-noconflict/theme-github_dark";
|
||||||
|
import "ace-builds/src-noconflict/ext-language_tools";
|
||||||
|
import {CozyLogger} from "../../main/CozyLogger.js";
|
||||||
|
import {ImagesContext} from "../ImagesContext.tsx";
|
||||||
|
|
||||||
|
export function ExifEditor({onClose, visible, imageHash}) {
|
||||||
|
|
||||||
|
const [exif, setExif] = useState({});
|
||||||
|
const [exifString, setExifString] = useState('');
|
||||||
|
const [isJsonValid, setIsJsonValid] = useState(false);
|
||||||
|
|
||||||
|
const {images, getImage} = useContext(ImagesContext)
|
||||||
|
|
||||||
|
const [image, setImage] = useState(
|
||||||
|
getImage(imageHash)
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setImage(getImage(imageHash));
|
||||||
|
}, [images, imageHash]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setExif(image.metadata.exif)
|
||||||
|
setExifString(JSON.stringify(image.metadata.exif, null, 2))
|
||||||
|
setIsJsonValid(true)
|
||||||
|
}, [visible, image])
|
||||||
|
|
||||||
|
const handleChange = (text) => {
|
||||||
|
setExifString(text)
|
||||||
|
try {
|
||||||
|
const _exif = JSON.parse(text)
|
||||||
|
setExif(_exif)
|
||||||
|
setIsJsonValid(true)
|
||||||
|
} catch (e) {
|
||||||
|
setIsJsonValid(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
if (!isJsonValid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const path = image.path
|
||||||
|
await saveExif(path, exif)
|
||||||
|
}
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{visible &&
|
||||||
|
<div className="ExifEditor backdrop">
|
||||||
|
<div className="container">
|
||||||
|
<h1>Exif Editor</h1>
|
||||||
|
<AceEditor
|
||||||
|
mode="json"
|
||||||
|
theme="github_dark"
|
||||||
|
showPrintMargin={false}
|
||||||
|
onChange={handleChange}
|
||||||
|
value={exifString}
|
||||||
|
name="ace-json-editor"
|
||||||
|
editorProps={{ $blockScrolling: true }}
|
||||||
|
style={{width: "100%", height: "100%", zIndex: 200}}
|
||||||
|
setOptions={{
|
||||||
|
enableSnippets: true,
|
||||||
|
cursorStyle: "smooth",
|
||||||
|
behavioursEnabled: true,
|
||||||
|
wrapBehavioursEnabled: true,
|
||||||
|
autoScrollEditorIntoView: true,
|
||||||
|
wrap: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button disabled={!isJsonValid} onClick={save}>{isJsonValid ? "Save" : "Invalid JSON"}</Button>
|
||||||
|
<Button onClick={() => close()}>Close</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
{!visible &&
|
||||||
|
<div/>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveExif(path, exif) {
|
||||||
|
|
||||||
|
// check if path is URL encoded
|
||||||
|
if (path.indexOf('%') !== -1) {
|
||||||
|
path = decodeURIComponent(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
//check if exif['cozy-nest-tags'] contains only ',' or only any number of ','
|
||||||
|
if (exif['cozy-nest-tags'] && exif['cozy-nest-tags'].match(/^,+$/g)) {
|
||||||
|
exif['cozy-nest-tags'] = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const response = await fetch(`/cozy-nest/image-exif`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
//decode path from url
|
||||||
|
path: path,
|
||||||
|
data: exif
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const json = await response.json()
|
||||||
|
CozyLogger.debug('json', json)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
save: saveExif,
|
||||||
|
ExifEditor: ExifEditor
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
:root {
|
||||||
|
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
color-scheme: light dark;
|
||||||
|
color: rgba(255, 255, 255, 0.87);
|
||||||
|
background-color: #242424;
|
||||||
|
|
||||||
|
font-synthesis: none;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #646cff;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #535bf2;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
min-width: 320px;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 3.2em;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
padding: 0.6em 1.2em;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: inherit;
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.25s;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
border-color: #646cff;
|
||||||
|
}
|
||||||
|
button:focus,
|
||||||
|
button:focus-visible {
|
||||||
|
outline: 4px auto -webkit-focus-ring-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
:root {
|
||||||
|
color: #213547;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #747bff;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,11 +4,9 @@
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite + React</title>
|
<title>Vite + React</title>
|
||||||
<script type="module" crossorigin src="/file=extensions/Cozy-Nest/cozy-nest-image-browser/assets/index.js"></script>
|
|
||||||
<link rel="stylesheet" href="/file=extensions/Cozy-Nest/cozy-nest-image-browser/assets/index.css">
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="cozy-img-browser-react"></div>
|
<div id="cozy-img-browser-react"></div>
|
||||||
|
<script type="module" src="/src/main.jsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom/client'
|
||||||
|
import App from './App.jsx'
|
||||||
|
import './index.css'
|
||||||
|
import {ImagesProvider} from "./ImagesContext.tsx";
|
||||||
|
import {ChakraProvider} from '@chakra-ui/react'
|
||||||
|
import {theme} from "../chakra/chakra-theme.ts";
|
||||||
|
|
||||||
|
export function startCozyNestImageBrowser() {
|
||||||
|
|
||||||
|
if (!document.getElementById('cozy-img-browser-react')) {
|
||||||
|
setTimeout(() => startCozyNestImageBrowser(), 200)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('cozy-img-browser-react')).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<ChakraProvider theme={theme} >
|
||||||
|
<ImagesProvider>
|
||||||
|
<App />
|
||||||
|
</ImagesProvider>
|
||||||
|
</ChakraProvider >
|
||||||
|
</React.StrictMode>,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -7,6 +7,6 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/main.js"></script>
|
<script type="module" src="/main.jsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,46 @@
|
||||||
import 'animate.css';
|
import 'animate.css';
|
||||||
import '@fontsource-variable/caveat';
|
import '@fontsource-variable/caveat';
|
||||||
|
|
||||||
import sheet from './main/cozy-nest-style.css?inline' assert { type: 'css' };
|
import sheet from './main/cozy-nest-style.css?inline' assert { type: 'css' };
|
||||||
|
import cozyNestModuleLoader, {fetchCozyNestConfig} from './main/nevysha-cozy-nest.js'
|
||||||
|
import SimpleTimer from "./main/SimpleTimer.js";
|
||||||
|
import {COZY_NEST_GRADIO_LOAD_DURATION} from "./main/Constants.js";
|
||||||
|
import {CozyLogger} from "./main/CozyLogger.js";
|
||||||
|
import {startCozyNestImageBrowser} from "@image-browser/main.jsx";
|
||||||
|
import startCozyNestSettings from "@settings/main.jsx";
|
||||||
import {
|
import {
|
||||||
dummyLoraCard, dummyControlNetBloc, dummySubdirs
|
dummyLoraCard, dummyControlNetBloc, dummySubdirs
|
||||||
} from './main/cozy-utils.js';
|
} from './main/cozy-utils.js';
|
||||||
|
import startCozyPrompt from "./cozy-prompt/main.jsx";
|
||||||
|
import {startExtraNetwork} from "./extra-network/main.jsx";
|
||||||
|
import Loading from "./main/Loading.js";
|
||||||
window.CozyTools = {
|
window.CozyTools = {
|
||||||
dummyLoraCard,
|
dummyLoraCard,
|
||||||
dummyControlNetBloc,
|
dummyControlNetBloc,
|
||||||
dummySubdirs
|
dummySubdirs
|
||||||
}
|
}
|
||||||
|
|
||||||
import cozyNestLoader from './main/nevysha-cozy-nest.js'
|
|
||||||
import SimpleTimer from "./main/SimpleTimer.js";
|
|
||||||
import {COZY_NEST_GRADIO_LOAD_DURATION} from "./main/Constants.js";
|
export default async function cozyNestLoader() {
|
||||||
import {CozyLogger} from "./main/CozyLogger.js";
|
await fetchCozyNestConfig();
|
||||||
|
await cozyNestModuleLoader(async () => {
|
||||||
|
startCozyNestSettings();
|
||||||
|
|
||||||
|
|
||||||
|
if (COZY_NEST_CONFIG.enable_cozy_prompt === true) {
|
||||||
|
startCozyPrompt('txt2img_prompt', 'cozy_nest_prompt_txt2img');
|
||||||
|
startCozyPrompt('img2img_prompt', 'cozy_nest_prompt_img2img');
|
||||||
|
}
|
||||||
|
if (COZY_NEST_CONFIG.enable_extra_network_tweaks === true) {
|
||||||
|
await startExtraNetwork('txt2img');
|
||||||
|
await startExtraNetwork('img2img');
|
||||||
|
}
|
||||||
|
|
||||||
|
startCozyNestImageBrowser();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.cozyNestLoader = cozyNestLoader;
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
//check if the param CozyNest=No is present in the url
|
//check if the param CozyNest=No is present in the url
|
||||||
|
|
@ -37,15 +61,6 @@ import {CozyLogger} from "./main/CozyLogger.js";
|
||||||
|
|
||||||
SimpleTimer.time(COZY_NEST_GRADIO_LOAD_DURATION);
|
SimpleTimer.time(COZY_NEST_GRADIO_LOAD_DURATION);
|
||||||
|
|
||||||
// Cozy-Nest-Image-Browser link
|
|
||||||
const cozyNestImageBrowserLink = document.createElement('link');
|
|
||||||
cozyNestImageBrowserLink.rel = 'stylesheet';
|
|
||||||
cozyNestImageBrowserLink.type = 'text/css';
|
|
||||||
cozyNestImageBrowserLink.href = `file=extensions/Cozy-Nest/cozy-nest-image-browser/assets/index.css?t=${Date.now()}`;
|
|
||||||
|
|
||||||
// Append the link element to the document head
|
|
||||||
document.head.appendChild(cozyNestImageBrowserLink);
|
|
||||||
|
|
||||||
if (import.meta.env.VITE_CONTEXT === 'DEV') {
|
if (import.meta.env.VITE_CONTEXT === 'DEV') {
|
||||||
CozyLogger.debug('DEV MODE');
|
CozyLogger.debug('DEV MODE');
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import cozyNestLoader from "./nevysha-cozy-nest.js";
|
|
||||||
|
|
||||||
export class CozyLogger {
|
export class CozyLogger {
|
||||||
|
|
||||||
static _instance = null;
|
static _instance = null;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import {getTheme} from "./cozy-utils.js";
|
import {getTheme} from "./cozy-utils.js";
|
||||||
import SimpleTimer from "./SimpleTimer.js";
|
import SimpleTimer from "./SimpleTimer.js";
|
||||||
import {COZY_NEST_GRADIO_LOAD_DURATION} from "./Constants.js";
|
import {COZY_NEST_GRADIO_LOAD_DURATION} from "./Constants.js";
|
||||||
import {waves, loading_roll} from "./svg.js";
|
import {waves, loading_ellipsis} from "./svg.js";
|
||||||
import {applyAccentColor, applyBgGradiantColor, applyWavesColor, applyFontColor} from "./tweaks/various-tweaks.js";
|
import {applyAccentColor, applyBgGradiantColor, applyWavesColor, applyFontColor} from "./tweaks/various-tweaks.js";
|
||||||
|
|
||||||
export default class Loading {
|
export default class Loading {
|
||||||
|
|
@ -19,8 +19,8 @@ export default class Loading {
|
||||||
if (Loading._instance) {
|
if (Loading._instance) {
|
||||||
Loading._instance.observer.disconnect();
|
Loading._instance.observer.disconnect();
|
||||||
}
|
}
|
||||||
//wait for one second to let gradio finish request...
|
|
||||||
setTimeout(() => document.querySelector("#nevysha-loading-wrap").remove(), 2000);
|
document.querySelector("#nevysha-loading-wrap").remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
@ -76,7 +76,7 @@ export default class Loading {
|
||||||
<div class="nevysha-cozy-nest-app-name animate__animated animate__backInLeft">
|
<div class="nevysha-cozy-nest-app-name animate__animated animate__backInLeft">
|
||||||
Cozy Nest
|
Cozy Nest
|
||||||
</div>
|
</div>
|
||||||
${loading_roll}
|
${loading_ellipsis}
|
||||||
<div id="loading_step_estimator" class="subtext3 animate__animated animate__pulse animate__infinite">
|
<div id="loading_step_estimator" class="subtext3 animate__animated animate__pulse animate__infinite">
|
||||||
1
|
1
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import {CozyLogger} from './CozyLogger';
|
||||||
|
|
||||||
export default class SimpleTimer {
|
export default class SimpleTimer {
|
||||||
|
|
||||||
static timers = {};
|
static timers = {};
|
||||||
|
|
@ -9,7 +11,9 @@ export default class SimpleTimer {
|
||||||
}
|
}
|
||||||
|
|
||||||
static end(timerName) {
|
static end(timerName) {
|
||||||
return SimpleTimer.timers[timerName].end();
|
const elapsedTime = SimpleTimer.timers[timerName].end();
|
||||||
|
CozyLogger.debug(`SimpleTimer: end ${timerName} in ${elapsedTime}ms`)
|
||||||
|
return elapsedTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
static last(timerName) {
|
static last(timerName) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
.flex-column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.flex-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
import React from 'react'
|
||||||
|
import './Utils.css'
|
||||||
|
|
||||||
|
//component to wrap flex row
|
||||||
|
export function Row(props) {
|
||||||
|
|
||||||
|
// if props.className is set, append flex-row to it
|
||||||
|
// otherwise, set className to flex-row
|
||||||
|
const className = props.className ? props.className + ' flex-row' : 'flex-row'
|
||||||
|
|
||||||
|
return <div
|
||||||
|
{...props}
|
||||||
|
className={className}>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RowFullWidth = (props) => {
|
||||||
|
return <Row {...props} style={{width: '100%', justifyContent: 'space-between', gap: '30px'}}/>
|
||||||
|
}
|
||||||
|
|
||||||
|
//component to wrap flex column
|
||||||
|
export function Column(props) {
|
||||||
|
|
||||||
|
const className = props.className ? props.className + ' flex-column' : 'flex-column'
|
||||||
|
|
||||||
|
return <div
|
||||||
|
{...props}
|
||||||
|
className={className}>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
console.log('injecting react-refresh')
|
||||||
|
import RefreshRuntime from "/@react-refresh"
|
||||||
|
RefreshRuntime.injectIntoGlobalHook(window)
|
||||||
|
window.$RefreshReg$ = () => {}
|
||||||
|
window.$RefreshSig$ = () => (type) => type
|
||||||
|
window.__vite_plugin_react_preamble_installed__ = true
|
||||||
|
|
@ -529,7 +529,6 @@ input[type=range]::-webkit-slider-thumb {
|
||||||
|
|
||||||
border-radius: var(--button-large-radius);
|
border-radius: var(--button-large-radius);
|
||||||
padding: var(--button-large-padding);
|
padding: var(--button-large-padding);
|
||||||
font-weight: var(--button-large-text-weight);
|
|
||||||
font-size: var(--button-large-text-size);
|
font-size: var(--button-large-text-size);
|
||||||
|
|
||||||
--checkbox-background-color: var(--neutral-800);
|
--checkbox-background-color: var(--neutral-800);
|
||||||
|
|
@ -930,6 +929,9 @@ button.secondary, button.primary {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
}
|
}
|
||||||
|
#cozy-img-browser_panel {
|
||||||
|
border: 1px solid var(--ae-input-border-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.vertical-line {
|
.vertical-line {
|
||||||
width: 2px !important;
|
width: 2px !important;
|
||||||
|
|
@ -987,6 +989,7 @@ canvas.nevysha {
|
||||||
height: calc(100% - (100px + var(--menu-top-height)));
|
height: calc(100% - (100px + var(--menu-top-height)));
|
||||||
top: calc(75px + var(--menu-top-height));
|
top: calc(75px + var(--menu-top-height));
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
|
border: 1px solid var(--ae-input-border-color) !important;
|
||||||
}
|
}
|
||||||
#txt2img_extra_networks_nevysha_wrapper,
|
#txt2img_extra_networks_nevysha_wrapper,
|
||||||
#img2img_extra_networks_nevysha_wrapper {
|
#img2img_extra_networks_nevysha_wrapper {
|
||||||
|
|
@ -998,7 +1001,7 @@ canvas.nevysha {
|
||||||
#txt2img_extra_networks_nevysha_wrapper > .vertical-line-wrapper,
|
#txt2img_extra_networks_nevysha_wrapper > .vertical-line-wrapper,
|
||||||
#img2img_extra_networks_nevysha_wrapper > .vertical-line-wrapper,
|
#img2img_extra_networks_nevysha_wrapper > .vertical-line-wrapper,
|
||||||
.slide-right-browser-panel > .vertical-line-wrapper {
|
.slide-right-browser-panel > .vertical-line-wrapper {
|
||||||
z-index: 9999;
|
z-index: 100;
|
||||||
margin: 15px 0 0 5px;
|
margin: 15px 0 0 5px;
|
||||||
}
|
}
|
||||||
/*div[id$="_extra_tabs"] {*/
|
/*div[id$="_extra_tabs"] {*/
|
||||||
|
|
@ -1141,6 +1144,8 @@ input[type="number"] {
|
||||||
margin: 15px 0 15px 0 !important;
|
margin: 15px 0 15px 0 !important;
|
||||||
border-left: 4px solid var(--ae-primary-color);
|
border-left: 4px solid var(--ae-primary-color);
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
.nevysha.settings-nevyui-top > .nevysha-reporting > a {
|
.nevysha.settings-nevyui-top > .nevysha-reporting > a {
|
||||||
color: var(--ae-primary-color);
|
color: var(--ae-primary-color);
|
||||||
|
|
@ -1558,6 +1563,62 @@ textarea.nevysha-image-browser-folder {
|
||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.lds-ellipsis {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
.lds-ellipsis div {
|
||||||
|
position: absolute;
|
||||||
|
top: 33px;
|
||||||
|
width: 13px;
|
||||||
|
height: 13px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--ae-primary-color);
|
||||||
|
animation-timing-function: cubic-bezier(0, 1, 1, 0);
|
||||||
|
}
|
||||||
|
.lds-ellipsis div:nth-child(1) {
|
||||||
|
left: 8px;
|
||||||
|
animation: lds-ellipsis1 0.6s infinite;
|
||||||
|
}
|
||||||
|
.lds-ellipsis div:nth-child(2) {
|
||||||
|
left: 8px;
|
||||||
|
animation: lds-ellipsis2 0.6s infinite;
|
||||||
|
}
|
||||||
|
.lds-ellipsis div:nth-child(3) {
|
||||||
|
left: 32px;
|
||||||
|
animation: lds-ellipsis2 0.6s infinite;
|
||||||
|
}
|
||||||
|
.lds-ellipsis div:nth-child(4) {
|
||||||
|
left: 56px;
|
||||||
|
animation: lds-ellipsis3 0.6s infinite;
|
||||||
|
}
|
||||||
|
@keyframes lds-ellipsis1 {
|
||||||
|
0% {
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes lds-ellipsis3 {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes lds-ellipsis2 {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate(24px, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#kofi_nevysha_support > img {
|
#kofi_nevysha_support > img {
|
||||||
height: 15px !important;
|
height: 15px !important;
|
||||||
|
|
@ -1799,10 +1860,18 @@ body.nevysha-light .nevysha-button:hover {
|
||||||
fill: var(--body-text-color);
|
fill: var(--body-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#nevyui_update_info_close_btn, #nevyui_update_btn {
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
#cozynest_others_settings_header > p:nth-child(1) {
|
#cozynest_others_settings_header > p:nth-child(1) {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#nevysha_cozy_nest {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
body.nsfw img:not([id='kofi_nevysha_support_img']) {
|
body.nsfw img:not([id='kofi_nevysha_support_img']) {
|
||||||
filter: blur(20px);
|
filter: blur(20px);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import Loading from "./Loading.js";
|
||||||
import {waves, svg_magic_wand, svg_update_info} from "./svg.js";
|
import {waves, svg_magic_wand, svg_update_info} from "./svg.js";
|
||||||
import {
|
import {
|
||||||
applyAccentColor, applyBgGradiantColor, applyWavesColor
|
applyAccentColor, applyBgGradiantColor, applyWavesColor
|
||||||
, wrapDataGenerationInfo, wrapSettings, createVerticalLineComp, applyFontColor
|
, wrapDataGenerationInfo, wrapSettings, createVerticalLineComp, applyFontColor, recalcOffsetFromMenuHeight
|
||||||
} from "./tweaks/various-tweaks.js";
|
} from "./tweaks/various-tweaks.js";
|
||||||
import kofiCup from './kofi-cup-border.png'
|
import kofiCup from './kofi-cup-border.png'
|
||||||
import {
|
import {
|
||||||
|
|
@ -213,197 +213,6 @@ function addScrollable(bundle) {
|
||||||
document.getElementById(`${bundle.prefix}_gallery_container`).classList.add("nevysha","nevysha-scrollable")
|
document.getElementById(`${bundle.prefix}_gallery_container`).classList.add("nevysha","nevysha-scrollable")
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHexColorForAccent() {
|
|
||||||
return document.querySelector("#setting_nevyui_accentColor").querySelector("input").value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function applyCozyNestConfig() {
|
|
||||||
|
|
||||||
//waves
|
|
||||||
const setWaveColor = () => {
|
|
||||||
const hexColor = document.querySelector("#setting_nevyui_waveColor").querySelector("input").value;
|
|
||||||
applyWavesColor(hexColor);
|
|
||||||
}
|
|
||||||
setWaveColor()
|
|
||||||
document.querySelector("#setting_nevyui_waveColor").querySelector("input").addEventListener("change", setWaveColor)
|
|
||||||
|
|
||||||
//font color
|
|
||||||
const fontColorInput =
|
|
||||||
getTheme() === "dark" ?
|
|
||||||
document.querySelector("#setting_nevyui_fontColor").querySelector("input") :
|
|
||||||
document.querySelector("#setting_nevyui_fontColorLight").querySelector("input")
|
|
||||||
//remove hidden css class of parent.parent
|
|
||||||
fontColorInput.parentElement.parentElement.style.display = "block";
|
|
||||||
const setFontColor = () => {
|
|
||||||
const hexColor = fontColorInput.value;
|
|
||||||
if (!hexColor) return;
|
|
||||||
applyFontColor(hexColor);
|
|
||||||
}
|
|
||||||
setFontColor()
|
|
||||||
fontColorInput.addEventListener("change", setFontColor)
|
|
||||||
|
|
||||||
//background gradient
|
|
||||||
const setGradientColor = () => {
|
|
||||||
const hexColor = document.querySelector("#setting_nevyui_bgGradiantColor").querySelector("input").value;
|
|
||||||
applyBgGradiantColor(hexColor);
|
|
||||||
}
|
|
||||||
setGradientColor()
|
|
||||||
document.querySelector("#setting_nevyui_bgGradiantColor").querySelector("input").addEventListener("change", setGradientColor)
|
|
||||||
|
|
||||||
//disable waves and gradiant
|
|
||||||
const setDisabledWavesAndGradiant = () => {
|
|
||||||
const disableWavesAndGradiant = document.querySelector("#setting_nevyui_disableWavesAndGradiant").querySelector("input").checked;
|
|
||||||
const $waves = $('.wave');
|
|
||||||
const $body = $('body');
|
|
||||||
if (disableWavesAndGradiant) {
|
|
||||||
$waves.css('animation', 'none');
|
|
||||||
$body.css('animation', 'none');
|
|
||||||
$body.css('background-position', '75% 75%')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$waves.css('animation', '');
|
|
||||||
$body.css('animation', '');
|
|
||||||
$body.css('background-position', '')
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
setDisabledWavesAndGradiant()
|
|
||||||
document.querySelector("#setting_nevyui_disableWavesAndGradiant").querySelector("input").addEventListener("change", setDisabledWavesAndGradiant)
|
|
||||||
|
|
||||||
//background gradient
|
|
||||||
const setAccentColor = () => {
|
|
||||||
const hexColor = getHexColorForAccent();
|
|
||||||
applyAccentColor(hexColor, getHexColorForAccent());
|
|
||||||
}
|
|
||||||
//accent generate button
|
|
||||||
const setAccentForGenerate = () => {
|
|
||||||
const checked = document.querySelector("#setting_nevyui_accentGenerateButton").querySelector("input").checked;
|
|
||||||
document.querySelectorAll('button[id$="_generate"]').forEach((btn) => {
|
|
||||||
if (checked) {
|
|
||||||
let txtColorAppending = "";
|
|
||||||
if (getLuminance(getHexColorForAccent()) > 0.5) {
|
|
||||||
txtColorAppending = "color: black !important";
|
|
||||||
}
|
|
||||||
btn.setAttribute("style", `background: var(--ae-primary-color) !important; ${txtColorAppending}`);
|
|
||||||
} else {
|
|
||||||
btn.setAttribute("style", '');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
setAccentColor()
|
|
||||||
document.querySelector("#setting_nevyui_accentColor").querySelector("input").addEventListener("change", setAccentColor)
|
|
||||||
document.querySelector("#setting_nevyui_accentColor").querySelector("input").addEventListener("change", setAccentForGenerate)
|
|
||||||
|
|
||||||
|
|
||||||
setAccentForGenerate()
|
|
||||||
document.querySelector("#setting_nevyui_accentGenerateButton").querySelector("input").addEventListener("change", setAccentForGenerate);
|
|
||||||
|
|
||||||
//font size
|
|
||||||
const setFontSize = () => {
|
|
||||||
const fontSize = document.querySelector("#setting_nevyui_fontSize").querySelector("input[type=number]").value;
|
|
||||||
document.querySelector(':root').style.setProperty('--nevysha-text-md', `${fontSize}px`);
|
|
||||||
recalcOffsetFromMenuHeight()
|
|
||||||
}
|
|
||||||
setFontSize()
|
|
||||||
document.querySelector("#setting_nevyui_fontSize").querySelector("input[type=number]").addEventListener("change", setFontSize)
|
|
||||||
document.querySelector("#setting_nevyui_fontSize").querySelector("input[type=range]").addEventListener("change", setFontSize)
|
|
||||||
|
|
||||||
//card height
|
|
||||||
const setCardHeight = () => {
|
|
||||||
const cardHeight = document.querySelector("#setting_nevyui_cardHeight").querySelector("input[type=number]").value;
|
|
||||||
document.querySelector(':root').style.setProperty('--extra-network-card-height', `${cardHeight}em`);
|
|
||||||
}
|
|
||||||
setCardHeight()
|
|
||||||
document.querySelector("#setting_nevyui_cardHeight").querySelector("input[type=number]").addEventListener("change", setCardHeight)
|
|
||||||
document.querySelector("#setting_nevyui_cardHeight").querySelector("input[type=range]").addEventListener("change", setCardHeight)
|
|
||||||
|
|
||||||
//card width
|
|
||||||
const setCardWidth = () => {
|
|
||||||
const cardWidth = document.querySelector("#setting_nevyui_cardWidth").querySelector("input[type=number]").value;
|
|
||||||
document.querySelector(':root').style.setProperty('--extra-network-card-width', `${cardWidth}em`);
|
|
||||||
}
|
|
||||||
setCardWidth()
|
|
||||||
document.querySelector("#setting_nevyui_cardWidth").querySelector("input[type=number]").addEventListener("change", setCardWidth)
|
|
||||||
document.querySelector("#setting_nevyui_cardWidth").querySelector("input[type=range]").addEventListener("change", setCardWidth)
|
|
||||||
|
|
||||||
//check if menu is in left or top mode
|
|
||||||
const menuPosition = () => {
|
|
||||||
const isLeftChecked = document.querySelector("#setting_nevyui_menuPosition").querySelector("input[value=left]").checked;
|
|
||||||
|
|
||||||
//top mode
|
|
||||||
if (!isLeftChecked) {
|
|
||||||
document.querySelector(".nevysha.nevysha-tabnav").classList.add("menu-fix-top")
|
|
||||||
document.querySelector(".gradio-container.app").classList.add("menu-fix-top")
|
|
||||||
document.querySelector("#nevysha-btn-menu-wrapper")?.classList.add("menu-fix-top")
|
|
||||||
document.querySelector(':root').style.setProperty('--nevysha-margin-left', `0`);
|
|
||||||
document.querySelector(':root').style.setProperty('--menu-top-height', `25px`);
|
|
||||||
|
|
||||||
//centered or not
|
|
||||||
const isCenteredChecked = document.querySelector("#setting_nevyui_menuPosition").querySelector("input[value=top_centered]").checked;
|
|
||||||
if (isCenteredChecked) {
|
|
||||||
COZY_NEST_CONFIG.main_menu_position = "top_centered";
|
|
||||||
document.querySelector(".nevysha.nevysha-tabnav").classList.add("center-menu-items")
|
|
||||||
} else {
|
|
||||||
COZY_NEST_CONFIG.main_menu_position = "top";
|
|
||||||
document.querySelector(".nevysha.nevysha-tabnav").classList.remove("center-menu-items")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//left mode
|
|
||||||
else {
|
|
||||||
COZY_NEST_CONFIG.main_menu_position = "left";
|
|
||||||
document.querySelector(".nevysha.nevysha-tabnav").classList.remove("center-menu-items")
|
|
||||||
document.querySelector(".nevysha.nevysha-tabnav").classList.remove("menu-fix-top")
|
|
||||||
document.querySelector(".gradio-container.app").classList.remove("menu-fix-top")
|
|
||||||
document.querySelector("#nevysha-btn-menu-wrapper")?.classList.remove("menu-fix-top")
|
|
||||||
document.querySelector(':root').style.setProperty('--nevysha-margin-left', `175px`);
|
|
||||||
document.querySelector(':root').style.setProperty('--menu-top-height', `1px`);
|
|
||||||
}
|
|
||||||
recalcOffsetFromMenuHeight()
|
|
||||||
}
|
|
||||||
menuPosition()
|
|
||||||
document.querySelector("#setting_nevyui_menuPosition").querySelector("input[value=left]").addEventListener("change", menuPosition)
|
|
||||||
document.querySelector("#setting_nevyui_menuPosition").querySelector("input[value=top]").addEventListener("change", menuPosition)
|
|
||||||
document.querySelector("#setting_nevyui_menuPosition").querySelector("input[value=top_centered]").addEventListener("change", menuPosition)
|
|
||||||
|
|
||||||
//quicksetting gap
|
|
||||||
const setQuicksettingPosition = () => {
|
|
||||||
const position = document.querySelector("#setting_nevyui_quicksettingsPosition")
|
|
||||||
.querySelector("input[type=radio]:checked").value;
|
|
||||||
if (position === 'split') {
|
|
||||||
document.querySelector("#quicksettings_gap").classList.add("nevysha-quicksettings-gap")
|
|
||||||
document.querySelector("#quicksettings").classList.remove("centered-quicksettings")
|
|
||||||
}
|
|
||||||
else if (position === 'centered') {
|
|
||||||
document.querySelector("#quicksettings_gap").classList.remove("nevysha-quicksettings-gap")
|
|
||||||
document.querySelector("#quicksettings").classList.add("centered-quicksettings")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
document.querySelector("#quicksettings_gap").classList.remove("nevysha-quicksettings-gap")
|
|
||||||
document.querySelector("#quicksettings").classList.remove("centered-quicksettings")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setQuicksettingPosition()
|
|
||||||
document.querySelector("#setting_nevyui_quicksettingsPosition")
|
|
||||||
.querySelectorAll("input[type=radio]").forEach((input) => input.addEventListener("change", setQuicksettingPosition))
|
|
||||||
|
|
||||||
//enable/disable the sfw mode
|
|
||||||
const setSfwSettings = () => {
|
|
||||||
const isSfwChecked = document.querySelector("#setting_nevyui_sfwMode").querySelector("input[type=checkbox]").checked;
|
|
||||||
if (isSfwChecked) {
|
|
||||||
document.querySelector('body').classList.add("nsfw");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
document.querySelector('body').classList.remove("nsfw");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setSfwSettings()
|
|
||||||
document.querySelector("#setting_nevyui_sfwMode").querySelector("input[type=checkbox]").addEventListener("change", setSfwSettings)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function tweakAWQ() {
|
function tweakAWQ() {
|
||||||
|
|
||||||
const observer = new MutationObserver((mutationsList, observer) => {
|
const observer = new MutationObserver((mutationsList, observer) => {
|
||||||
|
|
@ -458,8 +267,6 @@ function tweakAWQ() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const addCozyNestCustomBtn = () => {
|
const addCozyNestCustomBtn = () => {
|
||||||
|
|
@ -468,9 +275,6 @@ const addCozyNestCustomBtn = () => {
|
||||||
nevySettingstabMenuWrapper.classList.add("nevysha-btn-menu-wrapper");
|
nevySettingstabMenuWrapper.classList.add("nevysha-btn-menu-wrapper");
|
||||||
nevySettingstabMenuWrapper.id = "nevysha-btn-menu-wrapper";
|
nevySettingstabMenuWrapper.id = "nevysha-btn-menu-wrapper";
|
||||||
|
|
||||||
//add a new button in the tabnav
|
|
||||||
const nevySettingstabMenu2 = `<button class="nevysha-btn-menu" id="nevyui_sh_options" title="Nevysha Cozy Nest Settings">${svg_magic_wand}</button>`;
|
|
||||||
nevySettingstabMenuWrapper.insertAdjacentHTML('beforeend', nevySettingstabMenu2);
|
|
||||||
//add a new button in the tabnav
|
//add a new button in the tabnav
|
||||||
const updateInfoBtn = `<button class="nevysha-btn-menu" id="nevyui_update_info" title="Nevysha Cozy Nest Update Info">${svg_update_info}</button>`;
|
const updateInfoBtn = `<button class="nevysha-btn-menu" id="nevyui_update_info" title="Nevysha Cozy Nest Update Info">${svg_update_info}</button>`;
|
||||||
nevySettingstabMenuWrapper.insertAdjacentHTML('beforeend', updateInfoBtn);
|
nevySettingstabMenuWrapper.insertAdjacentHTML('beforeend', updateInfoBtn);
|
||||||
|
|
@ -485,32 +289,27 @@ const addCozyNestCustomBtn = () => {
|
||||||
updateTab.style = "display: none;";
|
updateTab.style = "display: none;";
|
||||||
document.querySelector("#tabs").insertAdjacentElement("beforeend", updateTab)
|
document.querySelector("#tabs").insertAdjacentElement("beforeend", updateTab)
|
||||||
|
|
||||||
//add kofi image :blush:
|
// add click event to the new update info button
|
||||||
const kofiImg = document.createElement('button')
|
function listenerClosure() {
|
||||||
kofiImg.id = 'kofi_nevysha_support'
|
let shown = false;
|
||||||
kofiImg.innerHTML = `<img id="kofi_nevysha_support_img" height="15" src="${kofiCup}" alt="Consider a donation on ko-fi! :3">`
|
document.querySelector("#nevyui_update_info").addEventListener("click", (e) => {
|
||||||
kofiImg.title = "Consider a donation on ko-fi! :3"
|
//cancel event
|
||||||
nevySettingstabMenuWrapper.insertAdjacentElement('beforeend', kofiImg);
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
//create a div that will contain a dialog to display the iframe
|
//show tab_nevyui by default to bypass gradio hidding tabs
|
||||||
const kofiTab = document.createElement("div");
|
document.querySelector("#tab_nevyui").style.display = "block";
|
||||||
kofiTab.classList.add("nevysha-kofi-tab", "nevysha", "nevysha-tab", "nevysha-tab-settings");
|
|
||||||
kofiTab.id = "nevyui_kofi_panel";
|
|
||||||
kofiTab.style = "display: none;";
|
|
||||||
// kofiTab.innerHTML = `<iframe id='kofiframe' src='https://ko-fi.com/nevysha/?hidefeed=true&widget=true&embed=true&preview=true' style='border:none;width:100%;padding:4px;background:#f9f9f9;' height='712' title='nevysha'></iframe>`
|
|
||||||
document.querySelector("#tabs").insertAdjacentElement("beforeend", kofiTab)
|
|
||||||
|
|
||||||
let kofiImgIsVisible = false
|
//toggle the panel with a slide animation using jquery
|
||||||
|
if (shown) {
|
||||||
function toggleKofiPanel() {
|
$("#nevyui_update_info_panel").slideUp(ANIMATION_SPEED);
|
||||||
|
} else {
|
||||||
window.open("https://ko-fi.com/nevysha", "_blank")
|
$("#nevyui_update_info_panel").slideDown(ANIMATION_SPEED);
|
||||||
|
}
|
||||||
|
shown = !shown;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
listenerClosure();
|
||||||
//add event listener to the button
|
|
||||||
kofiImg.addEventListener("click", () => {
|
|
||||||
toggleKofiPanel();
|
|
||||||
});
|
|
||||||
|
|
||||||
//fetch version_data.json
|
//fetch version_data.json
|
||||||
loadVersionData().then(ignored => ignored)
|
loadVersionData().then(ignored => ignored)
|
||||||
|
|
@ -614,6 +413,13 @@ function createFolderListComponent() {
|
||||||
const componentContainer = document.querySelector('#cnib_output_folder').parentElement;
|
const componentContainer = document.querySelector('#cnib_output_folder').parentElement;
|
||||||
const textarea = document.querySelector('#cnib_output_folder textarea');
|
const textarea = document.querySelector('#cnib_output_folder textarea');
|
||||||
componentContainer.classList.remove('hidden')
|
componentContainer.classList.remove('hidden')
|
||||||
|
$(componentContainer).css('padding', '0 10px')
|
||||||
|
|
||||||
|
//add a label
|
||||||
|
const label = document.createElement('label');
|
||||||
|
label.classList.add('nevysha-label');
|
||||||
|
label.innerHTML = 'Folders to scrap for images';
|
||||||
|
componentContainer.appendChild(label);
|
||||||
|
|
||||||
function updateList(foldersList) {
|
function updateList(foldersList) {
|
||||||
document.querySelectorAll('.nevysha-image-browser-folder-container').forEach(el => el.remove());
|
document.querySelectorAll('.nevysha-image-browser-folder-container').forEach(el => el.remove());
|
||||||
|
|
@ -707,199 +513,6 @@ function createFolderListComponent() {
|
||||||
parseAndDisplayFolderSettings();
|
parseAndDisplayFolderSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
const tweakNevyUiSettings = () => {
|
|
||||||
// select button element with "Nevysha Cozy Nest" as its content
|
|
||||||
const nevySettingstabMenu = $('#tabs > div > button:contains("Nevysha Cozy Nest")');
|
|
||||||
// hide the button
|
|
||||||
nevySettingstabMenu.hide();
|
|
||||||
|
|
||||||
addCozyNestCustomBtn();
|
|
||||||
|
|
||||||
///create an hideable right side panel
|
|
||||||
const nevySettingstab = `<div id="nevyui_sh_options_panel" class="nevysha nevysha-tab nevysha-tab-settings" style="display: none;">`;
|
|
||||||
document.querySelector("#tabs").insertAdjacentHTML('beforeend', nevySettingstab);
|
|
||||||
//put tab_nevyui inside the panel
|
|
||||||
document.querySelector("#nevyui_sh_options_panel").appendChild(document.querySelector("#tab_nevyui"));
|
|
||||||
|
|
||||||
//add title
|
|
||||||
const title = `
|
|
||||||
<div class="nevysha settings-nevyui-title">
|
|
||||||
<h2>Nevysha's Cozy Nest</h2>
|
|
||||||
<span class="subtitle">Find your cozy spot on Auto1111's webui</span>
|
|
||||||
</div>`;
|
|
||||||
document.querySelector("#nevyui_sh_options_panel").insertAdjacentHTML("afterbegin", title);
|
|
||||||
|
|
||||||
//add an event listener on #nevyui_sh_options_submit to briefly show a message when the user clicks on it
|
|
||||||
document.querySelector("#nevyui_sh_options_submit").addEventListener("click", (e) => {
|
|
||||||
//cancel event
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
//show the message with a smooth animation using jquery
|
|
||||||
$("#nevysha-saved-feedback").fadeIn();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const jsonFolders = JSON.parse(document.querySelector('#cnib_output_folder textarea').value);
|
|
||||||
//send config data with POST to /cozy-nest/config
|
|
||||||
const config = {
|
|
||||||
"cnib_output_folder": jsonFolders,
|
|
||||||
}
|
|
||||||
fetch('/cozy-nest/config', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(config)
|
|
||||||
}).then(response => {
|
|
||||||
if (response.status === 200) {
|
|
||||||
return response.json();
|
|
||||||
} else {
|
|
||||||
throw new Error('Something went wrong on api server!');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
CozyLogger.debug(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//hide the message after 1.5 second
|
|
||||||
setTimeout(() => {
|
|
||||||
$("#nevysha-saved-feedback").fadeOut();
|
|
||||||
|
|
||||||
//save new settings in localStorage
|
|
||||||
(() => fetchCozyNestConfig())() //ignore async warn
|
|
||||||
}, 1500);
|
|
||||||
});
|
|
||||||
|
|
||||||
//add an event listener on #nevyui_sh_options_submit to briefly show a message when the user clicks on it
|
|
||||||
document.querySelector("#nevyui_sh_options_reset").addEventListener("click", (e) => {
|
|
||||||
//cancel event
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
//show the message with a smooth animation using jquery
|
|
||||||
$("#nevysha-reset-feedback").fadeIn();
|
|
||||||
//hide the message after 1.5 second
|
|
||||||
setTimeout(() => {
|
|
||||||
$("#nevysha-reset-feedback").fadeOut();
|
|
||||||
|
|
||||||
//save new settings in localStorage
|
|
||||||
(() => fetchCozyNestConfig())() //ignore async warn
|
|
||||||
}, 1500);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
//show tab_nevyui by default to bypass gradio
|
|
||||||
document.querySelector("#tab_nevyui").style.display = "block";
|
|
||||||
|
|
||||||
//add click event to the new settings button
|
|
||||||
(function closure() {
|
|
||||||
let shown = false;
|
|
||||||
document.querySelector("#nevyui_sh_options").addEventListener("click", (e) => {
|
|
||||||
//cancel event
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
//show tab_nevyui by default to bypass gradio hidding tabs
|
|
||||||
document.querySelector("#tab_nevyui").style.display = "block";
|
|
||||||
|
|
||||||
//toggle the panel with a slide animation using jquery
|
|
||||||
if (shown) {
|
|
||||||
$("#nevyui_sh_options_panel").slideUp(ANIMATION_SPEED);
|
|
||||||
} else {
|
|
||||||
$("#nevyui_sh_options_panel").slideDown(ANIMATION_SPEED);
|
|
||||||
}
|
|
||||||
shown = !shown;
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
|
|
||||||
|
|
||||||
//add click event to the new update info button
|
|
||||||
(function closure() {
|
|
||||||
let shown = false;
|
|
||||||
document.querySelector("#nevyui_update_info").addEventListener("click", (e) => {
|
|
||||||
//cancel event
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
//show tab_nevyui by default to bypass gradio hidding tabs
|
|
||||||
document.querySelector("#tab_nevyui").style.display = "block";
|
|
||||||
|
|
||||||
//toggle the panel with a slide animation using jquery
|
|
||||||
if (shown) {
|
|
||||||
$("#nevyui_update_info_panel").slideUp(ANIMATION_SPEED);
|
|
||||||
} else {
|
|
||||||
$("#nevyui_update_info_panel").slideDown(ANIMATION_SPEED);
|
|
||||||
}
|
|
||||||
shown = !shown;
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
|
|
||||||
createFolderListComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
const makeSettingsDraggable = () => {
|
|
||||||
// Get a reference to the draggable div element
|
|
||||||
const draggableSettings = document.querySelector('#nevyui_sh_options_panel');
|
|
||||||
|
|
||||||
// Define variables to keep track of the mouse position and offset
|
|
||||||
let isDragging = false;
|
|
||||||
let mouseX = 0;
|
|
||||||
let mouseY = 0;
|
|
||||||
let offsetX = 0;
|
|
||||||
let offsetY = 0;
|
|
||||||
|
|
||||||
// create draggable icon
|
|
||||||
const draggableAnchorIcon = document.createElement('div');
|
|
||||||
draggableAnchorIcon.classList.add('nevysha-draggable-anchor-icon', 'nevysha-button');
|
|
||||||
//add a drag icon
|
|
||||||
draggableAnchorIcon.innerHTML = "Drag Me";
|
|
||||||
// add the anchor to the start of the draggable div
|
|
||||||
draggableSettings.insertBefore(draggableAnchorIcon, draggableSettings.firstChild);
|
|
||||||
// create a blank div above the svg icon to catch for mousedown events
|
|
||||||
const draggableAnchor = document.createElement('div');
|
|
||||||
draggableAnchor.classList.add('nevysha-draggable-anchor', 'nevysha-button');
|
|
||||||
// add the anchor to the start of the draggable div
|
|
||||||
draggableSettings.insertBefore(draggableAnchor, draggableSettings.firstChild);
|
|
||||||
|
|
||||||
//add close button
|
|
||||||
const settingCloseButton = document.createElement('div');
|
|
||||||
settingCloseButton.classList.add('nevysha-draggable-anchor', 'nevysha-draggable-anchor-icon', 'nevysha-setting-close-button', 'nevysha-button');
|
|
||||||
settingCloseButton.innerHTML = 'Close';
|
|
||||||
settingCloseButton.style.left = '70px';
|
|
||||||
settingCloseButton.style.top = '2px';
|
|
||||||
draggableSettings.appendChild(settingCloseButton);
|
|
||||||
settingCloseButton.addEventListener('click', () => {
|
|
||||||
document.querySelector("#nevyui_sh_options").click();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// Add event listeners for mouse events
|
|
||||||
draggableAnchor.addEventListener('mousedown', function(event) {
|
|
||||||
// Set dragging flag and store mouse position and offset from element top-left corner
|
|
||||||
isDragging = true;
|
|
||||||
mouseX = event.clientX;
|
|
||||||
mouseY = event.clientY;
|
|
||||||
offsetX = draggableSettings.offsetLeft;
|
|
||||||
offsetY = draggableSettings.offsetTop;
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('mousemove', function(event) {
|
|
||||||
// If dragging, update element position based on mouse movement
|
|
||||||
if (isDragging) {
|
|
||||||
const deltaX = event.clientX - mouseX;
|
|
||||||
const deltaY = event.clientY - mouseY;
|
|
||||||
draggableSettings.style.left = (offsetX + deltaX) + 'px';
|
|
||||||
draggableSettings.style.top = (offsetY + deltaY) + 'px';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('mouseup', function(event) {
|
|
||||||
// Reset dragging flag
|
|
||||||
isDragging = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function observeElementAdded(targetSelector, callback) {
|
function observeElementAdded(targetSelector, callback) {
|
||||||
// Create a new MutationObserver instance
|
// Create a new MutationObserver instance
|
||||||
|
|
@ -966,12 +579,12 @@ function tweakExtraNetworks({prefix}) {
|
||||||
} else {
|
} else {
|
||||||
//hide the extra network
|
//hide the extra network
|
||||||
$(extraNetworkGradioWrapper).animate({
|
$(extraNetworkGradioWrapper).animate({
|
||||||
"margin-right": `-=${extraNetworkGradioWrapper.offsetWidth}`},
|
"margin-right": `-=${extraNetworkGradioWrapper.offsetWidth}`},
|
||||||
ANIMATION_SPEED,
|
ANIMATION_SPEED,
|
||||||
() => {
|
() => {
|
||||||
// hide it after the animation is done
|
// hide it after the animation is done
|
||||||
extraNetworkGradioWrapper.style.display = 'none';
|
extraNetworkGradioWrapper.style.display = 'none';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1297,32 +910,18 @@ const addTabWrapper = () => {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createRightWrapperDiv() {
|
function buildRightSlidePanelFor(label, buttonLabel, rightPanBtnWrapper, tab) {
|
||||||
const tab = document.querySelector(`div#tabs`);
|
|
||||||
|
|
||||||
//create wrapper div for the button
|
|
||||||
const rightPanBtnWrapper = document.createElement('div');
|
|
||||||
rightPanBtnWrapper.setAttribute('id', `right_button_wrapper`);
|
|
||||||
rightPanBtnWrapper.classList.add('nevysha', 'nevysha-right-button-wrapper');
|
|
||||||
//add button to the begining of the tab
|
|
||||||
tab.insertAdjacentElement('beforeend', rightPanBtnWrapper);
|
|
||||||
|
|
||||||
//add a button for image browser
|
|
||||||
const cozyImgBrowserBtn = document.createElement('button');
|
const cozyImgBrowserBtn = document.createElement('button');
|
||||||
cozyImgBrowserBtn.setAttribute('id', `image_browser_right_button`);
|
cozyImgBrowserBtn.setAttribute('id', `${label}_right_button`);
|
||||||
cozyImgBrowserBtn.classList.add('nevysha', 'lg', 'primary', 'gradio-button');
|
cozyImgBrowserBtn.classList.add('nevysha', 'lg', 'primary', 'gradio-button');
|
||||||
cozyImgBrowserBtn.innerHTML = `<div>Cozy Image Browser</div>`;
|
cozyImgBrowserBtn.innerHTML = `<div>${buttonLabel}</div>`;
|
||||||
cozyImgBrowserBtn.addEventListener('click', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
});
|
|
||||||
rightPanBtnWrapper.appendChild(cozyImgBrowserBtn);
|
rightPanBtnWrapper.appendChild(cozyImgBrowserBtn);
|
||||||
|
|
||||||
//create a panel to display Cozy Image Browser
|
//create a panel to display Cozy Image Browser
|
||||||
const cozyImgBrowserPanel =
|
const cozyImgBrowserPanel =
|
||||||
`<div id="cozy_img_browser_panel" class="nevysha cozy-img-browser-panel slide-right-browser-panel" style="display: none">
|
`<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">
|
||||||
<div class="nevysha" id="cozy-img-browser-react"/>
|
<div class="nevysha" id="${label}-react"/>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
//add the panel to the end of the tab
|
//add the panel to the end of the tab
|
||||||
|
|
@ -1330,9 +929,9 @@ function createRightWrapperDiv() {
|
||||||
|
|
||||||
// Create a vertical line component
|
// Create a vertical line component
|
||||||
const lineWrapper = createVerticalLineComp();
|
const lineWrapper = createVerticalLineComp();
|
||||||
const cozyImgBrowserPanelWrapper = document.querySelector('#cozy_img_browser_panel');
|
const cozyImgBrowserPanelWrapper = document.querySelector(`#${label}_panel`);
|
||||||
//set cozyImgBrowserPanelWrapper.style.width from local storage value if it exists
|
//set cozyImgBrowserPanelWrapper.style.width from local storage value if it exists
|
||||||
const cozyImgBrowserPanelWidth = localStorage.getItem('cozyImgBrowserPanelWrapper');
|
const cozyImgBrowserPanelWidth = localStorage.getItem(`${label}_panelWidth`);
|
||||||
if (cozyImgBrowserPanelWidth) {
|
if (cozyImgBrowserPanelWidth) {
|
||||||
cozyImgBrowserPanelWrapper.style.width = cozyImgBrowserPanelWidth;
|
cozyImgBrowserPanelWrapper.style.width = cozyImgBrowserPanelWidth;
|
||||||
}
|
}
|
||||||
|
|
@ -1341,7 +940,7 @@ function createRightWrapperDiv() {
|
||||||
//TODO refactor to factorise code bellow with extraNetwork
|
//TODO refactor to factorise code bellow with extraNetwork
|
||||||
//add a close button inside the line
|
//add a close button inside the line
|
||||||
const closeCozyImgBrowser = document.createElement('button');
|
const closeCozyImgBrowser = document.createElement('button');
|
||||||
closeCozyImgBrowser.setAttribute('id', `floating_close_cozy_img_browser_panel_button`);
|
closeCozyImgBrowser.setAttribute('id', `floating_close_${label}__panel_button`);
|
||||||
//add button class
|
//add button class
|
||||||
closeCozyImgBrowser.classList.add('nevysha', 'lg', 'primary', 'gradio-button', 'nevysha-extra-network-floating-btn');
|
closeCozyImgBrowser.classList.add('nevysha', 'lg', 'primary', 'gradio-button', 'nevysha-extra-network-floating-btn');
|
||||||
closeCozyImgBrowser.innerHTML = '<div>Close</div>';
|
closeCozyImgBrowser.innerHTML = '<div>Close</div>';
|
||||||
|
|
@ -1352,7 +951,7 @@ function createRightWrapperDiv() {
|
||||||
//add the button at the begining of the div
|
//add the button at the begining of the div
|
||||||
lineWrapper.insertBefore(closeCozyImgBrowser, lineWrapper.firstChild);
|
lineWrapper.insertBefore(closeCozyImgBrowser, lineWrapper.firstChild);
|
||||||
//Add an event listener to the resizer element to track mouse movement
|
//Add an event listener to the resizer element to track mouse movement
|
||||||
lineWrapper.addEventListener('mousedown', function(e) {
|
lineWrapper.addEventListener('mousedown', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
// Set the initial values for the width and height of the container
|
// Set the initial values for the width and height of the container
|
||||||
|
|
@ -1376,7 +975,7 @@ function createRightWrapperDiv() {
|
||||||
function stopDrag() {
|
function stopDrag() {
|
||||||
|
|
||||||
//save the new width in local storage
|
//save the new width in local storage
|
||||||
localStorage.setItem(`cozyImgBrowserPanelWrapper`, cozyImgBrowserPanelWrapper.style.width);
|
localStorage.setItem(`${label}_panelWidth`, cozyImgBrowserPanelWrapper.style.width);
|
||||||
|
|
||||||
document.removeEventListener('mousemove', drag);
|
document.removeEventListener('mousemove', drag);
|
||||||
document.removeEventListener('mouseup', stopDrag);
|
document.removeEventListener('mouseup', stopDrag);
|
||||||
|
|
@ -1384,16 +983,15 @@ function createRightWrapperDiv() {
|
||||||
});
|
});
|
||||||
|
|
||||||
//add listener to open or close the panel using jquery animate
|
//add listener to open or close the panel using jquery animate
|
||||||
cozyImgBrowserBtn.addEventListener('click', (e) => {
|
cozyImgBrowserBtn.addEventListener('click', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const panel = document.querySelector('#cozy_img_browser_panel');
|
const panel = document.querySelector(`#${label}_panel`);
|
||||||
if (panel.style.display === 'none') {
|
if (panel.style.display === 'none') {
|
||||||
panel.style.display = 'flex'
|
panel.style.display = 'flex'
|
||||||
panel.style.marginRight = `-${panel.offsetWidth}px`;
|
panel.style.marginRight = `-${panel.offsetWidth}px`;
|
||||||
$(panel).animate({"margin-right": `+=${panel.offsetWidth}`}, ANIMATION_SPEED);
|
$(panel).animate({"margin-right": `+=${panel.offsetWidth}`}, ANIMATION_SPEED);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
$(panel).animate({"margin-right": `-=${panel.offsetWidth}`}, ANIMATION_SPEED, () => {
|
$(panel).animate({"margin-right": `-=${panel.offsetWidth}`}, ANIMATION_SPEED, () => {
|
||||||
panel.style.display = 'none'
|
panel.style.display = 'none'
|
||||||
});
|
});
|
||||||
|
|
@ -1401,19 +999,40 @@ function createRightWrapperDiv() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function setButtonVisibilityFromCurrentTab(id) {
|
function createRightWrapperDiv() {
|
||||||
|
const tab = document.querySelector(`div#tabs`);
|
||||||
|
|
||||||
//hide each button that ends with extra_networks_right_button
|
//create wrapper div for the button
|
||||||
const extraNetworksRightBtns = document.querySelectorAll(`button[id$="extra_networks_right_button"]`);
|
const rightPanBtnWrapper = document.createElement('div');
|
||||||
extraNetworksRightBtns.forEach((btn) => {
|
rightPanBtnWrapper.setAttribute('id', `right_button_wrapper`);
|
||||||
btn.style.display = 'none';
|
rightPanBtnWrapper.classList.add('nevysha', 'nevysha-right-button-wrapper');
|
||||||
|
//add button to the begining of the tab
|
||||||
|
tab.insertAdjacentElement('beforeend', rightPanBtnWrapper);
|
||||||
|
|
||||||
})
|
if (COZY_NEST_CONFIG.enable_extra_network_tweaks === true) {
|
||||||
if (id === 'tab_txt2img') {
|
buildRightSlidePanelFor('cozy-txt2img-extra-network', 'Extra Network', rightPanBtnWrapper, tab);
|
||||||
document.querySelector('button#txt2img_extra_networks_right_button').style.display = 'flex';
|
document.getElementById('cozy-txt2img-extra-network-react').classList.add('cozy-extra-network')
|
||||||
|
|
||||||
|
buildRightSlidePanelFor('cozy-img2img-extra-network', 'Extra Network', rightPanBtnWrapper, tab);
|
||||||
|
document.getElementById('cozy-img2img-extra-network-react').classList.add('cozy-extra-network')
|
||||||
|
document.querySelector(`#cozy-img2img-extra-network_right_button`).style.display = 'none';
|
||||||
}
|
}
|
||||||
if (id === 'tab_img2img') {
|
if (COZY_NEST_CONFIG.disable_image_browser !== true) {
|
||||||
document.querySelector('button#img2img_extra_networks_right_button').style.display = 'flex';
|
buildRightSlidePanelFor('cozy-img-browser', 'Cozy Image Browser', rightPanBtnWrapper, tab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setButtonVisibilityFromCurrentTab(id) {
|
||||||
|
CozyLogger.debug(`setButtonVisibilityFromCurrentTab(${id})`);
|
||||||
|
|
||||||
|
document.querySelector(`#cozy-txt2img-extra-network_right_button`).style.display = 'none';
|
||||||
|
document.querySelector(`#cozy-img2img-extra-network_right_button`).style.display = 'none';
|
||||||
|
|
||||||
|
if (id === 'tab_txt2img') {
|
||||||
|
document.querySelector(`#cozy-txt2img-extra-network_right_button`).style.display = 'flex';
|
||||||
|
}
|
||||||
|
else if (id === 'tab_img2img') {
|
||||||
|
document.querySelector(`#cozy-img2img-extra-network_right_button`).style.display = 'flex';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1484,68 +1103,6 @@ window.sendToPipe = sendToPipe;
|
||||||
|
|
||||||
window.troubleshootSize = {}
|
window.troubleshootSize = {}
|
||||||
|
|
||||||
const recalcOffsetFromMenuHeight = () => {
|
|
||||||
let menuHeight = 0;
|
|
||||||
|
|
||||||
const tabs = document.getElementById('tabs');
|
|
||||||
|
|
||||||
const footer = document.querySelector('#footer #footer');
|
|
||||||
let footerHeight;
|
|
||||||
if (!footer) {
|
|
||||||
if (COZY_NEST_CONFIG.webui === WEBUI_SDNEXT)
|
|
||||||
footerHeight = 5;
|
|
||||||
else
|
|
||||||
footerHeight = 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
footerHeight = footer.offsetHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (COZY_NEST_CONFIG.main_menu_position !== 'left') {
|
|
||||||
const menu = document.querySelector('.tab-nav.nevysha-tabnav')
|
|
||||||
|
|
||||||
menuHeight = menu.offsetHeight + 2;
|
|
||||||
document.querySelector(':root').style.setProperty('--menu-top-height', `${menuHeight}px`);
|
|
||||||
const $app = $('.gradio-container.app');
|
|
||||||
$app.attr('style', `${$app.attr('style')} padding-top: ${menuHeight}px !important;`);
|
|
||||||
|
|
||||||
const rect = tabs.getBoundingClientRect();
|
|
||||||
const tabsTop = rect.top;
|
|
||||||
|
|
||||||
document.querySelector(':root').style.setProperty('--main-container-height', `${window.innerHeight - (tabsTop + footerHeight)}px`);
|
|
||||||
|
|
||||||
window.troubleshootSize = {
|
|
||||||
menuHeight,
|
|
||||||
footerHeight: footerHeight,
|
|
||||||
tabsTop,
|
|
||||||
WindowInnerHeight: window.innerHeight,
|
|
||||||
bodyHeight: window.innerHeight - (tabsTop + footerHeight),
|
|
||||||
'main-container-height': `${window.innerHeight - (tabsTop + footerHeight)}px`,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
document.querySelector(':root').style.setProperty('--menu-top-height', `1px`);
|
|
||||||
|
|
||||||
const $app = $('.gradio-container.app');
|
|
||||||
$app.attr('style', `${$app.attr('style')} padding-top: ${menuHeight}px !important;`);
|
|
||||||
|
|
||||||
const rect = tabs.getBoundingClientRect();
|
|
||||||
const tabsTop = rect.top;
|
|
||||||
|
|
||||||
document.querySelector(':root').style.setProperty('--main-container-height', `${window.innerHeight - (tabsTop + footerHeight)}px`);
|
|
||||||
|
|
||||||
window.troubleshootSize = {
|
|
||||||
menuHeight,
|
|
||||||
footerHeight: footerHeight,
|
|
||||||
tabsTop,
|
|
||||||
WindowInnerHeight: window.innerHeight,
|
|
||||||
bodyHeight: window.innerHeight - (tabsTop + footerHeight),
|
|
||||||
'main-container-height': `${window.innerHeight - (tabsTop + footerHeight)}px`,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function addOptionsObserver() {
|
function addOptionsObserver() {
|
||||||
// Select the target node
|
// Select the target node
|
||||||
const targetNode = document.body;
|
const targetNode = document.body;
|
||||||
|
|
@ -1616,10 +1173,6 @@ const onloadSafe = (done) => {
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
function tweakForSDNext() {
|
|
||||||
document.querySelector('#setting_nevyui_fetchOutputFolderFromA1111Settings').style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
const onLoad = (done) => {
|
const onLoad = (done) => {
|
||||||
|
|
||||||
let gradioApp = window.gradioApp;
|
let gradioApp = window.gradioApp;
|
||||||
|
|
@ -1675,7 +1228,7 @@ const onLoad = (done) => {
|
||||||
document.querySelectorAll('.extra-network-cards').forEach(elem => elem.setAttribute('class', `${elem.getAttribute('class')} nevysha nevysha-scrollable`))
|
document.querySelectorAll('.extra-network-cards').forEach(elem => elem.setAttribute('class', `${elem.getAttribute('class')} nevysha nevysha-scrollable`))
|
||||||
document.querySelectorAll('#cozy_nest_settings_tabs > .tabitem').forEach(elem => elem.classList.add('nevysha', 'nevysha-scrollable'))
|
document.querySelectorAll('#cozy_nest_settings_tabs > .tabitem').forEach(elem => elem.classList.add('nevysha', 'nevysha-scrollable'))
|
||||||
|
|
||||||
document.querySelector('#nevyui_sh_options_start_socket').setAttribute('style', 'display: none;')
|
|
||||||
//hide "send to" panel in settings
|
//hide "send to" panel in settings
|
||||||
//this panel is used to transfert image data into tab
|
//this panel is used to transfert image data into tab
|
||||||
document.querySelector('#nevysha-send-to').setAttribute('style', 'display: none;')
|
document.querySelector('#nevysha-send-to').setAttribute('style', 'display: none;')
|
||||||
|
|
@ -1697,23 +1250,13 @@ const onLoad = (done) => {
|
||||||
addDraggable(bundle);
|
addDraggable(bundle);
|
||||||
addScrollable(bundle);
|
addScrollable(bundle);
|
||||||
|
|
||||||
if (COZY_NEST_CONFIG.enable_extra_network_tweaks) {
|
|
||||||
tweakExtraNetworks(bundle);
|
|
||||||
addExtraNetworksBtn(bundle);
|
|
||||||
}
|
|
||||||
|
|
||||||
//add a clear button to generated image
|
//add a clear button to generated image
|
||||||
clearGeneratedImage(bundle);
|
clearGeneratedImage(bundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (COZY_NEST_CONFIG.enable_extra_network_tweaks) {
|
nevysha_magic({prefix: "txt2img"});
|
||||||
document.querySelector(`button#txt2img_extra_networks`).click();
|
nevysha_magic({prefix: "img2img"});
|
||||||
document.querySelector(`button#img2img_extra_networks`).click();
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
|
||||||
nevysha_magic({prefix: "txt2img"});
|
|
||||||
nevysha_magic({prefix: "img2img"});
|
|
||||||
}, 500)
|
|
||||||
|
|
||||||
//general
|
//general
|
||||||
tweakButtonsIcons();
|
tweakButtonsIcons();
|
||||||
|
|
@ -1725,11 +1268,9 @@ const onLoad = (done) => {
|
||||||
//add expend to inpainting
|
//add expend to inpainting
|
||||||
tweakInpainting();
|
tweakInpainting();
|
||||||
|
|
||||||
//tweak webui setting page for Cozy Nest directly with JS because... gradio blblblbl
|
addCozyNestCustomBtn();
|
||||||
tweakNevyUiSettings();
|
|
||||||
|
|
||||||
//load settings
|
//load settings
|
||||||
applyCozyNestConfig();
|
|
||||||
recalcOffsetFromMenuHeight();
|
recalcOffsetFromMenuHeight();
|
||||||
|
|
||||||
//add tab wrapper
|
//add tab wrapper
|
||||||
|
|
@ -1744,19 +1285,9 @@ const onLoad = (done) => {
|
||||||
document.querySelector("body").classList.remove("nevysha-light")
|
document.querySelector("body").classList.remove("nevysha-light")
|
||||||
}
|
}
|
||||||
|
|
||||||
//make settings draggable
|
|
||||||
makeSettingsDraggable();
|
|
||||||
|
|
||||||
//add observer for .options resize
|
//add observer for .options resize
|
||||||
addOptionsObserver();
|
addOptionsObserver();
|
||||||
|
|
||||||
if (COZY_NEST_CONFIG.webui === WEBUI_SDNEXT) {
|
|
||||||
tweakForSDNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
//load /assets/index-eff6a2cc.js
|
|
||||||
loadCozyNestImageBrowserSubmodule();
|
|
||||||
|
|
||||||
/* --------------- TWEAK SOME EXTENSION --------------- */
|
/* --------------- TWEAK SOME EXTENSION --------------- */
|
||||||
//if AWQ-container is present in COZY_NEST_CONFIG.extensions array from localStorage, tweak AWQ
|
//if AWQ-container is present in COZY_NEST_CONFIG.extensions array from localStorage, tweak AWQ
|
||||||
if (COZY_NEST_CONFIG.extensions
|
if (COZY_NEST_CONFIG.extensions
|
||||||
|
|
@ -1768,15 +1299,17 @@ const onLoad = (done) => {
|
||||||
done();
|
done();
|
||||||
};
|
};
|
||||||
|
|
||||||
async function loadCozyNestImageBrowserSubmodule() {
|
export async function saveCozyNestConfig(config) {
|
||||||
try {
|
|
||||||
const jsModule = await fetch(`file=extensions/Cozy-Nest/cozy-nest-image-browser/assets/index.js?t=${Date.now()}`);
|
config = config || COZY_NEST_CONFIG;
|
||||||
eval(await jsModule.text());
|
|
||||||
}
|
await fetch('/cozy-nest/config', {
|
||||||
catch (err) {
|
method: 'POST',
|
||||||
// handle any errors that occur during the import process
|
headers: {
|
||||||
console.error("Failed to load cozy-nest-image-browser submodule", err);
|
'Content-Type': 'application/json'
|
||||||
}
|
},
|
||||||
|
body: JSON.stringify(config)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function detectWebuiContext() {
|
async function detectWebuiContext() {
|
||||||
|
|
@ -1794,13 +1327,7 @@ async function detectWebuiContext() {
|
||||||
COZY_NEST_CONFIG.webui = WEBUI_A1111;
|
COZY_NEST_CONFIG.webui = WEBUI_A1111;
|
||||||
}
|
}
|
||||||
|
|
||||||
await fetch('/cozy-nest/config', {
|
await saveCozyNestConfig();
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(COZY_NEST_CONFIG)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
CozyLogger.debug(`webui is ${COZY_NEST_CONFIG.webui}`)
|
CozyLogger.debug(`webui is ${COZY_NEST_CONFIG.webui}`)
|
||||||
}
|
}
|
||||||
|
|
@ -1810,7 +1337,7 @@ async function detectWebuiContext() {
|
||||||
* either from main.js for Dev or from the loader in the extension folder
|
* either from main.js for Dev or from the loader in the extension folder
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
export default async function cozyNestLoader() {
|
export default async function cozyNestModuleLoader(extraCozyNestModulesLoader) {
|
||||||
|
|
||||||
//check if the param CozyNest=No is present in the url
|
//check if the param CozyNest=No is present in the url
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
|
@ -1834,22 +1361,28 @@ export default async function cozyNestLoader() {
|
||||||
setupPopupInstanceInfo();
|
setupPopupInstanceInfo();
|
||||||
setupErrorHandling();
|
setupErrorHandling();
|
||||||
|
|
||||||
onloadSafe(() => {
|
// wrap onloadSafe in Promise
|
||||||
CozyLogger.log(`running.`);
|
return new Promise(resolve => {
|
||||||
//remove #nevysha-loading from DOM
|
onloadSafe(async () => {
|
||||||
Loading.stop();
|
|
||||||
|
|
||||||
SimpleTimer.end(COZY_NEST_DOM_TWEAK_LOAD_DURATION);
|
await extraCozyNestModulesLoader();
|
||||||
SimpleTimer.end(COZY_NEST_GRADIO_LOAD_DURATION);
|
|
||||||
|
|
||||||
if (shouldDisplaySDNextWarning)
|
//remove #nevysha-loading from DOM
|
||||||
showAlert(
|
Loading.stop();
|
||||||
"Warning",
|
CozyLogger.log(`running.`);
|
||||||
"Cozy Nest detected that you are using SD.Next and running Cozy Nest for the first time. To ensure compatibility, please restart the server."
|
|
||||||
)
|
SimpleTimer.end(COZY_NEST_DOM_TWEAK_LOAD_DURATION);
|
||||||
});
|
SimpleTimer.end(COZY_NEST_GRADIO_LOAD_DURATION);
|
||||||
|
|
||||||
|
if (shouldDisplaySDNextWarning)
|
||||||
|
showAlert(
|
||||||
|
"Warning",
|
||||||
|
"Cozy Nest detected that you are using SD.Next and running Cozy Nest for the first time. To ensure compatibility, please restart the server."
|
||||||
|
)
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
})
|
||||||
};
|
};
|
||||||
window.cozyNestLoader = cozyNestLoader;
|
|
||||||
|
|
||||||
|
|
||||||
function setupErrorHandling() {
|
function setupErrorHandling() {
|
||||||
|
|
@ -1857,26 +1390,28 @@ function setupErrorHandling() {
|
||||||
//set a global error handler
|
//set a global error handler
|
||||||
window.addEventListener('error', function ({message, filename , lineno, colno, error }) {
|
window.addEventListener('error', function ({message, filename , lineno, colno, error }) {
|
||||||
|
|
||||||
// get setting_nevyui_errorPopup checkbox value
|
//TODO uncomment
|
||||||
const errorPopup = document.querySelector('#setting_nevyui_errorPopup').querySelector("input").checked;
|
|
||||||
if (!errorPopup) return;
|
|
||||||
|
|
||||||
//if filename does not contains Cozy-Nest, ignore
|
// // get setting_nevyui_errorPopup checkbox value
|
||||||
if (!filename.toLowerCase().includes('cozy-nest')) return;
|
// const errorPopup = document.querySelector('#setting_nevyui_errorPopup').querySelector("input").checked;
|
||||||
|
// if (!errorPopup) return;
|
||||||
// Handle the error here
|
//
|
||||||
populateInstanceInfoDialog();
|
// //if filename does not contains Cozy-Nest, ignore
|
||||||
document.querySelector('#cozy_nest_error_handling_display').innerHTML = `An error occurred: ${message} at ${filename } line ${lineno} column ${colno}`;
|
// if (!filename.toLowerCase().includes('cozy-nest')) return;
|
||||||
document.querySelector('#cozy_nest_error_handling_display_stack').innerHTML = error.stack;
|
//
|
||||||
document.querySelector('#cozy_nest_error_handling_display_stack').setAttribute('style', 'display: block;');
|
// // Handle the error here
|
||||||
showInstanceInfoDialog();
|
// populateInstanceInfoDialog();
|
||||||
|
// document.querySelector('#cozy_nest_error_handling_display').innerHTML = `An error occurred: ${message} at ${filename } line ${lineno} column ${colno}`;
|
||||||
|
// document.querySelector('#cozy_nest_error_handling_display_stack').innerHTML = error.stack;
|
||||||
|
// document.querySelector('#cozy_nest_error_handling_display_stack').setAttribute('style', 'display: block;');
|
||||||
|
// showInstanceInfoDialog();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let COZY_NEST_CONFIG;
|
let COZY_NEST_CONFIG;
|
||||||
let shouldDisplaySDNextWarning = false;
|
let shouldDisplaySDNextWarning = false;
|
||||||
|
|
||||||
async function fetchCozyNestConfig() {
|
export async function fetchCozyNestConfig() {
|
||||||
const response = await fetch(`file=extensions/Cozy-Nest/nevyui_settings.json?t=${Date.now()}`);
|
const response = await fetch(`file=extensions/Cozy-Nest/nevyui_settings.json?t=${Date.now()}`);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
COZY_NEST_CONFIG = await response.json();
|
COZY_NEST_CONFIG = await response.json();
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,5 @@ export const waves = "<div id='nevy_waves'><div class='wave'></div> <div class='
|
||||||
export const svg_magic_wand = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><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>`;
|
export const svg_magic_wand = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><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>`;
|
||||||
export const svg_update_info = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M144 480C64.5 480 0 415.5 0 336c0-62.8 40.2-116.2 96.2-135.9c-.1-2.7-.2-5.4-.2-8.1c0-88.4 71.6-160 160-160c59.3 0 111 32.2 138.7 80.2C409.9 102 428.3 96 448 96c53 0 96 43 96 96c0 12.2-2.3 23.8-6.4 34.6C596 238.4 640 290.1 640 352c0 70.7-57.3 128-128 128H144zm79-167l80 80c9.4 9.4 24.6 9.4 33.9 0l80-80c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-39 39V184c0-13.3-10.7-24-24-24s-24 10.7-24 24V318.1l-39-39c-9.4-9.4-24.6-9.4-33.9 0s-9.4 24.6 0 33.9z"/></svg>`;
|
export const svg_update_info = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M144 480C64.5 480 0 415.5 0 336c0-62.8 40.2-116.2 96.2-135.9c-.1-2.7-.2-5.4-.2-8.1c0-88.4 71.6-160 160-160c59.3 0 111 32.2 138.7 80.2C409.9 102 428.3 96 448 96c53 0 96 43 96 96c0 12.2-2.3 23.8-6.4 34.6C596 238.4 640 290.1 640 352c0 70.7-57.3 128-128 128H144zm79-167l80 80c9.4 9.4 24.6 9.4 33.9 0l80-80c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-39 39V184c0-13.3-10.7-24-24-24s-24 10.7-24 24V318.1l-39-39c-9.4-9.4-24.6-9.4-33.9 0s-9.4 24.6 0 33.9z"/></svg>`;
|
||||||
export const loading_roll = `<div class="lds-roller"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div>`
|
export const loading_roll = `<div class="lds-roller"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div>`
|
||||||
|
export const loading_ellipsis = `<div class="lds-ellipsis"><div></div><div></div><div></div><div></div></div>`
|
||||||
export const xmark = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"/></svg>`
|
export const xmark = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"/></svg>`
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import {getLuminance, getSubduedFontColor, hexToRgb} from "../cozy-utils.js";
|
import {getLuminance, getSubduedFontColor, hexToRgb} from "../cozy-utils.js";
|
||||||
|
import $ from "jquery";
|
||||||
|
import {WEBUI_SDNEXT} from "../Constants.js";
|
||||||
|
|
||||||
export function applyWavesColor(hexColor) {
|
export function applyWavesColor(hexColor) {
|
||||||
|
COZY_NEST_CONFIG.waves_color = hexColor;
|
||||||
const rgbColor = hexToRgb(hexColor);
|
const rgbColor = hexToRgb(hexColor);
|
||||||
document.querySelectorAll(".wave").forEach((wave) => {
|
document.querySelectorAll(".wave").forEach((wave) => {
|
||||||
wave.setAttribute("style", `background: rgb(${rgbColor} / 16%)`);
|
wave.setAttribute("style", `background: rgb(${rgbColor} / 16%)`);
|
||||||
|
|
@ -8,17 +11,20 @@ export function applyWavesColor(hexColor) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyFontColor(hexColor) {
|
export function applyFontColor(hexColor) {
|
||||||
|
COZY_NEST_CONFIG.font_color = hexColor;
|
||||||
const rgbColor = hexToRgb(hexColor);
|
const rgbColor = hexToRgb(hexColor);
|
||||||
document.querySelector(':root').style.setProperty('--nevysha-font-color', `rgb(${rgbColor})`);
|
document.querySelector(':root').style.setProperty('--nevysha-font-color', `rgb(${rgbColor})`);
|
||||||
document.querySelector(':root').style.setProperty('--nevysha-font-color-subdued', getSubduedFontColor(hexColor));
|
document.querySelector(':root').style.setProperty('--nevysha-font-color-subdued', getSubduedFontColor(hexColor));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyBgGradiantColor(hexColor) {
|
export function applyBgGradiantColor(hexColor) {
|
||||||
|
COZY_NEST_CONFIG.bg_gradiant_color = hexColor;
|
||||||
const rgbColor = hexToRgb(hexColor);
|
const rgbColor = hexToRgb(hexColor);
|
||||||
document.querySelector(':root').style.setProperty('--nevysha-gradiant-1', `rgb(${rgbColor})`);
|
document.querySelector(':root').style.setProperty('--nevysha-gradiant-1', `rgb(${rgbColor})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyAccentColor(hexColor, colorFromLuminance) {
|
export function applyAccentColor(hexColor, colorFromLuminance) {
|
||||||
|
COZY_NEST_CONFIG.accent_color = hexColor;
|
||||||
const rgbColor = hexToRgb(hexColor);
|
const rgbColor = hexToRgb(hexColor);
|
||||||
document.querySelector(':root').style.setProperty('--ae-primary-color', `rgb(${rgbColor})`);
|
document.querySelector(':root').style.setProperty('--ae-primary-color', `rgb(${rgbColor})`);
|
||||||
if (getLuminance(colorFromLuminance) > 0.5) {
|
if (getLuminance(colorFromLuminance) > 0.5) {
|
||||||
|
|
@ -28,6 +34,169 @@ export function applyAccentColor(hexColor, colorFromLuminance) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const applyDisabledWavesAndGradiant = (disableWavesAndGradiant) => {
|
||||||
|
COZY_NEST_CONFIG.disable_waves_and_gradiant = disableWavesAndGradiant;
|
||||||
|
const $waves = $('.wave');
|
||||||
|
const $body = $('body');
|
||||||
|
if (disableWavesAndGradiant) {
|
||||||
|
$waves.css('animation', 'none');
|
||||||
|
$body.css('animation', 'none');
|
||||||
|
$body.css('background-position', '75% 75%')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$waves.css('animation', '');
|
||||||
|
$body.css('animation', '');
|
||||||
|
$body.css('background-position', '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const applyAccentForGenerate = (checked, hexColorForAccent) => {
|
||||||
|
COZY_NEST_CONFIG.accent_generate_button = checked;
|
||||||
|
document.querySelectorAll('button[id$="_generate"]').forEach((btn) => {
|
||||||
|
if (checked) {
|
||||||
|
let txtColorAppending = "";
|
||||||
|
if (getLuminance(hexColorForAccent) > 0.5) {
|
||||||
|
txtColorAppending = "color: black !important";
|
||||||
|
}
|
||||||
|
btn.setAttribute("style", `background: var(--ae-primary-color) !important; ${txtColorAppending}`);
|
||||||
|
} else {
|
||||||
|
btn.setAttribute("style", '');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const applyFontSize = (fontSize) => {
|
||||||
|
COZY_NEST_CONFIG.font_size = fontSize;
|
||||||
|
document.querySelector(':root').style.setProperty('--nevysha-text-md', `${fontSize}px`);
|
||||||
|
recalcOffsetFromMenuHeight()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setCardHeight = (cardHeight) => {
|
||||||
|
COZY_NEST_CONFIG.card_height = cardHeight;
|
||||||
|
document.querySelector(':root').style.setProperty('--extra-network-card-height', `${cardHeight}em`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setCardWidth = (cardWidth) => {
|
||||||
|
COZY_NEST_CONFIG.card_width = cardWidth;
|
||||||
|
document.querySelector(':root').style.setProperty('--extra-network-card-width', `${cardWidth}em`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const applyMenuPosition = (position) => {
|
||||||
|
COZY_NEST_CONFIG.main_menu_position = position;
|
||||||
|
//top mode
|
||||||
|
if (position === "top" || position === "top_centered") {
|
||||||
|
document.querySelector(".nevysha.nevysha-tabnav").classList.add("menu-fix-top")
|
||||||
|
document.querySelector(".gradio-container.app").classList.add("menu-fix-top")
|
||||||
|
document.querySelector("#nevysha-btn-menu-wrapper")?.classList.add("menu-fix-top")
|
||||||
|
document.querySelector(':root').style.setProperty('--nevysha-margin-left', `0`);
|
||||||
|
document.querySelector(':root').style.setProperty('--menu-top-height', `25px`);
|
||||||
|
|
||||||
|
//centered or not
|
||||||
|
if (position === "top_centered") {
|
||||||
|
document.querySelector(".nevysha.nevysha-tabnav").classList.add("center-menu-items")
|
||||||
|
} else {
|
||||||
|
document.querySelector(".nevysha.nevysha-tabnav").classList.remove("center-menu-items")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//left mode
|
||||||
|
else {
|
||||||
|
document.querySelector(".nevysha.nevysha-tabnav").classList.remove("center-menu-items")
|
||||||
|
document.querySelector(".nevysha.nevysha-tabnav").classList.remove("menu-fix-top")
|
||||||
|
document.querySelector(".gradio-container.app").classList.remove("menu-fix-top")
|
||||||
|
document.querySelector("#nevysha-btn-menu-wrapper")?.classList.remove("menu-fix-top")
|
||||||
|
document.querySelector(':root').style.setProperty('--nevysha-margin-left', `175px`);
|
||||||
|
document.querySelector(':root').style.setProperty('--menu-top-height', `1px`);
|
||||||
|
}
|
||||||
|
recalcOffsetFromMenuHeight()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setQuicksettingPosition = (position) => {
|
||||||
|
COZY_NEST_CONFIG.quicksettings_position = position;
|
||||||
|
if (position === 'split') {
|
||||||
|
document.querySelector("#quicksettings_gap").classList.add("nevysha-quicksettings-gap")
|
||||||
|
document.querySelector("#quicksettings").classList.remove("centered-quicksettings")
|
||||||
|
}
|
||||||
|
else if (position === 'centered') {
|
||||||
|
document.querySelector("#quicksettings_gap").classList.remove("nevysha-quicksettings-gap")
|
||||||
|
document.querySelector("#quicksettings").classList.add("centered-quicksettings")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
document.querySelector("#quicksettings_gap").classList.remove("nevysha-quicksettings-gap")
|
||||||
|
document.querySelector("#quicksettings").classList.remove("centered-quicksettings")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setSfwSettings = (isSfwChecked) => {
|
||||||
|
COZY_NEST_CONFIG.sfw_mode = isSfwChecked;
|
||||||
|
if (isSfwChecked) {
|
||||||
|
document.querySelector('body').classList.add("nsfw");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
document.querySelector('body').classList.remove("nsfw");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const recalcOffsetFromMenuHeight = () => {
|
||||||
|
let menuHeight = 0;
|
||||||
|
|
||||||
|
const tabs = document.getElementById('tabs');
|
||||||
|
|
||||||
|
const footer = document.querySelector('#footer #footer');
|
||||||
|
let footerHeight;
|
||||||
|
if (!footer) {
|
||||||
|
if (COZY_NEST_CONFIG.webui === WEBUI_SDNEXT)
|
||||||
|
footerHeight = 5;
|
||||||
|
else
|
||||||
|
footerHeight = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
footerHeight = footer.offsetHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (COZY_NEST_CONFIG.main_menu_position !== 'left') {
|
||||||
|
const menu = document.querySelector('.tab-nav.nevysha-tabnav')
|
||||||
|
|
||||||
|
menuHeight = menu.offsetHeight + 2;
|
||||||
|
document.querySelector(':root').style.setProperty('--menu-top-height', `${menuHeight}px`);
|
||||||
|
const $app = $('.gradio-container.app');
|
||||||
|
$app.attr('style', `${$app.attr('style')} padding-top: ${menuHeight}px !important;`);
|
||||||
|
|
||||||
|
const rect = tabs.getBoundingClientRect();
|
||||||
|
const tabsTop = rect.top;
|
||||||
|
|
||||||
|
document.querySelector(':root').style.setProperty('--main-container-height', `${window.innerHeight - (tabsTop + footerHeight)}px`);
|
||||||
|
|
||||||
|
window.troubleshootSize = {
|
||||||
|
menuHeight,
|
||||||
|
footerHeight: footerHeight,
|
||||||
|
tabsTop,
|
||||||
|
WindowInnerHeight: window.innerHeight,
|
||||||
|
bodyHeight: window.innerHeight - (tabsTop + footerHeight),
|
||||||
|
'main-container-height': `${window.innerHeight - (tabsTop + footerHeight)}px`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
document.querySelector(':root').style.setProperty('--menu-top-height', `1px`);
|
||||||
|
|
||||||
|
const $app = $('.gradio-container.app');
|
||||||
|
$app.attr('style', `${$app.attr('style')} padding-top: ${menuHeight}px !important;`);
|
||||||
|
|
||||||
|
const rect = tabs.getBoundingClientRect();
|
||||||
|
const tabsTop = rect.top;
|
||||||
|
|
||||||
|
document.querySelector(':root').style.setProperty('--main-container-height', `${window.innerHeight - (tabsTop + footerHeight)}px`);
|
||||||
|
|
||||||
|
window.troubleshootSize = {
|
||||||
|
menuHeight,
|
||||||
|
footerHeight: footerHeight,
|
||||||
|
tabsTop,
|
||||||
|
WindowInnerHeight: window.innerHeight,
|
||||||
|
bodyHeight: window.innerHeight - (tabsTop + footerHeight),
|
||||||
|
'main-container-height': `${window.innerHeight - (tabsTop + footerHeight)}px`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const wrapDataGenerationInfo = ({prefix}) => {
|
export const wrapDataGenerationInfo = ({prefix}) => {
|
||||||
// Get the generation info container
|
// Get the generation info container
|
||||||
const previewBlocks = document.querySelectorAll(`#tab_${prefix} div#${prefix}_results > *:not(#${prefix}_results)`);
|
const previewBlocks = document.querySelectorAll(`#tab_${prefix} div#${prefix}_results > *:not(#${prefix}_results)`);
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,32 @@
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
|
||||||
"vite": "^4.3.2"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@chakra-ui/react": "^2.7.0",
|
||||||
"@fontsource-variable/caveat": "^5.0.0",
|
"@fontsource-variable/caveat": "^5.0.0",
|
||||||
|
"ace": "^1.3.0",
|
||||||
|
"ace-builds": "^1.22.0",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
"jquery": "^3.7.0",
|
"jquery": "^3.7.0",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-ace": "^10.1.0",
|
||||||
|
"react-colorful": "^5.6.1",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-select": "^5.7.3",
|
||||||
|
"react-spinners": "^0.13.8",
|
||||||
|
"react-use-websocket": "^3.0.0",
|
||||||
"showdown": "^2.1.0"
|
"showdown": "^2.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/plugin-syntax-import-assertions": "^7.20.0",
|
||||||
|
"@types/react": "^18.2.7",
|
||||||
|
"@types/react-dom": "^18.2.4",
|
||||||
|
"@vitejs/plugin-react": "^4.0.0",
|
||||||
|
"eslint": "^8.38.0",
|
||||||
|
"eslint-plugin-react": "^7.32.2",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
|
"eslint-plugin-react-refresh": "^0.3.4",
|
||||||
|
"typescript": "^5.0.4",
|
||||||
|
"vite": "^4.3.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
.App {
|
||||||
|
/*TODO calc this*/
|
||||||
|
width: max(800px, 50vw);
|
||||||
|
height: fit-content;
|
||||||
|
min-height: 150px;
|
||||||
|
z-index: 9998;
|
||||||
|
border: 1px solid var(--ae-input-border-color);
|
||||||
|
|
||||||
|
background-color: var(--block-background-fill);
|
||||||
|
font-weight: var(--body-text-weight);
|
||||||
|
font-size: var(--body-text-size);
|
||||||
|
color: var(--button-secondary-text-color);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.nevysha-btn-menu {
|
||||||
|
fill: var(--ae-primary-color);
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
border-top: none;
|
||||||
|
border-bottom-right-radius: var(--container-radius);
|
||||||
|
border-bottom-left-radius: var(--container-radius);
|
||||||
|
padding: var(--block-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-settings {
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
width: 60px;
|
||||||
|
height: 35px;
|
||||||
|
display: flex;
|
||||||
|
place-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
background: var(--ae-input-bg-color);
|
||||||
|
border: 1px solid var(--ae-input-border-color);
|
||||||
|
border-radius: 0;
|
||||||
|
color: var(--body-text-color);
|
||||||
|
font-size: var(--body-text-size);
|
||||||
|
}
|
||||||
|
.btn-settings:hover {
|
||||||
|
background: var(--ae-input-bg-color);
|
||||||
|
border: 1px solid var(--ae-input-border-color);
|
||||||
|
}
|
||||||
|
.btn-settings:active {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.btn-toolbar {
|
||||||
|
padding: 10px 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
}
|
||||||
|
.title > h2 {
|
||||||
|
font-size: 1.8em;
|
||||||
|
color: var(--ae-primary-color);
|
||||||
|
font-family: "Caveat Variable", sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.title > .subtitle {
|
||||||
|
font-size: 10px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swatch {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border: 1px solid var(--ae-input-border-color);
|
||||||
|
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), inset 0 0 0 1px rgba(0, 0, 0, 0.1);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popoverWrap {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 29;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
/*background-color: #1a1a1a;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.popover {
|
||||||
|
position: fixed;
|
||||||
|
border-radius: 9px;
|
||||||
|
z-index: 30;
|
||||||
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
}
|
||||||
|
|
||||||
|
.OutputFolderSelector {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nevysha-reporting a {
|
||||||
|
margin: 0 4px;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,405 @@
|
||||||
|
import React, {useEffect, useRef, useState} from "react";
|
||||||
|
import './App.css'
|
||||||
|
import {svg_magic_wand} from "../main/svg.js";
|
||||||
|
import {Header} from "./Header.jsx";
|
||||||
|
import {Column, Row, RowFullWidth} from "../main/Utils.jsx";
|
||||||
|
import {
|
||||||
|
Radio,
|
||||||
|
RadioGroup,
|
||||||
|
Tabs,
|
||||||
|
TabList,
|
||||||
|
TabPanels,
|
||||||
|
Tab,
|
||||||
|
TabPanel,
|
||||||
|
Checkbox,
|
||||||
|
Stack,
|
||||||
|
Input,
|
||||||
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
|
NumberInput,
|
||||||
|
NumberInputStepper,
|
||||||
|
NumberIncrementStepper,
|
||||||
|
NumberInputField,
|
||||||
|
NumberDecrementStepper,
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogBody,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogOverlay,
|
||||||
|
useDisclosure,
|
||||||
|
} from '@chakra-ui/react'
|
||||||
|
import {PopoverColorPicker} from "./PopoverColorPicker.jsx";
|
||||||
|
import {OuputFolderSelector} from "./OuputFolderSelector.jsx";
|
||||||
|
import {
|
||||||
|
applyAccentColor,
|
||||||
|
applyBgGradiantColor,
|
||||||
|
applyFontColor,
|
||||||
|
applyWavesColor,
|
||||||
|
applyDisabledWavesAndGradiant,
|
||||||
|
applyAccentForGenerate,
|
||||||
|
applyFontSize,
|
||||||
|
setCardHeight,
|
||||||
|
setCardWidth,
|
||||||
|
applyMenuPosition, setQuicksettingPosition, setSfwSettings, recalcOffsetFromMenuHeight
|
||||||
|
} from "../main/tweaks/various-tweaks.js";
|
||||||
|
import {getTheme} from "../main/cozy-utils.js";
|
||||||
|
import {WEBUI_A1111, WEBUI_SDNEXT} from "../main/Constants.js";
|
||||||
|
import {saveCozyNestConfig} from "../main/nevysha-cozy-nest.js";
|
||||||
|
import {ButtonWithConfirmDialog} from "../chakra/ButtonWithConfirmDialog.jsx";
|
||||||
|
|
||||||
|
|
||||||
|
function DialogWrapper({children, isVisible}) {
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure({isOpen: isVisible})
|
||||||
|
const cancelRef = useRef()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isVisible) {
|
||||||
|
onOpen()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
}, [isVisible])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AlertDialog
|
||||||
|
motionPreset='scale'
|
||||||
|
isOpen={isOpen}
|
||||||
|
leastDestructiveRef={cancelRef}
|
||||||
|
onClose={onClose}
|
||||||
|
isCentered
|
||||||
|
>
|
||||||
|
<AlertDialogOverlay>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogBody>
|
||||||
|
{children}
|
||||||
|
</AlertDialogBody>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialogOverlay>
|
||||||
|
</AlertDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nevyshaScrollbar = {
|
||||||
|
'&::-webkit-scrollbar': {
|
||||||
|
width: '5px',
|
||||||
|
},
|
||||||
|
'&::-webkit-scrollbar-track': {
|
||||||
|
backgroundColor: 'transparent'
|
||||||
|
},
|
||||||
|
'&::-webkit-scrollbar-thumb': {
|
||||||
|
backgroundColor: 'var(--ae-primary-color)',
|
||||||
|
borderRadius: '20px',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export function App() {
|
||||||
|
|
||||||
|
const [isVisible, setIsVisible] = useState(false)
|
||||||
|
|
||||||
|
const [config, setConfig] = useState(COZY_NEST_CONFIG)
|
||||||
|
|
||||||
|
function applySettings() {
|
||||||
|
applyWavesColor(config.waves_color);
|
||||||
|
applyFontColor(
|
||||||
|
getTheme() === "dark" ?
|
||||||
|
config.font_color :
|
||||||
|
config.font_color_light
|
||||||
|
)
|
||||||
|
applyBgGradiantColor(config.bg_gradiant_color);
|
||||||
|
applyDisabledWavesAndGradiant(config.disable_waves_and_gradiant);
|
||||||
|
applyAccentColor(config.accent_color, config.accent_color);
|
||||||
|
applyAccentForGenerate(config.accent_generate_button, config.accent_color);
|
||||||
|
applyFontSize(config.font_size)
|
||||||
|
setCardHeight(config.card_height)
|
||||||
|
setCardWidth(config.card_width)
|
||||||
|
applyMenuPosition(config.main_menu_position)
|
||||||
|
setQuicksettingPosition(config.quicksettings_position)
|
||||||
|
setSfwSettings(config.sfw_mode)
|
||||||
|
recalcOffsetFromMenuHeight()
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
applySettings();
|
||||||
|
}, [config])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setConfig(COZY_NEST_CONFIG)
|
||||||
|
}, [COZY_NEST_CONFIG])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
applySettings();
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const toggle = () => {
|
||||||
|
CozyLogger.debug('toggle')
|
||||||
|
setIsVisible(!isVisible)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateConfig = (e, what) => {
|
||||||
|
|
||||||
|
const newConfig = {...config}
|
||||||
|
if (e.target)
|
||||||
|
newConfig[what] = e.target.value
|
||||||
|
else
|
||||||
|
newConfig[what] = e
|
||||||
|
|
||||||
|
setConfig(newConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveConfig = () => {
|
||||||
|
(async () => await saveCozyNestConfig(config))()
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetConfig = () => {
|
||||||
|
(async () => {
|
||||||
|
// call to @app.delete("/cozy-nest/config")
|
||||||
|
await fetch(`/cozy-nest/config`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
})
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
const reloadUi = () => {
|
||||||
|
|
||||||
|
if (config.webui === WEBUI_A1111) {
|
||||||
|
document.querySelector('#settings_restart_gradio').click();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (config.webui === WEBUI_SDNEXT) {
|
||||||
|
document.querySelector('#restart_submit').click();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
// call to @app.get("/cozy-nest/reloadui")
|
||||||
|
await fetch(`/cozy-nest/reloadui`)
|
||||||
|
//reload the page
|
||||||
|
window.location.reload()
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{display: 'flex'}}>
|
||||||
|
<button className="nevysha-btn-menu"
|
||||||
|
id="nevyui_sh_options"
|
||||||
|
title="Nevysha Cozy Nest Settings"
|
||||||
|
dangerouslySetInnerHTML={{__html:svg_magic_wand}}
|
||||||
|
onClick={toggle}
|
||||||
|
/>
|
||||||
|
{ isVisible &&
|
||||||
|
<DialogWrapper isVisible={isVisible}>
|
||||||
|
<div className="App nevysha">
|
||||||
|
<Header onClickClose={() => setIsVisible(false)}/>
|
||||||
|
|
||||||
|
<div className="container">
|
||||||
|
<Tabs variant='nevysha'>
|
||||||
|
<TabList style={{backgroundColor: 'var(--tab-nav-background-color)'}}>
|
||||||
|
<Tab>Main Settings</Tab>
|
||||||
|
<Tab>Image Browser Settings</Tab>
|
||||||
|
<Tab>Others</Tab>
|
||||||
|
</TabList>
|
||||||
|
|
||||||
|
<TabPanels>
|
||||||
|
<TabPanel css={nevyshaScrollbar}>
|
||||||
|
<RowFullWidth>
|
||||||
|
<Checkbox
|
||||||
|
isChecked={config.error_popup}
|
||||||
|
onChange={(e) => setConfig({...config, error_popup: e.target.checked})}
|
||||||
|
>Display information dialog on Cozy Nest error</Checkbox>
|
||||||
|
<Checkbox
|
||||||
|
isChecked={config.disable_waves_and_gradiant}
|
||||||
|
onChange={(e) => setConfig({...config, disable_waves_and_gradiant: e.target.checked})}
|
||||||
|
>Disable waves and gradiant background animations</Checkbox>
|
||||||
|
</RowFullWidth>
|
||||||
|
<Column>
|
||||||
|
<label>Main menu position</label>
|
||||||
|
<RadioGroup
|
||||||
|
value={config.main_menu_position}
|
||||||
|
onChange={(value) => setConfig({...config, main_menu_position: value})}
|
||||||
|
>
|
||||||
|
<Stack direction='row'>
|
||||||
|
<Radio value='left'>left</Radio>
|
||||||
|
<Radio value='top'>top</Radio>
|
||||||
|
<Radio value='top_centered'>top centered</Radio>
|
||||||
|
</Stack>
|
||||||
|
</RadioGroup>
|
||||||
|
</Column>
|
||||||
|
<Column>
|
||||||
|
<label>Quicksettings position</label>
|
||||||
|
<RadioGroup
|
||||||
|
value={config.quicksettings_position}
|
||||||
|
onChange={(value) => setConfig({...config, quicksettings_position: value})}
|
||||||
|
>
|
||||||
|
<Stack direction='row'>
|
||||||
|
<Radio value='left'>left</Radio>
|
||||||
|
<Radio value='split'>split</Radio>
|
||||||
|
<Radio value='centered'>centered</Radio>
|
||||||
|
</Stack>
|
||||||
|
</RadioGroup>
|
||||||
|
</Column>
|
||||||
|
<Row>
|
||||||
|
<Checkbox
|
||||||
|
isChecked={config.accent_generate_button}
|
||||||
|
onChange={(e) => setConfig({...config, accent_generate_button: e.target.checked})}
|
||||||
|
>Accent Generate Button</Checkbox>
|
||||||
|
</Row>
|
||||||
|
<RowFullWidth>
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Font size</FormLabel>
|
||||||
|
<NumberInput defaultValue={12} min={10} max={18}>
|
||||||
|
<NumberInputField/>
|
||||||
|
<NumberInputStepper>
|
||||||
|
<NumberIncrementStepper/>
|
||||||
|
<NumberDecrementStepper/>
|
||||||
|
</NumberInputStepper>
|
||||||
|
</NumberInput>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Extra network card height</FormLabel>
|
||||||
|
<NumberInput
|
||||||
|
value={config.card_height}
|
||||||
|
onChange={(e) => updateConfig(e, 'card_height')}
|
||||||
|
min={5} max={20}
|
||||||
|
>
|
||||||
|
<NumberInputField/>
|
||||||
|
<NumberInputStepper>
|
||||||
|
<NumberIncrementStepper/>
|
||||||
|
<NumberDecrementStepper/>
|
||||||
|
</NumberInputStepper>
|
||||||
|
</NumberInput>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Extra network card width</FormLabel>
|
||||||
|
<NumberInput
|
||||||
|
value={config.card_width}
|
||||||
|
onChange={(e) => updateConfig(e, 'card_width')}
|
||||||
|
min={5} max={20}
|
||||||
|
>
|
||||||
|
<NumberInputField/>
|
||||||
|
<NumberInputStepper>
|
||||||
|
<NumberIncrementStepper/>
|
||||||
|
<NumberDecrementStepper/>
|
||||||
|
</NumberInputStepper>
|
||||||
|
</NumberInput>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
</RowFullWidth>
|
||||||
|
<RowFullWidth>
|
||||||
|
<PopoverColorPicker
|
||||||
|
label="Font Color"
|
||||||
|
color={config.font_color}
|
||||||
|
onChange={(e) => updateConfig(e, 'font_color')} />
|
||||||
|
<PopoverColorPicker
|
||||||
|
label="Font Color"
|
||||||
|
color={config.font_color_light}
|
||||||
|
onChange={(e) => updateConfig(e, 'font_color_light')} />
|
||||||
|
<PopoverColorPicker
|
||||||
|
label="Waves Color"
|
||||||
|
color={config.waves_color}
|
||||||
|
onChange={(e) => updateConfig(e, 'waves_color')} />
|
||||||
|
<PopoverColorPicker
|
||||||
|
label="Background gradiant Color"
|
||||||
|
color={config.bg_gradiant_color}
|
||||||
|
onChange={(e) => updateConfig(e, 'bg_gradiant_color')} />
|
||||||
|
<PopoverColorPicker
|
||||||
|
label="Accent Color"
|
||||||
|
color={config.accent_color}
|
||||||
|
onChange={(e) => updateConfig(e, 'accent_color')} />
|
||||||
|
</RowFullWidth>
|
||||||
|
<RowFullWidth>
|
||||||
|
<Checkbox
|
||||||
|
isChecked={config.sfw_mode}
|
||||||
|
onChange={(e) => setConfig({...config, sfw_mode: e.target.checked})}
|
||||||
|
>SFW mode 👀 (blur all images)</Checkbox>
|
||||||
|
</RowFullWidth>
|
||||||
|
</TabPanel>
|
||||||
|
|
||||||
|
<TabPanel css={nevyshaScrollbar}>
|
||||||
|
<RowFullWidth>
|
||||||
|
<Checkbox
|
||||||
|
isChecked={config.disable_image_browser}
|
||||||
|
onChange={(e) => setConfig({...config, disable_image_browser: e.target.checked})}
|
||||||
|
>Disable image browser (Reload UI required)</Checkbox>
|
||||||
|
</RowFullWidth>
|
||||||
|
<RowFullWidth>
|
||||||
|
<FormControl style={{width: "30%"}}>
|
||||||
|
<FormLabel>Socket port for image browser</FormLabel>
|
||||||
|
<Input
|
||||||
|
placeholder='3333'
|
||||||
|
value={config.server_default_port}
|
||||||
|
onChange={(e) => updateConfig(e, 'server_default_port')}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<Checkbox
|
||||||
|
>Auto search port</Checkbox>
|
||||||
|
<Checkbox
|
||||||
|
visibility={config.webui === WEBUI_SDNEXT ? 'hidden' : 'visible'}
|
||||||
|
isChecked={config.fetch_output_folder_from_a1111_settings}
|
||||||
|
onChange={(e) => setConfig({...config, fetch_output_folder_from_a1111_settings: e.target.checked})}
|
||||||
|
>Fetch output folder from a1111 settings (Reload needed to enable)</Checkbox>
|
||||||
|
</RowFullWidth>
|
||||||
|
<Column>
|
||||||
|
<FormLabel>Archive path</FormLabel>
|
||||||
|
<Input
|
||||||
|
placeholder='C:/stable-difusion/...'
|
||||||
|
value={config.archive_path}
|
||||||
|
onChange={(e) => updateConfig(e, 'archive_path')}
|
||||||
|
/>
|
||||||
|
</Column>
|
||||||
|
<Column>
|
||||||
|
<FormLabel>Output path</FormLabel>
|
||||||
|
<OuputFolderSelector config={config} setConfig={setConfig}/>
|
||||||
|
</Column>
|
||||||
|
</TabPanel>
|
||||||
|
|
||||||
|
<TabPanel css={nevyshaScrollbar}>
|
||||||
|
<p>Those settings are heavy on DOM modification and might conflict with some others extensions</p>
|
||||||
|
<p>Reload UI needed to apply</p>
|
||||||
|
<Column>
|
||||||
|
<Checkbox
|
||||||
|
isChecked={config.enable_clear_button}
|
||||||
|
onChange={(e) => setConfig({...config, enable_clear_button: e.target.checked})}
|
||||||
|
>Enable clear gallery button in txt2img and img2img tabs</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>
|
||||||
|
</Column>
|
||||||
|
</TabPanel>
|
||||||
|
</TabPanels>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
<RowFullWidth className="btn-toolbar" style={{gap: '25px', padding: '15px'}}>
|
||||||
|
<button
|
||||||
|
className="btn-settings"
|
||||||
|
style={{width: '100%'}}
|
||||||
|
onClick={saveConfig}
|
||||||
|
>Save</button>
|
||||||
|
<ButtonWithConfirmDialog
|
||||||
|
message="Are you sure you want to reset all settings ? This will trigger a UI Reload"
|
||||||
|
confirmLabel="Reset"
|
||||||
|
cancelLabel="Cancel"
|
||||||
|
onConfirm={resetConfig}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="btn-settings"
|
||||||
|
style={{width: '100%'}}
|
||||||
|
onClick={reloadUi}
|
||||||
|
>Reload UI</button>
|
||||||
|
</RowFullWidth>
|
||||||
|
|
||||||
|
<div>Made by Nevysha with luv</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogWrapper>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import React from "react";
|
||||||
|
import {Row} from "../main/Utils.jsx";
|
||||||
|
import './App.css'
|
||||||
|
|
||||||
|
export function Header (props) {
|
||||||
|
|
||||||
|
const gatherInfoAndShowDialog = () => {
|
||||||
|
window.gatherInfoAndShowDialog()
|
||||||
|
props.onClickClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="nevysha header">
|
||||||
|
<div className="nevysha title">
|
||||||
|
<h2>Nevysha's Cozy Nest</h2>
|
||||||
|
<span className="subtitle">Find your cozy spot on Auto1111's webui</span>
|
||||||
|
</div>
|
||||||
|
<Row>
|
||||||
|
<div className="btn-settings" onClick={props.onClickClose}>Close</div>
|
||||||
|
</Row>
|
||||||
|
<div className="container">
|
||||||
|
<div className="nevysha settings-nevyui-top"><p className="nevysha-reporting">Found a bug or want to ask for
|
||||||
|
a feature ? Please <a onClick={gatherInfoAndShowDialog} target="_blank">click
|
||||||
|
here to gather relevant info</a> then use <a href="https://www.reddit.com/r/NevyshaCozyNest/"
|
||||||
|
target="_blank">this subreddit</a> or <a
|
||||||
|
href="https://github.com/Nevysha/Cozy-Nest" target="_blank">github</a>. You can also join this <a
|
||||||
|
href="https://discord.gg/yppzDXjT7S" target="_blank">discord server</a> to discuss about Cozy Nest
|
||||||
|
</p><p className="nevysha-emphasis">WARNING : Some visual settings are immediately applied but will not be
|
||||||
|
saved until you click "Save"</p></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
import React, {useEffect, useState} from "react";
|
||||||
|
import {Input, InputGroup, InputRightElement} from "@chakra-ui/react";
|
||||||
|
|
||||||
|
|
||||||
|
export function OuputFolderSelector({config, setConfig}) {
|
||||||
|
|
||||||
|
const [outputFolder, setOutputFolder] = useState(config.cnib_output_folder)
|
||||||
|
|
||||||
|
const [newOutputFolder, setNewOutputFolder] = useState('')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOutputFolder(config.cnib_output_folder)
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
|
const addNewOutputFolder = () => {
|
||||||
|
if (newOutputFolder === '') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const newOutputFolderArray = [...outputFolder]
|
||||||
|
newOutputFolderArray.push(newOutputFolder)
|
||||||
|
setOutputFolder(newOutputFolderArray)
|
||||||
|
setConfig({...config, cnib_output_folder: newOutputFolderArray})
|
||||||
|
setNewOutputFolder('')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="OutputFolderSelector">
|
||||||
|
{outputFolder.map((folder, index) => {
|
||||||
|
return (
|
||||||
|
<InputGroup key={index}>
|
||||||
|
<Input
|
||||||
|
placeholder="C:/stable-difusion/..."
|
||||||
|
value={folder}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newOutputFolder = [...outputFolder]
|
||||||
|
newOutputFolder[index] = e.target.value
|
||||||
|
setOutputFolder(newOutputFolder)
|
||||||
|
setConfig({...config, cnib_output_folder: newOutputFolder})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<InputRightElement width='4.5rem'>
|
||||||
|
<button
|
||||||
|
className="btn-settings"
|
||||||
|
onClick={() => {
|
||||||
|
const newOutputFolder = [...outputFolder]
|
||||||
|
newOutputFolder.splice(index, 1)
|
||||||
|
setOutputFolder(newOutputFolder)
|
||||||
|
setConfig({...config, cnib_output_folder: newOutputFolder})
|
||||||
|
}}
|
||||||
|
>Delete</button>
|
||||||
|
</InputRightElement>
|
||||||
|
</InputGroup>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
<InputGroup>
|
||||||
|
<Input
|
||||||
|
placeholder="Add a new folder..."
|
||||||
|
value={newOutputFolder}
|
||||||
|
onChange={(e) => {
|
||||||
|
setNewOutputFolder(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<InputRightElement width='4.5rem'>
|
||||||
|
<button className="btn-settings" onClick={addNewOutputFolder}>Add</button>
|
||||||
|
</InputRightElement>
|
||||||
|
</InputGroup>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import React, {useCallback, useEffect, useRef, useState} from "react";
|
||||||
|
import { HexColorPicker } from "react-colorful";
|
||||||
|
|
||||||
|
export const PopoverColorPicker = ({ color, onChange, label }) => {
|
||||||
|
const popover = useRef();
|
||||||
|
const [isOpen, toggle] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<label>{label}</label>
|
||||||
|
<div className="picker">
|
||||||
|
<div
|
||||||
|
className="swatch"
|
||||||
|
style={{ backgroundColor: color }}
|
||||||
|
onClick={() => toggle(true)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{isOpen && (
|
||||||
|
<>
|
||||||
|
<div className="popoverWrap" onClick={() => toggle(false)} />
|
||||||
|
<div className="popover" ref={popover}>
|
||||||
|
<HexColorPicker color={color} onChange={onChange} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import React from "react";
|
||||||
|
import ReactDOM from 'react-dom/client'
|
||||||
|
import {App} from "./App.jsx";
|
||||||
|
import {ChakraProvider} from '@chakra-ui/react'
|
||||||
|
import {theme} from "../chakra/chakra-theme.ts";
|
||||||
|
|
||||||
|
const containerId = 'cozy_nest_options';
|
||||||
|
|
||||||
|
export default function startCozyNestSettings() {
|
||||||
|
if (!document.getElementById('nevysha-btn-menu-wrapper')) {
|
||||||
|
setTimeout(() => startCozyNestSettings(), 200)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//add a div to hold settings
|
||||||
|
const settingsDiv = document.createElement("div");
|
||||||
|
settingsDiv.id = containerId;
|
||||||
|
settingsDiv.style = 'display: flex;'
|
||||||
|
// insert settingsDiv before the first child of '#nevysha-btn-menu-wrapper'
|
||||||
|
document.getElementById('nevysha-btn-menu-wrapper').insertBefore(settingsDiv, document.getElementById('nevysha-btn-menu-wrapper').firstChild);
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById(containerId)).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<ChakraProvider theme={theme} >
|
||||||
|
<App />
|
||||||
|
</ChakraProvider >
|
||||||
|
</React.StrictMode>,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
// Improved version of https://usehooks.com/useOnClickOutside/
|
||||||
|
const useClickOutside = (ref, handler) => {
|
||||||
|
useEffect(() => {
|
||||||
|
let startedInside = false;
|
||||||
|
let startedWhenMounted = false;
|
||||||
|
|
||||||
|
const listener = (event) => {
|
||||||
|
// Do nothing if `mousedown` or `touchstart` started inside ref element
|
||||||
|
if (startedInside || !startedWhenMounted) return;
|
||||||
|
// Do nothing if clicking ref's element or descendent elements
|
||||||
|
if (!ref.current || ref.current.contains(event.target)) return;
|
||||||
|
|
||||||
|
handler(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateEventStart = (event) => {
|
||||||
|
startedWhenMounted = ref.current;
|
||||||
|
startedInside = ref.current && ref.current.contains(event.target);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("mousedown", validateEventStart);
|
||||||
|
document.addEventListener("touchstart", validateEventStart);
|
||||||
|
document.addEventListener("click", listener);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("mousedown", validateEventStart);
|
||||||
|
document.removeEventListener("touchstart", validateEventStart);
|
||||||
|
document.removeEventListener("click", listener);
|
||||||
|
};
|
||||||
|
}, [ref, handler]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useClickOutside;
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"lib": ["DOM", "DOM.Iterable", "ESNext", "ES6"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["main", "image-browser"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["DOM", "DOM.Iterable", "ESNext", "ES6"],
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"allowImportingTsExtensions": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
import { defineConfig } from 'vite'
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [
|
|
||||||
{
|
|
||||||
name: "configure-response-headers",
|
|
||||||
configureServer: (server) => {
|
|
||||||
server.middlewares.use((_req, res, next) => {
|
|
||||||
res.setHeader("Cross-Origin-Embedder-Policy", "unsafe-none");
|
|
||||||
res.setHeader("Cross-Origin-Opener-Policy", "unsafe-non");
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'route-default-to-index',
|
|
||||||
configureServer: (server) => {
|
|
||||||
server.middlewares.use(async (_req, res, next) => {
|
|
||||||
if (_req.originalUrl === '/cozy-nest-client'
|
|
||||||
|| _req.originalUrl === '/cozy-nest-client?__theme=dark'
|
|
||||||
|| _req.originalUrl === '/cozy-nest-client?__theme=light') {
|
|
||||||
|
|
||||||
let updatedResponse =await (await fetch('http://127.0.0.1:7860/')).text()
|
|
||||||
|
|
||||||
// replace </body> with </body><script type="module" src="/main.js"></script>
|
|
||||||
updatedResponse = updatedResponse.replace('</body>', '</body><script type="module" src="/cozy-nest-client/main.js"></script>')
|
|
||||||
|
|
||||||
// Set the modified response
|
|
||||||
res.statusCode = 200;
|
|
||||||
res.setHeader('Content-Type', 'text/html');
|
|
||||||
res.setHeader('charset', 'utf-8');
|
|
||||||
res.end(updatedResponse);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Continue to the next middleware
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
build: {
|
|
||||||
outDir: '../client',
|
|
||||||
rollupOptions: {
|
|
||||||
output: {
|
|
||||||
entryFileNames: `assets/[name].js`,
|
|
||||||
chunkFileNames: `assets/[name].js`,
|
|
||||||
assetFileNames: `assets/[name].[ext]`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
server: {
|
|
||||||
host: '127.0.0.1',
|
|
||||||
port: 5173,
|
|
||||||
proxy: {
|
|
||||||
'/queue/join': {
|
|
||||||
target: 'ws://127.0.0.1:7860',
|
|
||||||
ws: true,
|
|
||||||
},
|
|
||||||
//route everything except /cozy-nest-client/ to localhost:7860
|
|
||||||
'^(?!.*cozy-nest-client).*$': 'http://127.0.0.1:7860',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
base: 'cozy-nest-client'
|
|
||||||
})
|
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@image-browser': './image-browser/',
|
||||||
|
'@settings': './settings/',
|
||||||
|
'@main': './main/'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
react({
|
||||||
|
babel: {
|
||||||
|
plugins: ['@babel/plugin-syntax-import-assertions'],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: "configure-response-headers",
|
||||||
|
configureServer: (server) => {
|
||||||
|
server.middlewares.use((_req, res, next) => {
|
||||||
|
res.setHeader("Cross-Origin-Embedder-Policy", "unsafe-none");
|
||||||
|
res.setHeader("Cross-Origin-Opener-Policy", "unsafe-non");
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'route-default-to-index',
|
||||||
|
configureServer: (server) => {
|
||||||
|
server.middlewares.use(
|
||||||
|
async (_req, res, next): Promise<void> => {
|
||||||
|
if (_req.originalUrl === '/cozy-nest-client'
|
||||||
|
|| _req.originalUrl === '/cozy-nest-client?__theme=dark'
|
||||||
|
|| _req.originalUrl === '/cozy-nest-client?__theme=light') {
|
||||||
|
|
||||||
|
let updatedResponse =await (await fetch('http://127.0.0.1:7860/')).text()
|
||||||
|
|
||||||
|
const toAdd = `
|
||||||
|
<script type="module" src="/cozy-nest-client/main/_dev.js"></script>
|
||||||
|
<script type="module" src="/cozy-nest-client/main.jsx"></script>
|
||||||
|
`
|
||||||
|
|
||||||
|
// replace </body> with </body><script type="module" src="/main.js"></script>
|
||||||
|
updatedResponse = updatedResponse.replace('</body>', `</body>${toAdd}`)
|
||||||
|
|
||||||
|
// Set the modified response
|
||||||
|
res.statusCode = 200;
|
||||||
|
res.setHeader('Content-Type', 'text/html');
|
||||||
|
res.setHeader('charset', 'utf-8');
|
||||||
|
res.end(updatedResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue to the next middleware
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
build: {
|
||||||
|
outDir: '../client',
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
entryFileNames: `assets/[name].js`,
|
||||||
|
chunkFileNames: `assets/[name].js`,
|
||||||
|
assetFileNames: `assets/[name].[ext]`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
host: '127.0.0.1',
|
||||||
|
port: 5173,
|
||||||
|
proxy: {
|
||||||
|
'/queue/join': {
|
||||||
|
target: 'ws://127.0.0.1:7860',
|
||||||
|
ws: true,
|
||||||
|
},
|
||||||
|
'http://127.0.0.1:5173/theme-cozy-json.js': 'http://127.0.0.1:5173/cozy-nest-client/image-browser/src/editor/theme-cozy-json.js',
|
||||||
|
//route everything except /cozy-nest-client/ to localhost:7860
|
||||||
|
'^(?!.*cozy-nest-client).*$': 'http://127.0.0.1:7860',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
base: 'cozy-nest-client'
|
||||||
|
})
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 4.0 KiB |
|
|
@ -12,25 +12,7 @@ import modules.images
|
||||||
import websockets
|
import websockets
|
||||||
from websockets.server import serve
|
from websockets.server import serve
|
||||||
|
|
||||||
|
from scripts import tools
|
||||||
def get_exif(path):
|
|
||||||
allExif = {}
|
|
||||||
try:
|
|
||||||
image = Image.open(path)
|
|
||||||
# info = image.info
|
|
||||||
(_, allExif, allExif_html) = modules.extras.run_pnginfo(image)
|
|
||||||
image.close()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"CozyNestSocket: WARNING cannot get exif data for image {path}")
|
|
||||||
pass
|
|
||||||
img = {
|
|
||||||
'path': path,
|
|
||||||
'metadata': {
|
|
||||||
'date': os.path.getmtime(path),
|
|
||||||
'exif': allExif,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return img
|
|
||||||
|
|
||||||
|
|
||||||
async def start_server(images_folders, server_port, stopper):
|
async def start_server(images_folders, server_port, stopper):
|
||||||
|
|
@ -73,24 +55,7 @@ async def start_server(images_folders, server_port, stopper):
|
||||||
async def process(data):
|
async def process(data):
|
||||||
what = data['what']
|
what = data['what']
|
||||||
if what == 'images':
|
if what == 'images':
|
||||||
# scrape the images folder recursively
|
data = tools.scrap_image_folders(images_folders)
|
||||||
images = []
|
|
||||||
for images_folder in images_folders:
|
|
||||||
for root, dirs, files in os.walk(images_folder):
|
|
||||||
for file in files:
|
|
||||||
if file.endswith(".png"):
|
|
||||||
# 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
|
|
||||||
}
|
|
||||||
return json.dumps(data)
|
return json.dumps(data)
|
||||||
|
|
||||||
if what == 'image_saved':
|
if what == 'image_saved':
|
||||||
|
|
@ -100,6 +65,13 @@ async def start_server(images_folders, server_port, stopper):
|
||||||
'data': 'None'
|
'data': 'None'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if what == 'index_built':
|
||||||
|
await on_index_built(data['data'])
|
||||||
|
return json.dumps({
|
||||||
|
'what': 'success',
|
||||||
|
'data': 'None'
|
||||||
|
})
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print(f"CozyNestSocket: Unknown data: {data}")
|
print(f"CozyNestSocket: Unknown data: {data}")
|
||||||
return json.dumps({
|
return json.dumps({
|
||||||
|
|
@ -116,6 +88,14 @@ async def start_server(images_folders, server_port, stopper):
|
||||||
for websocket in CLIENTS_COPY.copy():
|
for websocket in CLIENTS_COPY.copy():
|
||||||
await websocket_send('dispatch_on_image_saved', data, websocket)
|
await websocket_send('dispatch_on_image_saved', data, websocket)
|
||||||
|
|
||||||
|
async def on_index_built(data):
|
||||||
|
|
||||||
|
CLIENTS_COPY = CLIENTS.copy()
|
||||||
|
CLIENTS.clear()
|
||||||
|
|
||||||
|
for websocket in CLIENTS_COPY.copy():
|
||||||
|
await websocket_send('dispatch_on_index_built', data, websocket)
|
||||||
|
|
||||||
async def websocket_send(what, data, websocket):
|
async def websocket_send(what, data, websocket):
|
||||||
try:
|
try:
|
||||||
await websocket.send(json.dumps({
|
await websocket.send(json.dumps({
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,16 @@ import threading
|
||||||
|
|
||||||
import gradio as gr
|
import gradio as gr
|
||||||
import modules
|
import modules
|
||||||
|
from PIL import Image
|
||||||
|
from PIL.PngImagePlugin import PngInfo
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from fastapi import FastAPI, Response, Request
|
from fastapi import FastAPI, Response, Request
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
import websockets
|
import websockets
|
||||||
from modules import script_callbacks, shared, call_queue, scripts
|
from modules import script_callbacks, shared, call_queue, scripts
|
||||||
|
|
||||||
from scripts.cozynest_image_browser import start_server, get_exif
|
from scripts import tools
|
||||||
|
from scripts.cozynest_image_browser import start_server
|
||||||
|
|
||||||
|
|
||||||
def rgb_to_hex(r, g, b):
|
def rgb_to_hex(r, g, b):
|
||||||
|
|
@ -55,6 +58,7 @@ def gradio_save_settings(main_menu_position,
|
||||||
auto_search_port,
|
auto_search_port,
|
||||||
auto_start_server,
|
auto_start_server,
|
||||||
fetch_output_folder_from_a1111_settings,
|
fetch_output_folder_from_a1111_settings,
|
||||||
|
archive_path,
|
||||||
sfw_mode,
|
sfw_mode,
|
||||||
enable_clear_button,
|
enable_clear_button,
|
||||||
enable_extra_network_tweaks,
|
enable_extra_network_tweaks,
|
||||||
|
|
@ -81,6 +85,7 @@ def gradio_save_settings(main_menu_position,
|
||||||
'sfw_mode': sfw_mode,
|
'sfw_mode': sfw_mode,
|
||||||
'enable_clear_button': enable_clear_button,
|
'enable_clear_button': enable_clear_button,
|
||||||
'enable_extra_network_tweaks': enable_extra_network_tweaks,
|
'enable_extra_network_tweaks': enable_extra_network_tweaks,
|
||||||
|
'archive_path': archive_path,
|
||||||
}
|
}
|
||||||
|
|
||||||
current_config = get_dict_from_config()
|
current_config = get_dict_from_config()
|
||||||
|
|
@ -131,11 +136,13 @@ def get_default_settings():
|
||||||
'server_default_port': 3333,
|
'server_default_port': 3333,
|
||||||
'auto_search_port': True,
|
'auto_search_port': True,
|
||||||
'auto_start_server': True,
|
'auto_start_server': True,
|
||||||
'fetch_output_folder_from_a1111_settings': True,
|
'fetch_output_folder_from_a1111_settings': False,
|
||||||
'cnib_output_folder': [],
|
'cnib_output_folder': [],
|
||||||
|
'archive_path': '',
|
||||||
'sfw_mode': False,
|
'sfw_mode': False,
|
||||||
'enable_clear_button': True,
|
'enable_clear_button': True,
|
||||||
'enable_extra_network_tweaks': True,
|
'enable_extra_network_tweaks': True,
|
||||||
|
'enable_cozy_prompt': True,
|
||||||
'webui': 'unknown'
|
'webui': 'unknown'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -240,186 +247,6 @@ def start_server_in_dedicated_process(_images_folders, server_port):
|
||||||
script_callbacks.on_before_reload(stop_server)
|
script_callbacks.on_before_reload(stop_server)
|
||||||
|
|
||||||
|
|
||||||
def gradio_img_browser_tab(config):
|
|
||||||
with gr.Column(elem_id="img_browser_main_block"):
|
|
||||||
# disable_image_browser
|
|
||||||
disable_image_browser = gr.Checkbox(value=config.get('disable_image_browser'),
|
|
||||||
label="Disable image browser (Reload UI required)",
|
|
||||||
elem_id="setting_nevyui_disableImageBrowser", interactive=True)
|
|
||||||
|
|
||||||
with gr.Row():
|
|
||||||
server_default_port = gr.Number(value=config.get('server_default_port'),
|
|
||||||
label="Socket port for image browser", interactive=True, precision=0)
|
|
||||||
auto_search_port = gr.Checkbox(value=True, label="Auto search port",
|
|
||||||
elem_id="setting_nevyui_autoSearchPort",
|
|
||||||
interactive=True)
|
|
||||||
|
|
||||||
auto_start_server = gr.Checkbox(value=config.get('auto_start_server'), label="Auto start server",
|
|
||||||
elem_id="setting_nevyui_autoStartServer",
|
|
||||||
interactive=True, visible=False)
|
|
||||||
|
|
||||||
fetch_output_folder_from_a1111_settings = gr.Checkbox(
|
|
||||||
value=config.get('fetch_output_folder_from_a1111_settings'),
|
|
||||||
label="Fetch output folder from a1111 settings (Reload needed to enable)",
|
|
||||||
elem_id="setting_nevyui_fetchOutputFolderFromA1111Settings",
|
|
||||||
interactive=True)
|
|
||||||
|
|
||||||
# Add a text block to display each folder from output_folder_array()
|
|
||||||
with gr.Blocks(elem_id="img_browser_folders_block"):
|
|
||||||
# TODO refactor to remove this as it's no longer managed through gradio
|
|
||||||
gr.Textbox(value=json.dumps(config.get('cnib_output_folder')), label="Output folder",
|
|
||||||
elem_id="cnib_output_folder", interactive=True, visible=False)
|
|
||||||
|
|
||||||
return [
|
|
||||||
disable_image_browser,
|
|
||||||
server_default_port,
|
|
||||||
auto_search_port,
|
|
||||||
auto_start_server,
|
|
||||||
fetch_output_folder_from_a1111_settings]
|
|
||||||
|
|
||||||
|
|
||||||
def gradio_main_tab(config):
|
|
||||||
with gr.Column(elem_id="nevyui-ui-block"):
|
|
||||||
with gr.Row():
|
|
||||||
# error popup checkbox
|
|
||||||
error_popup = gr.Checkbox(value=config.get('error_popup'),
|
|
||||||
label="Display information dialog on Cozy Nest error",
|
|
||||||
elem_id="setting_nevyui_errorPopup", interactive=True)
|
|
||||||
|
|
||||||
# disable waves and gradiant bg
|
|
||||||
disable_waves_and_gradiant = gr.Checkbox(value=config.get('disable_waves_and_gradiant'),
|
|
||||||
label="Disable waves and gradiant background animations",
|
|
||||||
elem_id="setting_nevyui_disableWavesAndGradiant", interactive=True)
|
|
||||||
|
|
||||||
# main menu
|
|
||||||
main_menu_position = gr.Radio(value=config.get('main_menu_position'), label="Main menu position",
|
|
||||||
choices=['left', 'top', 'top_centered'],
|
|
||||||
elem_id="setting_nevyui_menuPosition", interactive=True)
|
|
||||||
quicksettings_position = gr.Radio(value=config.get('quicksettings_position'),
|
|
||||||
label="Quicksettings position",
|
|
||||||
choices=['left', 'split', 'centered'],
|
|
||||||
elem_id="setting_nevyui_quicksettingsPosition", interactive=True)
|
|
||||||
accent_generate_button = gr.Checkbox(value=config.get('accent_generate_button'),
|
|
||||||
label="Accent Generate Button",
|
|
||||||
elem_id="setting_nevyui_accentGenerateButton", interactive=True)
|
|
||||||
|
|
||||||
with gr.Row():
|
|
||||||
font_size = gr.Slider(value=config.get('font_size'), label="Font size", minimum=10, maximum=18, step=1,
|
|
||||||
elem_id="setting_nevyui_fontSize", interactive=True)
|
|
||||||
card_height = gr.Slider(value=config.get('card_height'), label="Extra network card height", minimum=5,
|
|
||||||
maximum=20, step=1, elem_id="setting_nevyui_cardHeight", interactive=True)
|
|
||||||
card_width = gr.Slider(value=config.get('card_width'), label="Extra network card width", minimum=5,
|
|
||||||
maximum=20, step=1, elem_id="setting_nevyui_cardWidth", interactive=True)
|
|
||||||
|
|
||||||
with gr.Row():
|
|
||||||
font_color = gr.ColorPicker(value=config.get('font_color'), label="Font color",
|
|
||||||
elem_id="setting_nevyui_fontColor", interactive=True, visible=False)
|
|
||||||
|
|
||||||
font_color_light = gr.ColorPicker(value=config.get('font_color_light'), label="Font color",
|
|
||||||
elem_id="setting_nevyui_fontColorLight", interactive=True, visible=False)
|
|
||||||
|
|
||||||
waves_color = gr.ColorPicker(value=config.get('waves_color'), label="Waves color",
|
|
||||||
elem_id="setting_nevyui_waveColor", interactive=True)
|
|
||||||
bg_gradiant_color = gr.ColorPicker(value=config.get('bg_gradiant_color'),
|
|
||||||
label="Background gradiant color",
|
|
||||||
elem_id="setting_nevyui_bgGradiantColor", interactive=True)
|
|
||||||
accent_color = gr.ColorPicker(value=config.get('accent_color'), label="Accent color",
|
|
||||||
elem_id="setting_nevyui_accentColor", interactive=True)
|
|
||||||
|
|
||||||
sfw_mode = gr.Checkbox(value=config.get('sfw_mode'),
|
|
||||||
label="SFW mode 👀 (blur all images)",
|
|
||||||
elem_id="setting_nevyui_sfwMode", interactive=True)
|
|
||||||
|
|
||||||
return [
|
|
||||||
accent_color,
|
|
||||||
accent_generate_button,
|
|
||||||
bg_gradiant_color,
|
|
||||||
card_height,
|
|
||||||
card_width,
|
|
||||||
disable_waves_and_gradiant,
|
|
||||||
error_popup,
|
|
||||||
font_size,
|
|
||||||
main_menu_position,
|
|
||||||
quicksettings_position,
|
|
||||||
font_color,
|
|
||||||
font_color_light,
|
|
||||||
waves_color,
|
|
||||||
sfw_mode,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def ui_action_btn(accent_color, accent_generate_button, bg_gradiant_color, card_height, card_width,
|
|
||||||
disable_waves_and_gradiant, error_popup, font_size, main_menu_position,
|
|
||||||
quicksettings_position, font_color, font_color_light, waves_color, disable_image_browser,
|
|
||||||
server_default_port,
|
|
||||||
auto_search_port,
|
|
||||||
auto_start_server,
|
|
||||||
fetch_output_folder_from_a1111_settings, sfw_mode, enable_clear_button, enable_extra_network_tweaks):
|
|
||||||
with gr.Row():
|
|
||||||
btn_save = gr.Button(value="Save", elem_id="nevyui_sh_options_submit",
|
|
||||||
elem_classes="nevyui_apply_settings")
|
|
||||||
btn_save.click(gradio_save_settings, inputs=[
|
|
||||||
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,
|
|
||||||
sfw_mode,
|
|
||||||
enable_clear_button,
|
|
||||||
enable_extra_network_tweaks,
|
|
||||||
], outputs=[])
|
|
||||||
|
|
||||||
btn_reset = gr.Button(value="Reset default (Reload UI needed to apply)",
|
|
||||||
elem_id="nevyui_sh_options_reset", elem_classes="nevyui_apply_settings")
|
|
||||||
# restore default settings
|
|
||||||
btn_reset.click(reset_settings)
|
|
||||||
|
|
||||||
btn_reload = gr.Button(value="Reload UI", elem_id="nevyui_sh_options_reset",
|
|
||||||
elem_classes="nevyui_apply_settings")
|
|
||||||
# reload the page
|
|
||||||
btn_reload.click(
|
|
||||||
fn=request_restart,
|
|
||||||
_js='restart_reload',
|
|
||||||
inputs=[],
|
|
||||||
outputs=[], )
|
|
||||||
|
|
||||||
# start socket server
|
|
||||||
btn_start = gr.Button(value="Start Socket Server", elem_id="nevyui_sh_options_start_socket",
|
|
||||||
elem_classes="nevyui_apply_settings")
|
|
||||||
btn_start.click(
|
|
||||||
fn=serv_img_browser_socket,
|
|
||||||
inputs=[],
|
|
||||||
outputs=[], )
|
|
||||||
|
|
||||||
with gr.Row(elem_id='nevysha-saved-feedback-wrapper'):
|
|
||||||
gr.HTML(
|
|
||||||
value="<div id='nevysha-saved-feedback' class='nevysha nevysha-feedback' style='display:none;'>Saved !</div>")
|
|
||||||
gr.HTML(
|
|
||||||
value="<div id='nevysha-reset-feedback' class='nevysha nevysha-feedback' style='display:none;'>Reset !</div>")
|
|
||||||
gr.HTML(
|
|
||||||
value="<div id='nevysha-dummy-feedback' class='nevysha nevysha-feedback' style='display:none;' />")
|
|
||||||
|
|
||||||
# add button to trigger git pull
|
|
||||||
btn_update = gr.Button(value="Update", elem_id="nevyui_sh_options_update", visible=False, )
|
|
||||||
btn_update.click(
|
|
||||||
fn=update,
|
|
||||||
inputs=[],
|
|
||||||
outputs=[], )
|
|
||||||
|
|
||||||
|
|
||||||
def gradio_hidden_field(server_port):
|
def gradio_hidden_field(server_port):
|
||||||
# text with port number
|
# text with port number
|
||||||
gr.Textbox(elem_id='cnib_socket_server_port', value=f"{server_port}", label="Server port READONLY",
|
gr.Textbox(elem_id='cnib_socket_server_port', value=f"{server_port}", label="Server port READONLY",
|
||||||
|
|
@ -447,36 +274,11 @@ def gradio_hidden_field(server_port):
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
def prune_ui_settings(**kwargs):
|
_server_port = None
|
||||||
# load file ui-config.json located in working directory
|
|
||||||
ui_config_path = shared.cmd_opts.ui_config_file
|
|
||||||
if ui_config_path is None:
|
|
||||||
ui_config_path = os.path.join(shared.get_app_dir(), 'ui-config.json')
|
|
||||||
if os.path.exists(ui_config_path):
|
|
||||||
with open(ui_config_path, 'r') as f:
|
|
||||||
ui_config = json.load(f)
|
|
||||||
# remove keys that contains "nevyui" prefix
|
|
||||||
pruned = False
|
|
||||||
for key in list(ui_config.keys()):
|
|
||||||
if key.startswith('nevyui'):
|
|
||||||
pruned = True
|
|
||||||
del ui_config[key]
|
|
||||||
|
|
||||||
if pruned:
|
|
||||||
print('CozyNest: Pruned ui-config.json')
|
|
||||||
|
|
||||||
# save the file
|
|
||||||
with open(ui_config_path, 'w') as f:
|
|
||||||
json.dump(ui_config, f, indent=4)
|
|
||||||
|
|
||||||
|
|
||||||
script_callbacks.on_before_reload(prune_ui_settings)
|
|
||||||
script_callbacks.on_app_started(lambda a, b: prune_ui_settings)
|
|
||||||
prune_ui_settings()
|
|
||||||
|
|
||||||
|
|
||||||
def on_ui_tabs():
|
def on_ui_tabs():
|
||||||
prune_ui_settings()
|
global _server_port
|
||||||
# shared options
|
# shared options
|
||||||
config = get_dict_from_config()
|
config = get_dict_from_config()
|
||||||
# merge default settings with user settings
|
# merge default settings with user settings
|
||||||
|
|
@ -519,25 +321,10 @@ def on_ui_tabs():
|
||||||
config.get('auto_search_port'),
|
config.get('auto_search_port'),
|
||||||
config.get('cnib_output_folder')
|
config.get('cnib_output_folder')
|
||||||
)
|
)
|
||||||
|
_server_port = server_port
|
||||||
else:
|
else:
|
||||||
print("CozyNest: Image browser is disabled. To enable it, go to the CozyNest settings.")
|
print("CozyNest: Image browser is disabled. To enable it, go to the CozyNest settings.")
|
||||||
|
|
||||||
async def send_to_socket(data):
|
|
||||||
async with websockets.connect(f'ws://localhost:{server_port}') as websocket:
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
# Send data to the server
|
|
||||||
data = json.dumps(data).encode('utf-8')
|
|
||||||
await websocket.send(data)
|
|
||||||
|
|
||||||
# Receive response from the server
|
|
||||||
await websocket.recv()
|
|
||||||
websocket.close()
|
|
||||||
break
|
|
||||||
|
|
||||||
except websockets.exceptions.ConnectionClosed:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def on_image_saved(gen_params: script_callbacks.ImageSaveParams):
|
def on_image_saved(gen_params: script_callbacks.ImageSaveParams):
|
||||||
base_dir = scripts.basedir()
|
base_dir = scripts.basedir()
|
||||||
|
|
||||||
|
|
@ -551,10 +338,13 @@ def on_ui_tabs():
|
||||||
if not any([path.startswith(folder) for folder in images_folders]):
|
if not any([path.startswith(folder) for folder in images_folders]):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
data = tools.get_exif(path)
|
||||||
|
tools.new_image(data)
|
||||||
|
|
||||||
asyncio.run(send_to_socket({
|
asyncio.run(send_to_socket({
|
||||||
'what': 'image_saved',
|
'what': 'image_saved',
|
||||||
'data': get_exif(path),
|
'data': data,
|
||||||
}))
|
}, server_port))
|
||||||
|
|
||||||
if not disable_image_browser_value:
|
if not disable_image_browser_value:
|
||||||
script_callbacks.on_image_saved(on_image_saved)
|
script_callbacks.on_image_saved(on_image_saved)
|
||||||
|
|
@ -567,79 +357,34 @@ def on_ui_tabs():
|
||||||
" then use <a href='https://www.reddit.com/r/NevyshaCozyNest/'>this subreddit</a>"
|
" then use <a href='https://www.reddit.com/r/NevyshaCozyNest/'>this subreddit</a>"
|
||||||
" or <a href='https://github.com/Nevysha/Cozy-Nest'>github</a>. "
|
" or <a href='https://github.com/Nevysha/Cozy-Nest'>github</a>. "
|
||||||
"You can also join this <a href='https://discord.gg/yppzDXjT7S'>discord server</a> to discuss about Cozy Nest</p>"
|
"You can also join this <a href='https://discord.gg/yppzDXjT7S'>discord server</a> to discuss about Cozy Nest</p>"
|
||||||
"<p class='nevysha-emphasis'>WARNING : Some visual settings are immediately applied but will not be saved until you click \"Save\"</p></div>")
|
"</div>")
|
||||||
|
|
||||||
with gr.Tabs(id="cozy_nest_settings_tabs", elem_id="cozy_nest_settings_tabs"):
|
|
||||||
with gr.TabItem(label="Main Settings", elem_id="cozy_nest_settings_tab"):
|
|
||||||
[
|
|
||||||
accent_color,
|
|
||||||
accent_generate_button,
|
|
||||||
bg_gradiant_color,
|
|
||||||
card_height,
|
|
||||||
card_width,
|
|
||||||
disable_waves_and_gradiant,
|
|
||||||
error_popup,
|
|
||||||
font_size,
|
|
||||||
main_menu_position,
|
|
||||||
quicksettings_position,
|
|
||||||
font_color,
|
|
||||||
font_color_light,
|
|
||||||
waves_color,
|
|
||||||
sfw_mode
|
|
||||||
] = gradio_main_tab(config)
|
|
||||||
with gr.TabItem(label="Image Browser Settings", elem_id="cozy_nest_img_browser_settings_tab"):
|
|
||||||
[
|
|
||||||
disable_image_browser,
|
|
||||||
server_default_port,
|
|
||||||
auto_search_port,
|
|
||||||
auto_start_server,
|
|
||||||
fetch_output_folder_from_a1111_settings,
|
|
||||||
] = gradio_img_browser_tab(config)
|
|
||||||
with gr.TabItem(label="Others", elem_id="cozy_nest_others_settings_tab"):
|
|
||||||
with gr.Column():
|
|
||||||
[
|
|
||||||
enable_clear_button,
|
|
||||||
enable_extra_network_tweaks
|
|
||||||
] = gradio_others_settings(config)
|
|
||||||
|
|
||||||
ui_action_btn(accent_color, accent_generate_button, bg_gradiant_color, card_height, card_width,
|
|
||||||
disable_waves_and_gradiant, error_popup, font_size, main_menu_position,
|
|
||||||
quicksettings_position, font_color, font_color_light, waves_color, disable_image_browser,
|
|
||||||
server_default_port,
|
|
||||||
auto_search_port,
|
|
||||||
auto_start_server,
|
|
||||||
fetch_output_folder_from_a1111_settings, sfw_mode, enable_clear_button,
|
|
||||||
enable_extra_network_tweaks)
|
|
||||||
|
|
||||||
# hidden field to store some useful data and trigger some server actions (like "send to" txt2img,...)
|
# hidden field to store some useful data and trigger some server actions (like "send to" txt2img,...)
|
||||||
gradio_hidden_field(server_port)
|
gradio_hidden_field(server_port)
|
||||||
|
|
||||||
# footer
|
|
||||||
gr.HTML(value="<div class='nevysha settings-nevyui-bottom'>"
|
|
||||||
" <p class='info'>Made by Nevysha with luv</p>"
|
|
||||||
"</div>", elem_id="nevyui_footer_wrapper")
|
|
||||||
|
|
||||||
return [(ui, "Nevysha Cozy Nest", "nevyui")]
|
return [(ui, "Nevysha Cozy Nest", "nevyui")]
|
||||||
|
|
||||||
|
|
||||||
def gradio_others_settings(config):
|
|
||||||
gr.HTML(value="<div id='cozynest_others_settings_header'>"
|
|
||||||
"<p>Those settings are heavy on DOM modification and might conflict with some others extensions</p>"
|
|
||||||
"<p>Reload UI needed to apply</p>"
|
|
||||||
"</div>")
|
|
||||||
|
|
||||||
enable_clear_button = gr.Checkbox(label="Enable clear gallery button in txt2img and img2img tabs",
|
|
||||||
value=config.get('enable_clear_button'), elem_id="cozynest_various_clearbtn")
|
|
||||||
enable_extra_network_tweaks = gr.Checkbox(label="Enable extra network tweaks",
|
|
||||||
value=config.get('enable_extra_network_tweaks'),
|
|
||||||
elem_id="cozynest_various_extra_network_tweaks")
|
|
||||||
|
|
||||||
return [enable_clear_button, enable_extra_network_tweaks]
|
|
||||||
|
|
||||||
|
|
||||||
cwd = os.path.normpath(os.path.join(__file__, "../../"))
|
cwd = os.path.normpath(os.path.join(__file__, "../../"))
|
||||||
|
|
||||||
|
|
||||||
|
async def send_to_socket(data, server_port):
|
||||||
|
async with websockets.connect(f'ws://localhost:{server_port}') as websocket:
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
# Send data to the server
|
||||||
|
data = json.dumps(data).encode('utf-8')
|
||||||
|
await websocket.send(data)
|
||||||
|
|
||||||
|
# Receive response from the server
|
||||||
|
await websocket.recv()
|
||||||
|
await websocket.close()
|
||||||
|
break
|
||||||
|
|
||||||
|
except websockets.exceptions.ConnectionClosed:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def cozy_nest_api(_: Any, app: FastAPI, **kwargs):
|
def cozy_nest_api(_: Any, app: FastAPI, **kwargs):
|
||||||
app.mount(
|
app.mount(
|
||||||
"/cozy-nest-client/",
|
"/cozy-nest-client/",
|
||||||
|
|
@ -662,6 +407,15 @@ def cozy_nest_api(_: Any, app: FastAPI, **kwargs):
|
||||||
|
|
||||||
return {"message": "Config saved successfully"}
|
return {"message": "Config saved successfully"}
|
||||||
|
|
||||||
|
@app.delete("/cozy-nest/config")
|
||||||
|
async def delete_config():
|
||||||
|
reset_settings()
|
||||||
|
return {"message": "Config deleted successfully"}
|
||||||
|
|
||||||
|
@app.get("/cozy-nest/reloadui")
|
||||||
|
async def reload_ui():
|
||||||
|
request_restart()
|
||||||
|
|
||||||
@app.get("/cozy-nest/image")
|
@app.get("/cozy-nest/image")
|
||||||
async def get_image(path: str):
|
async def get_image(path: str):
|
||||||
# Open the file in binary mode
|
# Open the file in binary mode
|
||||||
|
|
@ -679,8 +433,101 @@ def cozy_nest_api(_: Any, app: FastAPI, **kwargs):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
tools.delete_img_data(path)
|
||||||
return Response(status_code=404, content="File not found")
|
return Response(status_code=404, content="File not found")
|
||||||
|
|
||||||
|
@app.delete("/cozy-nest/image")
|
||||||
|
async def delete_image(path: str):
|
||||||
|
try:
|
||||||
|
os.remove(path)
|
||||||
|
tools.delete_img_data(path)
|
||||||
|
return {"message": "File deleted successfully"}
|
||||||
|
except FileNotFoundError:
|
||||||
|
return Response(status_code=404, content="File not found")
|
||||||
|
|
||||||
|
@app.delete("/cozy-nest/index")
|
||||||
|
async def delete_index():
|
||||||
|
global _server_port
|
||||||
|
|
||||||
|
config = get_dict_from_config()
|
||||||
|
cnib_output_folder = config.get('cnib_output_folder')
|
||||||
|
if cnib_output_folder and cnib_output_folder != "":
|
||||||
|
tools.delete_index()
|
||||||
|
|
||||||
|
def _scrap():
|
||||||
|
try:
|
||||||
|
data = tools.scrap_image_folders(cnib_output_folder)
|
||||||
|
asyncio.run(send_to_socket({
|
||||||
|
'what': 'index_built',
|
||||||
|
'data': data['images'],
|
||||||
|
}, _server_port))
|
||||||
|
finally:
|
||||||
|
pass
|
||||||
|
|
||||||
|
thread = threading.Thread(target=_scrap)
|
||||||
|
thread.start()
|
||||||
|
return {"message": "Index deleted successfully, rebuilding index in background"}
|
||||||
|
else:
|
||||||
|
return Response(status_code=412, content="Missing output folder in config")
|
||||||
|
|
||||||
|
@app.put('/cozy-nest/image')
|
||||||
|
async def move_image(request: Request, path: str):
|
||||||
|
try:
|
||||||
|
request_json = await request.json()
|
||||||
|
is_archive = request_json['archive']
|
||||||
|
if not is_archive:
|
||||||
|
# do nothing for now
|
||||||
|
return Response(status_code=501, content="unimplemented")
|
||||||
|
|
||||||
|
config = get_dict_from_config()
|
||||||
|
archive_path = config.get('archive_path')
|
||||||
|
if not archive_path or archive_path == "":
|
||||||
|
# return {"message": "archive path not set"}
|
||||||
|
return Response(status_code=412, content="archive path not set")
|
||||||
|
|
||||||
|
# check if archive path exists
|
||||||
|
if not os.path.exists(archive_path):
|
||||||
|
return Response(status_code=412, content=f"archive path:{archive_path} does not exist")
|
||||||
|
|
||||||
|
new_path = os.path.join(archive_path, os.path.basename(path))
|
||||||
|
|
||||||
|
os.rename(path, new_path)
|
||||||
|
tools.delete_img_data(path)
|
||||||
|
return {"message": "File moved successfully"}
|
||||||
|
except FileNotFoundError:
|
||||||
|
return Response(status_code=404, content="File not found")
|
||||||
|
|
||||||
|
@app.get("/cozy-nest/image-exif")
|
||||||
|
async def get_image_exif(path: str):
|
||||||
|
|
||||||
|
src_info = tools.get_image_exif(path)
|
||||||
|
|
||||||
|
return Response(content=json.dumps(src_info), media_type="application/json")
|
||||||
|
|
||||||
|
@app.post("/cozy-nest/image-exif")
|
||||||
|
async def set_image_exif(request: Request):
|
||||||
|
# Access POST parameters
|
||||||
|
request_json = await request.json()
|
||||||
|
data = request_json['data']
|
||||||
|
path = request_json['path']
|
||||||
|
image = Image.open(path)
|
||||||
|
image.load()
|
||||||
|
|
||||||
|
tgt_info = PngInfo()
|
||||||
|
|
||||||
|
for key, value in data.items():
|
||||||
|
tgt_info.add_text(key, str(value))
|
||||||
|
|
||||||
|
image.save(path, pnginfo=tgt_info)
|
||||||
|
tools.update_img_data(path)
|
||||||
|
|
||||||
|
return {"message": "EXIF data saved successfully"}
|
||||||
|
|
||||||
|
@app.get("/cozy-nest/extra-networks")
|
||||||
|
async def get_extra_networks():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
script_callbacks.on_ui_tabs(on_ui_tabs)
|
script_callbacks.on_ui_tabs(on_ui_tabs)
|
||||||
script_callbacks.on_app_started(cozy_nest_api)
|
script_callbacks.on_app_started(cozy_nest_api)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
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"):
|
||||||
|
# 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.1.7"
|
"version": "2.2.0"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue