💄 style: Update ESLint configuration and add Prettier configuration
parent
ad040e1c18
commit
fefad8c569
|
|
@ -11,3 +11,4 @@ jest*
|
|||
/docs
|
||||
/dist
|
||||
/javascript
|
||||
style.css
|
||||
|
|
|
|||
14
.eslintrc.js
14
.eslintrc.js
|
|
@ -1,7 +1,15 @@
|
|||
module.exports = {
|
||||
extends: require.resolve('@umijs/lint/dist/config/eslint'),
|
||||
plugins: ['simple-import-sort', 'import', 'typescript-sort-keys'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
'no-param-reassign': 1,
|
||||
'simple-import-sort/imports': 'error',
|
||||
'simple-import-sort/exports': 'error',
|
||||
'import/first': 'error',
|
||||
'import/newline-after-import': 'error',
|
||||
'import/no-duplicates': 'error',
|
||||
'typescript-sort-keys/interface': 'error',
|
||||
'typescript-sort-keys/string-enum': 'error',
|
||||
'react/jsx-sort-props': 'error',
|
||||
'react/jsx-no-useless-fragment': 'error',
|
||||
},
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
tasks:
|
||||
- init: pnpm install
|
||||
command: pnpm run start
|
||||
|
|
@ -25,4 +25,4 @@ yarn-error.log
|
|||
.env
|
||||
.env.local
|
||||
style.css
|
||||
javascript/index.js
|
||||
javascript/
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 120
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
module.exports = {
|
||||
pluginSearchDirs: false,
|
||||
plugins: [
|
||||
require.resolve('prettier-plugin-organize-imports'),
|
||||
require.resolve('prettier-plugin-packagejson'),
|
||||
],
|
||||
printWidth: 100,
|
||||
proseWrap: 'never',
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
overrides: [
|
||||
{
|
||||
files: '*.md',
|
||||
options: {
|
||||
proseWrap: 'preserve',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
style.css
|
||||
File diff suppressed because one or more lines are too long
20
package.json
20
package.json
|
|
@ -12,7 +12,7 @@
|
|||
"scripts": {
|
||||
"build": "umi build",
|
||||
"dev": "umi build",
|
||||
"lint": "eslint \"{src,javascript}/**/*.{js,jsx,ts,tsx}\" --fix",
|
||||
"lint": "eslint \"src/**/*.{js,jsx,ts,tsx}\" --fix",
|
||||
"prepare": "husky install",
|
||||
"prettier": "prettier -c --write \"**/**\" && npm run lint && npm run stylelint",
|
||||
"release": "semantic-release",
|
||||
|
|
@ -24,25 +24,27 @@
|
|||
"type-check": "tsc -p tsconfig-check.json"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.less": [
|
||||
"*.{css,less}": [
|
||||
"stylelint --fix",
|
||||
"prettier --write"
|
||||
],
|
||||
"*.{md,json}": [
|
||||
"prettier --write --no-error-on-unmatched-pattern"
|
||||
],
|
||||
"*.json": [
|
||||
"prettier --write --no-error-on-unmatched-pattern"
|
||||
],
|
||||
"*.jsx": [
|
||||
"*.{js,jsx}": [
|
||||
"stylelint --fix",
|
||||
"eslint --fix",
|
||||
"prettier --write"
|
||||
],
|
||||
"*.{ts,tsx}": [
|
||||
"stylelint --fix",
|
||||
"eslint --fix",
|
||||
"prettier --parser=typescript --write"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"lucide-react": "^0.224.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ant-design/icons": "^5",
|
||||
"@commitlint/cli": "^17",
|
||||
|
|
@ -67,6 +69,9 @@
|
|||
"eslint": "^8",
|
||||
"eslint-import-resolver-alias": "^1",
|
||||
"eslint-import-resolver-typescript": "^3",
|
||||
"eslint-plugin-import": "^2",
|
||||
"eslint-plugin-simple-import-sort": "^10",
|
||||
"eslint-plugin-typescript-sort-keys": "^2",
|
||||
"husky": "^8",
|
||||
"lightningcss": "^1",
|
||||
"lint-staged": "^13",
|
||||
|
|
@ -99,8 +104,5 @@
|
|||
"use-merge-value": "^1",
|
||||
"webpack-shell-plugin-next": "^2",
|
||||
"zustand": "^4"
|
||||
},
|
||||
"dependencies": {
|
||||
"lucide-react": "^0.224.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
// import { useAppStore } from '@/store'
|
||||
import { FloatButton } from 'antd'
|
||||
import React, { useRef } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { useAppStore } from '@/store'
|
||||
import { shallow } from 'zustand/shallow'
|
||||
import { FloatButton } from 'antd';
|
||||
import React, { useRef } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
import { useAppStore } from '@/store';
|
||||
|
||||
const ContentView = styled.div<{ isPromptResizable: boolean }>`
|
||||
overflow-x: hidden;
|
||||
|
|
@ -17,23 +18,23 @@ const ContentView = styled.div<{ isPromptResizable: boolean }>`
|
|||
[id$='2img_neg_prompt'] textarea {
|
||||
max-height: ${({ isPromptResizable }) => (isPromptResizable ? 'unset' : '84px')};
|
||||
}
|
||||
`
|
||||
`;
|
||||
|
||||
interface ContentProps {
|
||||
children: React.ReactNode
|
||||
loading?: boolean
|
||||
children: React.ReactNode;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
const Content: React.FC<ContentProps> = ({ children }) => {
|
||||
const ref: any = useRef(null)
|
||||
const [setting] = useAppStore((st) => [st.setting], shallow)
|
||||
const ref: any = useRef(null);
|
||||
const [setting] = useAppStore((st) => [st.setting], shallow);
|
||||
|
||||
return (
|
||||
<ContentView ref={ref} isPromptResizable={setting.promotTextarea === 'resizable'}>
|
||||
<ContentView isPromptResizable={setting.promotTextarea === 'resizable'} ref={ref}>
|
||||
{children}
|
||||
<FloatButton.BackTop target={() => ref.current} />
|
||||
</ContentView>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Content)
|
||||
export default React.memo(Content);
|
||||
|
|
|
|||
|
|
@ -1,38 +1,40 @@
|
|||
import type { DivProps } from '@/types'
|
||||
import { useHover } from 'ahooks'
|
||||
import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp } from 'lucide-react'
|
||||
import type { Enable, NumberSize, Size } from 're-resizable'
|
||||
import { HandleClassName, Resizable } from 're-resizable'
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Center } from 'react-layout-kit'
|
||||
import type { Props as RndProps } from 'react-rnd'
|
||||
import useControlledState from 'use-merge-value'
|
||||
import { useStyle } from './style'
|
||||
import { revesePlacement } from './utils'
|
||||
import { useHover } from 'ahooks';
|
||||
import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp } from 'lucide-react';
|
||||
import type { Enable, NumberSize, Size } from 're-resizable';
|
||||
import { HandleClassName, Resizable } from 're-resizable';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Center } from 'react-layout-kit';
|
||||
import type { Props as RndProps } from 'react-rnd';
|
||||
import useControlledState from 'use-merge-value';
|
||||
|
||||
const DEFAULT_HEIGHT = 180
|
||||
const DEFAULT_WIDTH = 280
|
||||
import type { DivProps } from '@/types';
|
||||
|
||||
export type placementType = 'right' | 'left' | 'top' | 'bottom'
|
||||
import { useStyle } from './style';
|
||||
import { revesePlacement } from './utils';
|
||||
|
||||
const DEFAULT_HEIGHT = 180;
|
||||
const DEFAULT_WIDTH = 280;
|
||||
|
||||
export type placementType = 'right' | 'left' | 'top' | 'bottom';
|
||||
|
||||
export interface DraggablePanelProps extends DivProps {
|
||||
pin?: boolean
|
||||
mode?: 'fixed' | 'float'
|
||||
placement: placementType
|
||||
minWidth?: number
|
||||
minHeight?: number
|
||||
resize?: RndProps['enableResizing']
|
||||
size?: Partial<Size>
|
||||
onSizeChange?: (delta: NumberSize, size?: Size) => void
|
||||
onSizeDragging?: (delta: NumberSize, size?: Size) => void
|
||||
expandable?: boolean
|
||||
expand?: boolean
|
||||
defaultExpand?: boolean
|
||||
onExpandChange?: (expand: boolean) => void
|
||||
defaultSize?: Partial<Size>
|
||||
destroyOnClose?: boolean
|
||||
showHandlerWhenUnexpand?: boolean
|
||||
hanlderStyle?: React.CSSProperties
|
||||
defaultExpand?: boolean;
|
||||
defaultSize?: Partial<Size>;
|
||||
destroyOnClose?: boolean;
|
||||
expand?: boolean;
|
||||
expandable?: boolean;
|
||||
hanlderStyle?: React.CSSProperties;
|
||||
minHeight?: number;
|
||||
minWidth?: number;
|
||||
mode?: 'fixed' | 'float';
|
||||
onExpandChange?: (expand: boolean) => void;
|
||||
onSizeChange?: (delta: NumberSize, size?: Size) => void;
|
||||
onSizeDragging?: (delta: NumberSize, size?: Size) => void;
|
||||
pin?: boolean;
|
||||
placement: placementType;
|
||||
resize?: RndProps['enableResizing'];
|
||||
showHandlerWhenUnexpand?: boolean;
|
||||
size?: Partial<Size>;
|
||||
}
|
||||
|
||||
const DraggablePanel: React.FC<DraggablePanelProps> = ({
|
||||
|
|
@ -57,37 +59,37 @@ const DraggablePanel: React.FC<DraggablePanelProps> = ({
|
|||
destroyOnClose,
|
||||
hanlderStyle,
|
||||
}) => {
|
||||
const ref = useRef(null)
|
||||
const isHovering = useHover(ref)
|
||||
const isVertical = placement === 'top' || placement === 'bottom'
|
||||
const ref = useRef(null);
|
||||
const isHovering = useHover(ref);
|
||||
const isVertical = placement === 'top' || placement === 'bottom';
|
||||
|
||||
const { styles, cx } = useStyle('draggable-panel')
|
||||
const { styles, cx } = useStyle('draggable-panel');
|
||||
|
||||
const [isExpand, setIsExpand] = useControlledState(defaultExpand, {
|
||||
value: expand,
|
||||
onChange: onExpandChange,
|
||||
})
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (pin) return
|
||||
if (pin) return;
|
||||
if (isHovering && !isExpand) {
|
||||
setIsExpand(true)
|
||||
setIsExpand(true);
|
||||
} else if (!isHovering && isExpand) {
|
||||
setIsExpand(false)
|
||||
setIsExpand(false);
|
||||
}
|
||||
}, [pin, isHovering, isExpand])
|
||||
}, [pin, isHovering, isExpand]);
|
||||
|
||||
const [showExpand, setShowExpand] = useState(true)
|
||||
const [showExpand, setShowExpand] = useState(true);
|
||||
|
||||
const canResizing = resize !== false && isExpand
|
||||
const canResizing = resize !== false && isExpand;
|
||||
|
||||
const resizeHandleClassNames: HandleClassName = useMemo(() => {
|
||||
if (!canResizing) return {}
|
||||
if (!canResizing) return {};
|
||||
|
||||
return {
|
||||
[revesePlacement(placement)]: styles[`${revesePlacement(placement)}Handle`],
|
||||
}
|
||||
}, [canResizing, placement])
|
||||
};
|
||||
}, [canResizing, placement]);
|
||||
|
||||
const resizing = {
|
||||
top: false,
|
||||
|
|
@ -100,7 +102,7 @@ const DraggablePanel: React.FC<DraggablePanelProps> = ({
|
|||
topLeft: false,
|
||||
[revesePlacement(placement)]: true,
|
||||
...(resize as Enable),
|
||||
}
|
||||
};
|
||||
|
||||
const defaultSize: Size = useMemo(() => {
|
||||
if (isVertical)
|
||||
|
|
@ -108,14 +110,14 @@ const DraggablePanel: React.FC<DraggablePanelProps> = ({
|
|||
width: '100%',
|
||||
height: DEFAULT_HEIGHT,
|
||||
...customizeDefaultSize,
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
width: DEFAULT_WIDTH,
|
||||
height: '100%',
|
||||
...customizeDefaultSize,
|
||||
}
|
||||
}, [isVertical])
|
||||
};
|
||||
}, [isVertical]);
|
||||
|
||||
const sizeProps = isExpand
|
||||
? {
|
||||
|
|
@ -132,20 +134,20 @@ const DraggablePanel: React.FC<DraggablePanelProps> = ({
|
|||
: {
|
||||
minWidth: 0,
|
||||
size: { width: 0 },
|
||||
}
|
||||
};
|
||||
|
||||
const { Arrow, className: arrowPlacement } = useMemo(() => {
|
||||
switch (placement) {
|
||||
case 'top':
|
||||
return { className: 'Bottom', Arrow: ChevronDown }
|
||||
return { className: 'Bottom', Arrow: ChevronDown };
|
||||
case 'bottom':
|
||||
return { className: 'Top', Arrow: ChevronUp }
|
||||
return { className: 'Top', Arrow: ChevronUp };
|
||||
case 'right':
|
||||
return { className: 'Left', Arrow: ChevronLeft }
|
||||
return { className: 'Left', Arrow: ChevronLeft };
|
||||
case 'left':
|
||||
return { className: 'Right', Arrow: ChevronRight }
|
||||
return { className: 'Right', Arrow: ChevronRight };
|
||||
}
|
||||
}, [styles, placement])
|
||||
}, [styles, placement]);
|
||||
|
||||
const handler = (
|
||||
// @ts-ignore
|
||||
|
|
@ -155,62 +157,65 @@ const DraggablePanel: React.FC<DraggablePanelProps> = ({
|
|||
style={{ opacity: isExpand ? (!pin ? 0 : undefined) : showHandlerWhenUnexpand ? 1 : 0 }}
|
||||
>
|
||||
<Center
|
||||
style={hanlderStyle}
|
||||
onClick={() => {
|
||||
setIsExpand(!isExpand)
|
||||
setIsExpand(!isExpand);
|
||||
}}
|
||||
style={hanlderStyle}
|
||||
>
|
||||
<div className={styles.handlerIcon} style={{ transform: `rotate(${isExpand ? 180 : 0}deg)` }}>
|
||||
<div
|
||||
className={styles.handlerIcon}
|
||||
style={{ transform: `rotate(${isExpand ? 180 : 0}deg)` }}
|
||||
>
|
||||
<Arrow size={16} strokeWidth={1.5} />
|
||||
</div>
|
||||
</Center>
|
||||
</Center>
|
||||
)
|
||||
);
|
||||
|
||||
const inner = (
|
||||
// @ts-ignore
|
||||
<Resizable
|
||||
{...sizeProps}
|
||||
style={style}
|
||||
className={styles.panel}
|
||||
enable={canResizing ? (resizing as Enable) : undefined}
|
||||
handleClasses={resizeHandleClassNames}
|
||||
onResizeStop={(e, direction, ref, delta) => {
|
||||
setShowExpand(true)
|
||||
onSizeChange?.(delta, {
|
||||
width: ref.style.width,
|
||||
height: ref.style.height,
|
||||
})
|
||||
}}
|
||||
onResizeStart={() => {
|
||||
setShowExpand(false)
|
||||
}}
|
||||
onResize={(_, direction, ref, delta) => {
|
||||
onSizeDragging?.(delta, {
|
||||
width: ref.style.width,
|
||||
height: ref.style.height,
|
||||
})
|
||||
});
|
||||
}}
|
||||
onResizeStart={() => {
|
||||
setShowExpand(false);
|
||||
}}
|
||||
onResizeStop={(e, direction, ref, delta) => {
|
||||
setShowExpand(true);
|
||||
onSizeChange?.(delta, {
|
||||
width: ref.style.width,
|
||||
height: ref.style.height,
|
||||
});
|
||||
}}
|
||||
style={style}
|
||||
>
|
||||
{children}
|
||||
</Resizable>
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cx(
|
||||
styles.container,
|
||||
// @ts-ignore
|
||||
styles[mode === 'fixed' ? 'fixed' : `${placement}Float`],
|
||||
className
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
style={{ [`border${arrowPlacement}Width`]: 1 }}
|
||||
>
|
||||
{expandable && showExpand && handler}
|
||||
{destroyOnClose ? isExpand && inner : inner}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(DraggablePanel)
|
||||
export default React.memo(DraggablePanel);
|
||||
|
|
|
|||
|
|
@ -1,26 +1,28 @@
|
|||
import { DraggablePanel } from '@/components'
|
||||
import { useAppStore } from '@/store'
|
||||
import { ZoomInOutlined } from '@ant-design/icons'
|
||||
import { Slider } from 'antd'
|
||||
import { useResponsive } from 'antd-style'
|
||||
import React, { CSSProperties, useEffect, useState } from 'react'
|
||||
import styled, { createGlobalStyle } from 'styled-components'
|
||||
import { shallow } from 'zustand/shallow'
|
||||
import { ZoomInOutlined } from '@ant-design/icons';
|
||||
import { Slider } from 'antd';
|
||||
import { useResponsive } from 'antd-style';
|
||||
import React, { CSSProperties, useEffect, useState } from 'react';
|
||||
import styled, { createGlobalStyle } from 'styled-components';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
import { DraggablePanel } from '@/components';
|
||||
import { useAppStore } from '@/store';
|
||||
|
||||
const GlobalStyle = createGlobalStyle`
|
||||
button#txt2img_extra_networks,
|
||||
button#img2img_extra_networks {
|
||||
display: none !important;
|
||||
}
|
||||
`
|
||||
`;
|
||||
|
||||
const View = styled.div`
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
height: -webkit-fill-available;
|
||||
height: -moz-available;
|
||||
`
|
||||
`;
|
||||
|
||||
const SidebarView = styled.div<{ size: number }>`
|
||||
overflow-x: hidden;
|
||||
|
|
@ -47,7 +49,7 @@ const SidebarView = styled.div<{ size: number }>`
|
|||
height: ${({ size }) => size * 1.5}px !important;
|
||||
}
|
||||
}
|
||||
`
|
||||
`;
|
||||
|
||||
const Footer = styled.div`
|
||||
display: flex;
|
||||
|
|
@ -58,57 +60,57 @@ const Footer = styled.div`
|
|||
padding: 8px 16px;
|
||||
|
||||
border-top: 1px solid var(--color-border);
|
||||
`
|
||||
`;
|
||||
|
||||
const ZoomSlider = styled(Slider)`
|
||||
flex: 1;
|
||||
margin-left: 16px;
|
||||
`
|
||||
`;
|
||||
|
||||
interface SidebarProps {
|
||||
children: React.ReactNode
|
||||
loading?: boolean
|
||||
style?: CSSProperties
|
||||
children: React.ReactNode;
|
||||
loading?: boolean;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
const Sidebar: React.FC<SidebarProps> = ({ children, style }) => {
|
||||
const { mobile } = useResponsive()
|
||||
const [setting] = useAppStore((st) => [st.setting], shallow)
|
||||
const [mode] = useState<'fixed' | 'float'>(setting.extraNetworkFixedMode)
|
||||
const [expand, setExpand] = useState<boolean>(setting.extraNetworkSidebarExpand)
|
||||
const [size, setSize] = useState<number>(setting?.extraNetworkCardSize || 86)
|
||||
const { mobile } = useResponsive();
|
||||
const [setting] = useAppStore((st) => [st.setting], shallow);
|
||||
const [mode] = useState<'fixed' | 'float'>(setting.extraNetworkFixedMode);
|
||||
const [expand, setExpand] = useState<boolean>(setting.extraNetworkSidebarExpand);
|
||||
const [size, setSize] = useState<number>(setting?.extraNetworkCardSize || 86);
|
||||
|
||||
useEffect(() => {
|
||||
if (mobile) setExpand(false)
|
||||
}, [])
|
||||
if (mobile) setExpand(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<GlobalStyle />
|
||||
<DraggablePanel
|
||||
defaultSize={{ width: setting.extraNetworkSidebarWidth }}
|
||||
expand={expand}
|
||||
minWidth={setting.extraNetworkSidebarWidth}
|
||||
mode={mode}
|
||||
onExpandChange={setExpand}
|
||||
pin={mode === 'fixed'}
|
||||
placement="right"
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
...style,
|
||||
}}
|
||||
placement="right"
|
||||
defaultSize={{ width: setting.extraNetworkSidebarWidth }}
|
||||
minWidth={setting.extraNetworkSidebarWidth}
|
||||
expand={expand}
|
||||
onExpandChange={setExpand}
|
||||
>
|
||||
<View>
|
||||
<SidebarView size={size}>{children}</SidebarView>
|
||||
<Footer>
|
||||
<ZoomInOutlined />
|
||||
<ZoomSlider defaultValue={size} step={8} max={256} min={64} onChange={setSize} />
|
||||
<ZoomSlider defaultValue={size} max={256} min={64} onChange={setSize} step={8} />
|
||||
</Footer>
|
||||
</View>
|
||||
</DraggablePanel>
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Sidebar)
|
||||
export default React.memo(Sidebar);
|
||||
|
|
|
|||
|
|
@ -1,28 +1,28 @@
|
|||
import React, { useEffect } from 'react'
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
interface GiscusProps {
|
||||
themeMode: 'light' | 'dark'
|
||||
themeMode: 'light' | 'dark';
|
||||
}
|
||||
const Giscus: React.FC<GiscusProps> = ({ themeMode }) => {
|
||||
useEffect(() => {
|
||||
// giscus
|
||||
const giscus: HTMLScriptElement = document.createElement('script')
|
||||
giscus.src = 'https://giscus.app/client.js'
|
||||
giscus.setAttribute('data-repo', 'canisminor1990/sd-webui-kitchen-theme')
|
||||
giscus.setAttribute('data-repo-id', 'R_kgDOJCPcNg')
|
||||
giscus.setAttribute('data-mapping', 'number')
|
||||
giscus.setAttribute('data-term', '53')
|
||||
giscus.setAttribute('data-reactions-enabled', '1')
|
||||
giscus.setAttribute('data-emit-metadata', '0')
|
||||
giscus.setAttribute('data-input-position', 'bottom')
|
||||
giscus.setAttribute('data-theme', themeMode)
|
||||
giscus.setAttribute('data-lang', 'en')
|
||||
giscus.crossOrigin = 'anonymous'
|
||||
giscus.async = true
|
||||
document.getElementsByTagName('head')[0].appendChild(giscus)
|
||||
}, [])
|
||||
const giscus: HTMLScriptElement = document.createElement('script');
|
||||
giscus.src = 'https://giscus.app/client.js';
|
||||
giscus.setAttribute('data-repo', 'canisminor1990/sd-webui-kitchen-theme');
|
||||
giscus.setAttribute('data-repo-id', 'R_kgDOJCPcNg');
|
||||
giscus.setAttribute('data-mapping', 'number');
|
||||
giscus.setAttribute('data-term', '53');
|
||||
giscus.setAttribute('data-reactions-enabled', '1');
|
||||
giscus.setAttribute('data-emit-metadata', '0');
|
||||
giscus.setAttribute('data-input-position', 'bottom');
|
||||
giscus.setAttribute('data-theme', themeMode);
|
||||
giscus.setAttribute('data-lang', 'en');
|
||||
giscus.crossOrigin = 'anonymous';
|
||||
giscus.async = true;
|
||||
document.getElementsByTagName('head')[0].appendChild(giscus);
|
||||
}, []);
|
||||
|
||||
return <div id="giscus" className="giscus" />
|
||||
}
|
||||
return <div className="giscus" id="giscus" />;
|
||||
};
|
||||
|
||||
export default React.memo(Giscus)
|
||||
export default React.memo(Giscus);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,21 @@
|
|||
import React from 'react'
|
||||
import { darkLogo, lightLogo } from './style'
|
||||
import React from 'react';
|
||||
|
||||
import { darkLogo, lightLogo } from './style';
|
||||
|
||||
interface LogoProps {
|
||||
size?: number
|
||||
style?: React.CSSProperties
|
||||
themeMode: 'dark' | 'light'
|
||||
size?: number;
|
||||
style?: React.CSSProperties;
|
||||
themeMode: 'dark' | 'light';
|
||||
}
|
||||
|
||||
const Logo: React.FC<LogoProps> = ({ size = 20, style, themeMode }) => {
|
||||
return <img src={themeMode === 'dark' ? darkLogo : lightLogo} alt="logo" style={{ height: size, ...style }} />
|
||||
}
|
||||
return (
|
||||
<img
|
||||
alt="logo"
|
||||
src={themeMode === 'dark' ? darkLogo : lightLogo}
|
||||
style={{ height: size, ...style }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Logo)
|
||||
export default React.memo(Logo);
|
||||
|
|
|
|||
|
|
@ -1,56 +1,69 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import type { MenuProps } from 'antd'
|
||||
import { Menu } from 'antd'
|
||||
import styled from 'styled-components'
|
||||
import type { MenuProps } from 'antd';
|
||||
import { Menu } from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const NavBar = styled(Menu)`
|
||||
flex: 1;
|
||||
border: none;
|
||||
line-height: 1;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
line-height: 1;
|
||||
border: none;
|
||||
|
||||
.ant-menu-overflow-item {
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px !important;
|
||||
color: var(--color-text-secondary);
|
||||
&:after {
|
||||
display: none; !important;
|
||||
}
|
||||
&:hover {
|
||||
color: var(--color-text);
|
||||
background: var(--color-fill-tertiary);
|
||||
}
|
||||
&.ant-menu-item-selected {
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
}
|
||||
padding: 8px 12px;
|
||||
color: var(--color-text-secondary);
|
||||
border-radius: 4px !important;
|
||||
|
||||
&:after {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--color-text);
|
||||
background: var(--color-fill-tertiary);
|
||||
}
|
||||
|
||||
&.ant-menu-item-selected {
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
`
|
||||
`;
|
||||
|
||||
const Nav: React.FC = () => {
|
||||
const [items, setItems] = useState<MenuProps['items']>([])
|
||||
const [items, setItems] = useState<MenuProps['items']>([]);
|
||||
|
||||
useEffect(() => {
|
||||
onUiLoaded(() => {
|
||||
const buttons = gradioApp().querySelectorAll('#tabs > .tab-nav:first-child button')
|
||||
const list: MenuProps['items'] | any = []
|
||||
const buttons = gradioApp().querySelectorAll('#tabs > .tab-nav:first-child button');
|
||||
const list: MenuProps['items'] | any = [];
|
||||
buttons.forEach((button: HTMLButtonElement | any, index) => {
|
||||
button.id = `kitchen-nav-${index}`
|
||||
button.id = `kitchen-nav-${index}`;
|
||||
list.push({
|
||||
label: button.textContent,
|
||||
key: String(index),
|
||||
})
|
||||
})
|
||||
setItems(list)
|
||||
})
|
||||
}, [])
|
||||
});
|
||||
});
|
||||
setItems(list);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onClick: MenuProps['onClick'] = (e: any) => {
|
||||
const buttons: HTMLButtonElement[] | any = gradioApp().querySelectorAll('#tabs > .tab-nav:first-child button')
|
||||
buttons[Number(e.key)]?.click()
|
||||
}
|
||||
const buttons: HTMLButtonElement[] | any = gradioApp().querySelectorAll(
|
||||
'#tabs > .tab-nav:first-child button',
|
||||
);
|
||||
buttons[Number(e.key)]?.click();
|
||||
};
|
||||
|
||||
return <NavBar defaultActiveFirst defaultSelectedKeys={['0']} onClick={onClick} mode="horizontal" items={items} />
|
||||
}
|
||||
return (
|
||||
<NavBar
|
||||
defaultActiveFirst
|
||||
defaultSelectedKeys={['0']}
|
||||
items={items}
|
||||
mode="horizontal"
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Nav
|
||||
export default Nav;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { WebuiSetting, defaultSetting, useAppStore } from '@/store'
|
||||
import { SettingOutlined } from '@ant-design/icons'
|
||||
import { Button, Divider, Form, InputNumber, Popover, Space, Switch, Segmented } from 'antd'
|
||||
import React, { useCallback } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { shallow } from 'zustand/shallow'
|
||||
import { SettingOutlined } from '@ant-design/icons';
|
||||
import { Button, Divider, Form, InputNumber, Popover, Segmented, Space, Switch } from 'antd';
|
||||
import React, { useCallback } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
import { defaultSetting, useAppStore, WebuiSetting } from '@/store';
|
||||
|
||||
/******************************************************
|
||||
*********************** Style *************************
|
||||
|
|
@ -18,42 +19,46 @@ const FormItem = styled(Form.Item)`
|
|||
flex-grow: unset !important;
|
||||
}
|
||||
}
|
||||
`
|
||||
`;
|
||||
|
||||
const Title = styled.div`
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
`
|
||||
`;
|
||||
|
||||
const SubTitle = styled.div`
|
||||
margin-bottom: 4px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
`
|
||||
`;
|
||||
|
||||
/******************************************************
|
||||
************************* Dom *************************
|
||||
******************************************************/
|
||||
|
||||
const Setting: React.FC = () => {
|
||||
const [setting, onSetSetting] = useAppStore((st) => [st.setting, st.onSetSetting], shallow)
|
||||
const [setting, onSetSetting] = useAppStore((st) => [st.setting, st.onSetSetting], shallow);
|
||||
|
||||
const onReset = useCallback(() => {
|
||||
onSetSetting(defaultSetting)
|
||||
gradioApp().getElementById('settings_restart_gradio')?.click()
|
||||
}, [])
|
||||
onSetSetting(defaultSetting);
|
||||
gradioApp().getElementById('settings_restart_gradio')?.click();
|
||||
}, []);
|
||||
|
||||
const onFinish = useCallback((value: WebuiSetting) => {
|
||||
onSetSetting(value)
|
||||
gradioApp().getElementById('settings_restart_gradio')?.click()
|
||||
}, [])
|
||||
onSetSetting(value);
|
||||
gradioApp().getElementById('settings_restart_gradio')?.click();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
title={<Title>⚙ Setting</Title>}
|
||||
trigger="click"
|
||||
content={
|
||||
<Form size="small" initialValues={setting} layout="horizontal" onFinish={onFinish} style={{ maxWidth: 260 }}>
|
||||
<Form
|
||||
initialValues={setting}
|
||||
layout="horizontal"
|
||||
onFinish={onFinish}
|
||||
size="small"
|
||||
style={{ maxWidth: 260 }}
|
||||
>
|
||||
<Divider style={{ margin: '4px 0 8px' }} />
|
||||
<SubTitle>Promot Textarea</SubTitle>
|
||||
<FormItem label="Display mode" name="promotTextarea">
|
||||
|
|
@ -98,17 +103,19 @@ const Setting: React.FC = () => {
|
|||
<Button htmlType="button" onClick={onReset} style={{ borderRadius: 4 }}>
|
||||
Reset
|
||||
</Button>
|
||||
<Button type="primary" htmlType="submit" style={{ borderRadius: 4 }}>
|
||||
<Button htmlType="submit" style={{ borderRadius: 4 }} type="primary">
|
||||
Apply and restart UI
|
||||
</Button>
|
||||
</Space>
|
||||
</FormItem>
|
||||
</Form>
|
||||
}
|
||||
title={<Title>⚙ Setting</Title>}
|
||||
trigger="click"
|
||||
>
|
||||
<Button title="Setting" icon={<SettingOutlined />} />
|
||||
<Button icon={<SettingOutlined />} title="Setting" />
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Setting)
|
||||
export default React.memo(Setting);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,19 @@
|
|||
import { DraggablePanel } from '@/components'
|
||||
import { useAppStore } from '@/store'
|
||||
import { BoldOutlined, GithubOutlined } from '@ant-design/icons'
|
||||
import { Button, Modal, Space } from 'antd'
|
||||
import { useResponsive } from 'antd-style'
|
||||
import qs from 'query-string'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { shallow } from 'zustand/shallow'
|
||||
import Giscus from './Giscus'
|
||||
import Logo from './Logo'
|
||||
import Setting from './Setting'
|
||||
import Nav from './Nav'
|
||||
import { civitaiLogo, themeIcon } from './style'
|
||||
import { BoldOutlined, GithubOutlined } from '@ant-design/icons';
|
||||
import { Button, Modal, Space } from 'antd';
|
||||
import { useResponsive } from 'antd-style';
|
||||
import qs from 'query-string';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
import { DraggablePanel } from '@/components';
|
||||
import { useAppStore } from '@/store';
|
||||
|
||||
import Giscus from './Giscus';
|
||||
import Logo from './Logo';
|
||||
import Nav from './Nav';
|
||||
import Setting from './Setting';
|
||||
import { civitaiLogo, themeIcon } from './style';
|
||||
|
||||
/******************************************************
|
||||
*********************** Style *************************
|
||||
|
|
@ -27,48 +29,52 @@ const HeaderView = styled.div`
|
|||
height: -webkit-fill-available;
|
||||
height: -moz-available;
|
||||
padding: 16px 24px;
|
||||
`
|
||||
`;
|
||||
|
||||
/******************************************************
|
||||
************************* Dom *************************
|
||||
******************************************************/
|
||||
|
||||
interface HeaderProps {
|
||||
children: React.ReactNode
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const Header: React.FC<HeaderProps> = ({ children }) => {
|
||||
const [themeMode] = useAppStore((st) => [st.themeMode], shallow)
|
||||
const { mobile } = useResponsive()
|
||||
const [expand, setExpand] = useState<boolean>(true)
|
||||
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||
const [themeMode] = useAppStore((st) => [st.themeMode], shallow);
|
||||
const { mobile } = useResponsive();
|
||||
const [expand, setExpand] = useState<boolean>(true);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (mobile) setExpand(false)
|
||||
}, [])
|
||||
if (mobile) setExpand(false);
|
||||
}, []);
|
||||
|
||||
const handleSetTheme = useCallback(() => {
|
||||
const theme = themeMode === 'light' ? 'dark' : 'light'
|
||||
const gradioURL = qs.parseUrl(window.location.href)
|
||||
gradioURL.query.__theme = theme
|
||||
window.location.replace(qs.stringifyUrl(gradioURL))
|
||||
}, [themeMode])
|
||||
const theme = themeMode === 'light' ? 'dark' : 'light';
|
||||
const gradioURL = qs.parseUrl(window.location.href);
|
||||
gradioURL.query.__theme = theme;
|
||||
window.location.replace(qs.stringifyUrl(gradioURL));
|
||||
}, [themeMode]);
|
||||
|
||||
const showModal = () => setIsModalOpen(true)
|
||||
const showModal = () => setIsModalOpen(true);
|
||||
|
||||
const handleCancel = () => setIsModalOpen(false)
|
||||
const handleCancel = () => setIsModalOpen(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DraggablePanel
|
||||
placement="top"
|
||||
defaultSize={{ height: 'auto' }}
|
||||
minHeight={64}
|
||||
expand={expand}
|
||||
minHeight={64}
|
||||
onExpandChange={setExpand}
|
||||
placement="top"
|
||||
>
|
||||
<HeaderView id="header" style={{ flexDirection: mobile ? 'column' : 'row' }}>
|
||||
<a href="https://github.com/canisminor1990/sd-webui-kitchen-theme" target="_blank" rel="noreferrer">
|
||||
<a
|
||||
href="https://github.com/canisminor1990/sd-webui-kitchen-theme"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<Logo themeMode={themeMode} />
|
||||
</a>
|
||||
|
||||
|
|
@ -76,35 +82,43 @@ const Header: React.FC<HeaderProps> = ({ children }) => {
|
|||
{children}
|
||||
|
||||
<Space.Compact>
|
||||
<a href="https://civitai.com/" target="_blank" rel="noreferrer">
|
||||
<Button title="Civitai" icon={civitaiLogo} />
|
||||
<a href="https://civitai.com/" rel="noreferrer" target="_blank">
|
||||
<Button icon={civitaiLogo} title="Civitai" />
|
||||
</a>
|
||||
<a href="https://www.birme.net/?target_width=512&target_height=512" target="_blank" rel="noreferrer">
|
||||
<Button title="Birme" icon={<BoldOutlined />} />
|
||||
<a
|
||||
href="https://www.birme.net/?target_width=512&target_height=512"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<Button icon={<BoldOutlined />} title="Birme" />
|
||||
</a>
|
||||
<Button title="Feedback" icon={<GithubOutlined />} onClick={showModal} />
|
||||
<Button icon={<GithubOutlined />} onClick={showModal} title="Feedback" />
|
||||
<Setting />
|
||||
<Button title="Switch Theme" icon={themeIcon[themeMode]} onClick={handleSetTheme} />
|
||||
<Button icon={themeIcon[themeMode]} onClick={handleSetTheme} title="Switch Theme" />
|
||||
</Space.Compact>
|
||||
</HeaderView>
|
||||
</DraggablePanel>
|
||||
<Modal
|
||||
footer={null}
|
||||
onCancel={handleCancel}
|
||||
open={isModalOpen}
|
||||
title={
|
||||
<a href="https://github.com/canisminor1990/sd-webui-kitchen-theme" target="_blank" rel="noreferrer">
|
||||
<a
|
||||
href="https://github.com/canisminor1990/sd-webui-kitchen-theme"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<Space>
|
||||
<GithubOutlined />
|
||||
{'canisminor1990/sd-webui-kitchen-theme'}
|
||||
</Space>
|
||||
</a>
|
||||
}
|
||||
open={isModalOpen}
|
||||
onCancel={handleCancel}
|
||||
footer={null}
|
||||
>
|
||||
<Giscus themeMode={themeMode} />
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Header)
|
||||
export default React.memo(Header);
|
||||
|
|
|
|||
|
|
@ -1,27 +1,29 @@
|
|||
export const themeIcon = {
|
||||
light: (
|
||||
<span role="img" className="anticon anticon-github">
|
||||
<svg viewBox="0 0 16 16" width="1em" height="1em" fill="currentColor">
|
||||
<span className="anticon anticon-github" role="img">
|
||||
<svg fill="currentColor" height="1em" viewBox="0 0 16 16" width="1em">
|
||||
<path d="M8 13a1 1 0 0 1 1 1v1a1 1 0 1 1-2 0v-1a1 1 0 0 1 1-1ZM8 3a1 1 0 0 1-1-1V1a1 1 0 1 1 2 0v1a1 1 0 0 1-1 1Zm7 4a1 1 0 1 1 0 2h-1a1 1 0 1 1 0-2h1ZM3 8a1 1 0 0 1-1 1H1a1 1 0 1 1 0-2h1a1 1 0 0 1 1 1Zm9.95 3.536.707.707a1 1 0 0 1-1.414 1.414l-.707-.707a1 1 0 0 1 1.414-1.414Zm-9.9-7.072-.707-.707a1 1 0 0 1 1.414-1.414l.707.707A1 1 0 0 1 3.05 4.464Zm9.9 0a1 1 0 0 1-1.414-1.414l.707-.707a1 1 0 0 1 1.414 1.414l-.707.707Zm-9.9 7.072a1 1 0 0 1 1.414 1.414l-.707.707a1 1 0 0 1-1.414-1.414l.707-.707ZM8 4a4 4 0 1 0 0 8 4 4 0 0 0 0-8Zm0 6.5a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5Z"></path>
|
||||
</svg>
|
||||
</span>
|
||||
),
|
||||
dark: (
|
||||
<span role="img" className="anticon anticon-github">
|
||||
<svg viewBox="0 0 16 16" width="1em" height="1em" fill="currentColor">
|
||||
<span className="anticon anticon-github" role="img">
|
||||
<svg fill="currentColor" height="1em" viewBox="0 0 16 16" width="1em">
|
||||
<path d="M8.218 1.455c3.527.109 6.327 3.018 6.327 6.545 0 3.6-2.945 6.545-6.545 6.545a6.562 6.562 0 0 1-6.036-4h.218c3.6 0 6.545-2.945 6.545-6.545 0-.91-.182-1.745-.509-2.545m0-1.455c-.473 0-.909.218-1.2.618-.29.4-.327.946-.145 1.382.254.655.4 1.31.4 2 0 2.8-2.291 5.09-5.091 5.09h-.218c-.473 0-.91.22-1.2.62-.291.4-.328.945-.146 1.38C1.891 14.074 4.764 16 8 16c4.4 0 8-3.6 8-8a7.972 7.972 0 0 0-7.745-8h-.037Z"></path>
|
||||
</svg>
|
||||
</span>
|
||||
),
|
||||
}
|
||||
};
|
||||
|
||||
export const darkLogo = 'https://gw.alipayobjects.com/zos/bmw-prod/9ecb2822-1592-4cb0-a087-ce0097fef2ca.svg'
|
||||
export const lightLogo = 'https://gw.alipayobjects.com/zos/bmw-prod/e146116d-c65a-4306-a3d2-bb8d05e1c49b.svg'
|
||||
export const darkLogo =
|
||||
'https://gw.alipayobjects.com/zos/bmw-prod/9ecb2822-1592-4cb0-a087-ce0097fef2ca.svg';
|
||||
export const lightLogo =
|
||||
'https://gw.alipayobjects.com/zos/bmw-prod/e146116d-c65a-4306-a3d2-bb8d05e1c49b.svg';
|
||||
|
||||
export const civitaiLogo = (
|
||||
<span role="img" className="anticon civitai">
|
||||
<svg viewBox="0 0 16 16" width="1em" height="1em" fill="currentColor">
|
||||
<span className="anticon civitai" role="img">
|
||||
<svg fill="currentColor" height="1em" viewBox="0 0 16 16" width="1em">
|
||||
<path d="M2 4.5L8 1l6 3.5v7L8 15l-6-3.5v-7zm6-1.194L3.976 5.653v4.694L8 12.694l4.024-2.347V5.653L8 3.306zm0 1.589l2.662 1.552v.824H9.25L8 6.54l-1.25.73v1.458L8 9.46l1.25-.73h1.412v.824L8 11.105 5.338 9.553V6.447L8 4.895z" />
|
||||
</svg>
|
||||
</span>
|
||||
)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
import React, { useCallback, useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import TagList, { PromptType, TagItem } from './TagList'
|
||||
import { formatPrompt } from './utils'
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import TagList, { PromptType, TagItem } from './TagList';
|
||||
import { formatPrompt } from './utils';
|
||||
|
||||
const View = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
`
|
||||
`;
|
||||
|
||||
const BtnGroup = styled.div`
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
`
|
||||
`;
|
||||
|
||||
const Btn = styled.button`
|
||||
cursor: pointer;
|
||||
|
|
@ -33,53 +34,54 @@ const Btn = styled.button`
|
|||
background: var(--button-secondary-background-fill);
|
||||
border: var(--button-border-width) solid var(--button-secondary-border-color);
|
||||
border-radius: var(--input-radius);
|
||||
`
|
||||
`;
|
||||
|
||||
interface PromptProps {
|
||||
type: PromptType
|
||||
type: PromptType;
|
||||
}
|
||||
|
||||
const Prompt: React.FC<PromptProps> = ({ type }) => {
|
||||
const [tags, setTags] = useState<TagItem[]>([])
|
||||
const [tags, setTags] = useState<TagItem[]>([]);
|
||||
|
||||
const id = type === 'positive' ? "[id$='2img_prompt'] textarea" : "[id$='2img_neg_prompt'] textarea"
|
||||
const id =
|
||||
type === 'positive' ? "[id$='2img_prompt'] textarea" : "[id$='2img_neg_prompt'] textarea";
|
||||
|
||||
const getValue = useCallback(() => {
|
||||
try {
|
||||
const textarea: HTMLTextAreaElement | any = get_uiCurrentTabContent().querySelector(id)
|
||||
if (textarea) setTags(formatPrompt(textarea.value))
|
||||
const textarea: HTMLTextAreaElement | any = get_uiCurrentTabContent().querySelector(id);
|
||||
if (textarea) setTags(formatPrompt(textarea.value));
|
||||
} catch {}
|
||||
}, [])
|
||||
}, []);
|
||||
|
||||
const setValue = useCallback(() => {
|
||||
try {
|
||||
const textarea: HTMLTextAreaElement | any = get_uiCurrentTabContent().querySelector(id)
|
||||
if (textarea) textarea.value = tags.map((t) => t.text).join(', ')
|
||||
updateInput(textarea)
|
||||
const textarea: HTMLTextAreaElement | any = get_uiCurrentTabContent().querySelector(id);
|
||||
if (textarea) textarea.value = tags.map((t) => t.text).join(', ');
|
||||
updateInput(textarea);
|
||||
} catch {}
|
||||
}, [tags])
|
||||
}, [tags]);
|
||||
|
||||
const setCurrentValue = useCallback((currentTags: TagItem[]) => {
|
||||
try {
|
||||
const textarea: HTMLTextAreaElement | any = get_uiCurrentTabContent().querySelector(id)
|
||||
if (textarea) textarea.value = currentTags.map((t) => t.text).join(', ')
|
||||
updateInput(textarea)
|
||||
const textarea: HTMLTextAreaElement | any = get_uiCurrentTabContent().querySelector(id);
|
||||
if (textarea) textarea.value = currentTags.map((t) => t.text).join(', ');
|
||||
updateInput(textarea);
|
||||
} catch {}
|
||||
}, [])
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<TagList type={type} tags={tags} setTags={setTags} setValue={setCurrentValue} />
|
||||
<TagList setTags={setTags} setValue={setCurrentValue} tags={tags} type={type} />
|
||||
<BtnGroup>
|
||||
<Btn title="Load Prompt" onClick={getValue}>
|
||||
<Btn onClick={getValue} title="Load Prompt">
|
||||
🔄
|
||||
</Btn>
|
||||
<Btn title="Set Prompt" onClick={setValue}>
|
||||
<Btn onClick={setValue} title="Set Prompt">
|
||||
➡️
|
||||
</Btn>
|
||||
</BtnGroup>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Prompt)
|
||||
export default React.memo(Prompt);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import Prompt from './Prompt'
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import Prompt from './Prompt';
|
||||
|
||||
const View = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
margin-bottom: 1em;
|
||||
`
|
||||
`;
|
||||
|
||||
const Desc = styled.div`
|
||||
position: relative;
|
||||
|
|
@ -24,7 +25,7 @@ const Desc = styled.div`
|
|||
background: var(--block-title-background-fill);
|
||||
border: solid var(--block-title-border-width) var(--block-title-border-color);
|
||||
border-radius: var(--block-title-radius);
|
||||
`
|
||||
`;
|
||||
|
||||
const PromptGroup: React.FC = () => {
|
||||
return (
|
||||
|
|
@ -34,7 +35,7 @@ const PromptGroup: React.FC = () => {
|
|||
<Desc>Negative</Desc>
|
||||
<Prompt type="negative" />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(PromptGroup)
|
||||
export default React.memo(PromptGroup);
|
||||
|
|
|
|||
|
|
@ -1,23 +1,24 @@
|
|||
import React, { useCallback, useMemo } from 'react'
|
||||
import { WithContext, ReactTagsProps as WithContextProps } from 'react-tag-input'
|
||||
import styled from 'styled-components'
|
||||
import { genTagType, suggestions } from './utils'
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { WithContext, ReactTagsProps as WithContextProps } from 'react-tag-input';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { genTagType, suggestions } from './utils';
|
||||
|
||||
export interface TagItem {
|
||||
id: string
|
||||
text: string
|
||||
className?: string
|
||||
className?: string;
|
||||
id: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export type PromptType = 'positive' | 'negative'
|
||||
export type PromptType = 'positive' | 'negative';
|
||||
|
||||
interface ReactTagsProps extends WithContextProps {
|
||||
editable?: boolean
|
||||
onTagUpdate?: (editIndex: number, tag: TagItem) => void
|
||||
editable?: boolean;
|
||||
onTagUpdate?: (editIndex: number, tag: TagItem) => void;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const ReactTags: React.FC<ReactTagsProps> = WithContext
|
||||
const ReactTags: React.FC<ReactTagsProps> = WithContext;
|
||||
|
||||
const View = styled.div<{ type: PromptType }>`
|
||||
/* Styles for the input */
|
||||
|
|
@ -143,81 +144,81 @@ const View = styled.div<{ type: PromptType }>`
|
|||
background: var(--orange-2) !important;
|
||||
border-color: var(--orange-3) !important;
|
||||
}
|
||||
`
|
||||
`;
|
||||
|
||||
const KeyCodes = {
|
||||
comma: 188,
|
||||
enter: 13,
|
||||
}
|
||||
};
|
||||
|
||||
const delimiters = [KeyCodes.comma, KeyCodes.enter]
|
||||
const delimiters = [KeyCodes.comma, KeyCodes.enter];
|
||||
|
||||
interface TagListProps {
|
||||
tags: TagItem[]
|
||||
setTags: (tags: TagItem[]) => void
|
||||
type: PromptType
|
||||
setValue: (tags: TagItem[]) => void
|
||||
setTags: (tags: TagItem[]) => void;
|
||||
setValue: (tags: TagItem[]) => void;
|
||||
tags: TagItem[];
|
||||
type: PromptType;
|
||||
}
|
||||
|
||||
const TagList: React.FC<TagListProps> = ({ tags, setTags, type, setValue }) => {
|
||||
const handleDelete = useCallback(
|
||||
(i: number) => {
|
||||
const newTags = tags.filter((tag, index) => index !== i)
|
||||
setTags(newTags)
|
||||
setValue(newTags)
|
||||
const newTags = tags.filter((tag, index) => index !== i);
|
||||
setTags(newTags);
|
||||
setValue(newTags);
|
||||
},
|
||||
[tags]
|
||||
)
|
||||
[tags],
|
||||
);
|
||||
|
||||
const handleAddition = useCallback(
|
||||
(tag: TagItem) => {
|
||||
const newTags = [...tags, genTagType(tag)]
|
||||
setTags(newTags)
|
||||
setValue(newTags)
|
||||
const newTags = [...tags, genTagType(tag)];
|
||||
setTags(newTags);
|
||||
setValue(newTags);
|
||||
},
|
||||
[tags]
|
||||
)
|
||||
[tags],
|
||||
);
|
||||
|
||||
const handleDrag = useCallback(
|
||||
(tag: TagItem, currPos: number, newPos: number) => {
|
||||
const newTags = tags.slice()
|
||||
newTags.splice(currPos, 1)
|
||||
newTags.splice(newPos, 0, genTagType(tag))
|
||||
setTags(newTags)
|
||||
setValue(newTags)
|
||||
const newTags = tags.slice();
|
||||
newTags.splice(currPos, 1);
|
||||
newTags.splice(newPos, 0, genTagType(tag));
|
||||
setTags(newTags);
|
||||
setValue(newTags);
|
||||
},
|
||||
[tags]
|
||||
)
|
||||
[tags],
|
||||
);
|
||||
|
||||
const handleTagUpdate = useCallback(
|
||||
(i: number, tag: TagItem) => {
|
||||
const newTags = [...tags]
|
||||
newTags[i] = genTagType(tag)
|
||||
setTags(newTags)
|
||||
setValue(newTags)
|
||||
const newTags = [...tags];
|
||||
newTags[i] = genTagType(tag);
|
||||
setTags(newTags);
|
||||
setValue(newTags);
|
||||
},
|
||||
[tags]
|
||||
)
|
||||
[tags],
|
||||
);
|
||||
|
||||
const suggestionData = useMemo(() => suggestions[type], [type])
|
||||
const suggestionData = useMemo(() => suggestions[type], [type]);
|
||||
|
||||
return (
|
||||
<View type={type}>
|
||||
<ReactTags
|
||||
tags={tags}
|
||||
autocomplete
|
||||
delimiters={delimiters}
|
||||
handleDelete={handleDelete}
|
||||
editable
|
||||
handleAddition={handleAddition}
|
||||
handleDelete={handleDelete}
|
||||
handleDrag={handleDrag}
|
||||
inline
|
||||
inputFieldPosition="bottom"
|
||||
onTagUpdate={handleTagUpdate}
|
||||
suggestions={suggestionData}
|
||||
inputFieldPosition="bottom"
|
||||
inline
|
||||
autocomplete
|
||||
editable
|
||||
tags={tags}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(TagList)
|
||||
export default React.memo(TagList);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import { DraggablePanel } from '@/components'
|
||||
import { useAppStore } from '@/store'
|
||||
import { useResponsive } from 'antd-style'
|
||||
import React, { CSSProperties, useEffect, useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { shallow } from 'zustand/shallow'
|
||||
import PromptGroup from './PromptGroup'
|
||||
import { useResponsive } from 'antd-style';
|
||||
import React, { CSSProperties, useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
import { DraggablePanel } from '@/components';
|
||||
import { useAppStore } from '@/store';
|
||||
|
||||
import PromptGroup from './PromptGroup';
|
||||
|
||||
const SidebarView = styled.div`
|
||||
overflow-x: hidden;
|
||||
|
|
@ -13,45 +15,45 @@ const SidebarView = styled.div`
|
|||
height: -webkit-fill-available;
|
||||
height: -moz-available;
|
||||
padding: 16px;
|
||||
`
|
||||
`;
|
||||
|
||||
interface SidebarProps {
|
||||
children: React.ReactNode
|
||||
loading?: boolean
|
||||
style?: CSSProperties
|
||||
children: React.ReactNode;
|
||||
loading?: boolean;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
const Sidebar: React.FC<SidebarProps> = ({ children, loading, style }) => {
|
||||
const [setting] = useAppStore((st) => [st.setting], shallow)
|
||||
const [mode] = useState<'fixed' | 'float'>(setting.sidebarFixedMode)
|
||||
const { mobile } = useResponsive()
|
||||
const [expand, setExpand] = useState<boolean>(setting.sidebarExpand)
|
||||
const [setting] = useAppStore((st) => [st.setting], shallow);
|
||||
const [mode] = useState<'fixed' | 'float'>(setting.sidebarFixedMode);
|
||||
const { mobile } = useResponsive();
|
||||
const [expand, setExpand] = useState<boolean>(setting.sidebarExpand);
|
||||
|
||||
useEffect(() => {
|
||||
if (mobile) setExpand(false)
|
||||
}, [])
|
||||
if (mobile) setExpand(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<DraggablePanel
|
||||
defaultSize={{ width: setting.sidebarWidth }}
|
||||
expand={expand}
|
||||
minWidth={setting.sidebarWidth}
|
||||
mode={mode}
|
||||
onExpandChange={setExpand}
|
||||
pin={mode === 'fixed'}
|
||||
placement="left"
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
...style,
|
||||
}}
|
||||
placement="left"
|
||||
defaultSize={{ width: setting.sidebarWidth }}
|
||||
minWidth={setting.sidebarWidth}
|
||||
expand={expand}
|
||||
onExpandChange={setExpand}
|
||||
>
|
||||
<SidebarView>
|
||||
{!loading && <PromptGroup />}
|
||||
{children}
|
||||
</SidebarView>
|
||||
</DraggablePanel>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Sidebar)
|
||||
export default React.memo(Sidebar);
|
||||
|
|
|
|||
|
|
@ -1,47 +1,48 @@
|
|||
import negativeData from '@/data/negative.json'
|
||||
import positiveData from '@/data/positive.json'
|
||||
import { Converter } from '@/script/formatPrompt'
|
||||
import { TagItem } from './TagList'
|
||||
import negativeData from '@/data/negative.json';
|
||||
import positiveData from '@/data/positive.json';
|
||||
import { Converter } from '@/script/formatPrompt';
|
||||
|
||||
import { TagItem } from './TagList';
|
||||
|
||||
export const genTagType = (tag: TagItem): TagItem => {
|
||||
const newTag = tag
|
||||
const newTag = tag;
|
||||
if (newTag.text.includes('<lora')) {
|
||||
newTag.className = 'ReactTags__lora'
|
||||
newTag.className = 'ReactTags__lora';
|
||||
} else if (newTag.text.includes('<hypernet')) {
|
||||
newTag.className = 'ReactTags__hypernet'
|
||||
newTag.className = 'ReactTags__hypernet';
|
||||
} else if (newTag.text.includes('<embedding')) {
|
||||
newTag.className = 'ReactTags__embedding'
|
||||
newTag.className = 'ReactTags__embedding';
|
||||
} else {
|
||||
newTag.className = undefined
|
||||
newTag.className = undefined;
|
||||
}
|
||||
return newTag
|
||||
}
|
||||
return newTag;
|
||||
};
|
||||
|
||||
export const formatPrompt = (value: string) => {
|
||||
const text = Converter.convertStr(value)
|
||||
const text = Converter.convertStr(value);
|
||||
const textArray = Converter.convertStr2Array(text).map((item) => {
|
||||
if (item.includes('<')) return item
|
||||
if (item.includes('<')) return item;
|
||||
const newItem = item
|
||||
.replace(/\s+/g, ' ')
|
||||
.replace(/,|\.\|。/g, ',')
|
||||
.replace(/“|‘|”|"|\/'/g, '')
|
||||
.replace(/, /g, ',')
|
||||
.replace(/,,/g, ',')
|
||||
.replace(/,/g, ', ')
|
||||
return Converter.convertStr2Array(newItem).join(', ')
|
||||
})
|
||||
return textArray.map((tag) => genTagType({ id: tag, text: tag }))
|
||||
}
|
||||
.replace(/,/g, ', ');
|
||||
return Converter.convertStr2Array(newItem).join(', ');
|
||||
});
|
||||
return textArray.map((tag) => genTagType({ id: tag, text: tag }));
|
||||
};
|
||||
|
||||
const genSuggestions = (array: string[]) =>
|
||||
array.map((text) => {
|
||||
return {
|
||||
id: text,
|
||||
text,
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
export const suggestions = {
|
||||
positive: genSuggestions(positiveData),
|
||||
negative: genSuggestions(negativeData),
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import { Content, ExtraNetworkSidebar, Header, Sidebar } from '@/components'
|
||||
import dragablePanel from '@/script/draggablePanel'
|
||||
import replaceIcon from '@/script/replaceIcon'
|
||||
import { useAppStore } from '@/store'
|
||||
import { Spin } from 'antd'
|
||||
import { useResponsive } from 'antd-style'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { shallow } from 'zustand/shallow'
|
||||
import civitaiHelperFix from '@/script/civitaiHelperFix'
|
||||
import { Spin } from 'antd';
|
||||
import { useResponsive } from 'antd-style';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
import { Content, ExtraNetworkSidebar, Header, Sidebar } from '@/components';
|
||||
import civitaiHelperFix from '@/script/civitaiHelperFix';
|
||||
import dragablePanel from '@/script/draggablePanel';
|
||||
import replaceIcon from '@/script/replaceIcon';
|
||||
import { useAppStore } from '@/store';
|
||||
|
||||
const View = styled.div`
|
||||
position: relative;
|
||||
|
|
@ -16,7 +17,7 @@ const View = styled.div`
|
|||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row !important;
|
||||
`
|
||||
`;
|
||||
|
||||
const MainView = styled.div`
|
||||
position: relative;
|
||||
|
|
@ -28,7 +29,7 @@ const MainView = styled.div`
|
|||
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
`
|
||||
`;
|
||||
|
||||
const LoadingBox = styled.div`
|
||||
display: flex;
|
||||
|
|
@ -37,74 +38,76 @@ const LoadingBox = styled.div`
|
|||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`
|
||||
`;
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [currentTab, setCurrentTab, setting] = useAppStore(
|
||||
(st) => [st.currentTab, st.setCurrentTab, st.setting],
|
||||
shallow
|
||||
)
|
||||
const { mobile } = useResponsive()
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [extraLoading, setExtraLoading] = useState(true)
|
||||
const sidebarRef: any = useRef<HTMLElement>()
|
||||
const mainRef: any = useRef<HTMLElement>()
|
||||
const txt2imgExtraNetworkSidebarRef: any = useRef<HTMLElement>()
|
||||
const img2imgExtraNetworkSidebarRef: any = useRef<HTMLElement>()
|
||||
shallow,
|
||||
);
|
||||
const { mobile } = useResponsive();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [extraLoading, setExtraLoading] = useState(true);
|
||||
const sidebarRef: any = useRef<HTMLElement>();
|
||||
const mainRef: any = useRef<HTMLElement>();
|
||||
const txt2imgExtraNetworkSidebarRef: any = useRef<HTMLElement>();
|
||||
const img2imgExtraNetworkSidebarRef: any = useRef<HTMLElement>();
|
||||
useEffect(() => {
|
||||
onUiLoaded(() => {
|
||||
// Content
|
||||
const main = gradioApp().querySelector('.app')
|
||||
if (main) mainRef.current?.appendChild(main)
|
||||
if (!mobile) dragablePanel()
|
||||
const main = gradioApp().querySelector('.app');
|
||||
if (main) mainRef.current?.appendChild(main);
|
||||
if (!mobile) dragablePanel();
|
||||
|
||||
// Sidebar
|
||||
const sidebar = gradioApp().querySelector('#quicksettings')
|
||||
if (sidebar) sidebarRef.current?.appendChild(sidebar)
|
||||
const sidebar = gradioApp().querySelector('#quicksettings');
|
||||
if (sidebar) sidebarRef.current?.appendChild(sidebar);
|
||||
|
||||
// ExtraNetworkSidebar
|
||||
if (setting.enableExtraNetworkSidebar) {
|
||||
const txt2imgExtraNetworks = gradioApp().querySelector('div#txt2img_extra_networks')
|
||||
const img2imgExtraNetworks = gradioApp().querySelector('div#img2img_extra_networks')
|
||||
const txt2imgExtraNetworks = gradioApp().querySelector('div#txt2img_extra_networks');
|
||||
const img2imgExtraNetworks = gradioApp().querySelector('div#img2img_extra_networks');
|
||||
if (txt2imgExtraNetworks && img2imgExtraNetworks) {
|
||||
txt2imgExtraNetworkSidebarRef.current?.appendChild(txt2imgExtraNetworks)
|
||||
img2imgExtraNetworkSidebarRef.current?.appendChild(img2imgExtraNetworks)
|
||||
txt2imgExtraNetworkSidebarRef.current?.appendChild(txt2imgExtraNetworks);
|
||||
img2imgExtraNetworkSidebarRef.current?.appendChild(img2imgExtraNetworks);
|
||||
}
|
||||
}
|
||||
|
||||
// Other
|
||||
if (setting.svgIcon) replaceIcon()
|
||||
if (setting.svgIcon) replaceIcon();
|
||||
|
||||
setLoading(false)
|
||||
})
|
||||
setLoading(false);
|
||||
});
|
||||
onUiUpdate(() => {
|
||||
setCurrentTab()
|
||||
})
|
||||
}, [])
|
||||
setCurrentTab();
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && setting.enableExtraNetworkSidebar) {
|
||||
if (document.querySelector('#txt2img_lora_cards')) {
|
||||
civitaiHelperFix()
|
||||
setExtraLoading(false)
|
||||
return
|
||||
civitaiHelperFix();
|
||||
setExtraLoading(false);
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
const t2iBtn: any = document.querySelector('#txt2img_extra_refresh')
|
||||
const i2iBtn: any = document.querySelector('#img2img_extra_refresh')
|
||||
t2iBtn.click()
|
||||
i2iBtn.click()
|
||||
setExtraLoading(false)
|
||||
const t2iBtn: any = document.querySelector('#txt2img_extra_refresh');
|
||||
const i2iBtn: any = document.querySelector('#img2img_extra_refresh');
|
||||
t2iBtn.click();
|
||||
i2iBtn.click();
|
||||
setExtraLoading(false);
|
||||
try {
|
||||
const civitaiBtn = document.querySelectorAll('button[title="Refresh Civitai Helper\'s additional buttons"]')
|
||||
const civitaiBtn = document.querySelectorAll(
|
||||
'button[title="Refresh Civitai Helper\'s additional buttons"]',
|
||||
);
|
||||
if (civitaiBtn) {
|
||||
civitaiBtn.forEach((btn: any) => (btn.onclick = civitaiHelperFix))
|
||||
civitaiBtn.forEach((btn: any) => (btn.onclick = civitaiHelperFix));
|
||||
}
|
||||
civitaiHelperFix()
|
||||
civitaiHelperFix();
|
||||
} catch {}
|
||||
}, 2000)
|
||||
}, 2000);
|
||||
}
|
||||
}, [loading])
|
||||
}, [loading]);
|
||||
|
||||
return (
|
||||
<MainView>
|
||||
|
|
@ -122,15 +125,15 @@ const App: React.FC = () => {
|
|||
<Spin size="small" />
|
||||
</LoadingBox>
|
||||
)}
|
||||
<div style={loading ? { display: 'none' } : {}} id="sidebar" ref={sidebarRef} />
|
||||
<div id="sidebar" ref={sidebarRef} style={loading ? { display: 'none' } : {}} />
|
||||
</Sidebar>
|
||||
<Content loading={loading}>
|
||||
{loading && (
|
||||
<LoadingBox>
|
||||
<Spin tip="Loading" size="large" />
|
||||
<Spin size="large" tip="Loading" />
|
||||
</LoadingBox>
|
||||
)}
|
||||
<div style={loading ? { display: 'none' } : {}} id="content" ref={mainRef} />
|
||||
<div id="content" ref={mainRef} style={loading ? { display: 'none' } : {}} />
|
||||
</Content>
|
||||
{setting?.enableExtraNetworkSidebar && (
|
||||
<ExtraNetworkSidebar>
|
||||
|
|
@ -142,20 +145,20 @@ const App: React.FC = () => {
|
|||
<div style={extraLoading ? { display: 'none' } : {}}>
|
||||
<div
|
||||
id="txt2img-extra-netwrok-sidebar"
|
||||
style={currentTab !== 'tab_img2img' ? {} : { display: 'none' }}
|
||||
ref={txt2imgExtraNetworkSidebarRef}
|
||||
style={currentTab !== 'tab_img2img' ? {} : { display: 'none' }}
|
||||
/>
|
||||
<div
|
||||
id="img2img-extra-netwrok-sidebar"
|
||||
style={currentTab === 'tab_img2img' ? {} : { display: 'none' }}
|
||||
ref={img2imgExtraNetworkSidebarRef}
|
||||
style={currentTab === 'tab_img2img' ? {} : { display: 'none' }}
|
||||
/>
|
||||
</div>
|
||||
</ExtraNetworkSidebar>
|
||||
)}
|
||||
</View>
|
||||
</MainView>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(App)
|
||||
export default React.memo(App);
|
||||
|
|
|
|||
|
|
@ -1,72 +1,75 @@
|
|||
import { useIsDarkMode } from '@/components/theme/useIsDarkMode'
|
||||
import formatPrompt from '@/script/formatPrompt'
|
||||
import promptBracketChecker from '@/script/promptBracketChecker'
|
||||
import setupHead from '@/script/setupHead'
|
||||
import { useAppStore } from '@/store'
|
||||
import '@/theme/style.less'
|
||||
import { ThemeProvider, setupStyled } from 'antd-style'
|
||||
import qs from 'query-string'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { shallow } from 'zustand/shallow'
|
||||
import App from './App'
|
||||
import '@/theme/style.less';
|
||||
|
||||
import { setupStyled, ThemeProvider } from 'antd-style';
|
||||
import qs from 'query-string';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { ThemeContext } from 'styled-components';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
import { useIsDarkMode } from '@/components/theme/useIsDarkMode';
|
||||
import formatPrompt from '@/script/formatPrompt';
|
||||
import promptBracketChecker from '@/script/promptBracketChecker';
|
||||
import setupHead from '@/script/setupHead';
|
||||
import { useAppStore } from '@/store';
|
||||
|
||||
import App from './App';
|
||||
|
||||
const Root: React.FC = () => {
|
||||
setupStyled({ ThemeContext })
|
||||
const [onSetThemeMode, onInit] = useAppStore((st) => [st.onSetThemeMode, st.onInit], shallow)
|
||||
const isDarkMode = useIsDarkMode()
|
||||
const [appearance, setAppearance] = useState<'light' | 'dark'>('light')
|
||||
const [first, setFirst] = useState(true)
|
||||
setupStyled({ ThemeContext });
|
||||
const [onSetThemeMode, onInit] = useAppStore((st) => [st.onSetThemeMode, st.onInit], shallow);
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const [appearance, setAppearance] = useState<'light' | 'dark'>('light');
|
||||
const [first, setFirst] = useState(true);
|
||||
useEffect(() => {
|
||||
onInit()
|
||||
}, [])
|
||||
onInit();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
const queryTheme: any = String(qs.parseUrl(window.location.href).query.__theme || '')
|
||||
const queryTheme: any = String(qs.parseUrl(window.location.href).query.__theme || '');
|
||||
if (queryTheme) {
|
||||
setAppearance(queryTheme as any)
|
||||
document.body.classList.add(queryTheme)
|
||||
onSetThemeMode(queryTheme)
|
||||
return
|
||||
setAppearance(queryTheme as any);
|
||||
document.body.classList.add(queryTheme);
|
||||
onSetThemeMode(queryTheme);
|
||||
return;
|
||||
}
|
||||
setAppearance(isDarkMode ? 'dark' : 'light')
|
||||
document.body.classList.add(isDarkMode ? 'dark' : 'light')
|
||||
onSetThemeMode(isDarkMode ? 'dark' : 'light')
|
||||
}, [isDarkMode])
|
||||
setAppearance(isDarkMode ? 'dark' : 'light');
|
||||
document.body.classList.add(isDarkMode ? 'dark' : 'light');
|
||||
onSetThemeMode(isDarkMode ? 'dark' : 'light');
|
||||
}, [isDarkMode]);
|
||||
useEffect(() => {
|
||||
if (first) {
|
||||
setFirst(false)
|
||||
return
|
||||
setFirst(false);
|
||||
return;
|
||||
}
|
||||
window.location.reload()
|
||||
}, [isDarkMode])
|
||||
window.location.reload();
|
||||
}, [isDarkMode]);
|
||||
return (
|
||||
<ThemeProvider appearance={appearance}>
|
||||
<App />
|
||||
</ThemeProvider>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
document.addEventListener(
|
||||
'DOMContentLoaded',
|
||||
() => {
|
||||
setupHead()
|
||||
const root = document.createElement('div')
|
||||
root.setAttribute('id', 'root')
|
||||
setupHead();
|
||||
const root = document.createElement('div');
|
||||
root.setAttribute('id', 'root');
|
||||
try {
|
||||
gradioApp()?.append(root)
|
||||
gradioApp()?.append(root);
|
||||
} catch {
|
||||
document.querySelector('gradio-app')?.append(root)
|
||||
document.querySelector('gradio-app')?.append(root);
|
||||
}
|
||||
const client = createRoot(root)
|
||||
client.render(<Root />)
|
||||
const client = createRoot(root);
|
||||
client.render(<Root />);
|
||||
},
|
||||
{ once: true }
|
||||
)
|
||||
{ once: true },
|
||||
);
|
||||
|
||||
onUiLoaded(() => {
|
||||
formatPrompt()
|
||||
promptBracketChecker()
|
||||
})
|
||||
formatPrompt();
|
||||
promptBracketChecker();
|
||||
});
|
||||
|
||||
export default () => null
|
||||
export default () => null;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export class Converter {
|
|||
* @returns 四舍五入后的数字
|
||||
*/
|
||||
static round(value: number): number {
|
||||
return Math.round(value * 10000) / 10000
|
||||
return Math.round(value * 10000) / 10000;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -17,7 +17,7 @@ export class Converter {
|
|||
* @returns 转换后的字符串
|
||||
*/
|
||||
static convertStr(srt: string): string {
|
||||
return srt.replace(/:/g, ':').replace(/(/g, '(').replace(/)/g, ')')
|
||||
return srt.replace(/:/g, ':').replace(/(/g, '(').replace(/)/g, ')');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -27,7 +27,7 @@ export class Converter {
|
|||
*/
|
||||
static convertStr2Array(str: string): string[] {
|
||||
// 匹配各种括号中的内容,包括括号本身
|
||||
const bracketRegex = /([()<>[\]])/g
|
||||
const bracketRegex = /([()<>[\]])/g;
|
||||
|
||||
/**
|
||||
* 将字符串按照各种括号分割成数组
|
||||
|
|
@ -35,30 +35,30 @@ export class Converter {
|
|||
* @returns 分割后的数组
|
||||
*/
|
||||
const splitByBracket = (str: string): string[] => {
|
||||
const arr: string[] = []
|
||||
let start = 0
|
||||
let depth = 0
|
||||
let match
|
||||
const arr: string[] = [];
|
||||
let start = 0;
|
||||
let depth = 0;
|
||||
let match;
|
||||
while ((match = bracketRegex.exec(str)) !== null) {
|
||||
if (depth === 0 && match.index > start) {
|
||||
arr.push(str.substring(start, match.index))
|
||||
start = match.index
|
||||
arr.push(str.substring(start, match.index));
|
||||
start = match.index;
|
||||
}
|
||||
if (match[0] === '(' || match[0] === '<' || match[0] === '[') {
|
||||
depth++
|
||||
depth++;
|
||||
} else if (match[0] === ')' || match[0] === '>' || match[0] === ']') {
|
||||
depth--
|
||||
depth--;
|
||||
}
|
||||
if (depth === 0) {
|
||||
arr.push(str.substring(start, match.index + 1))
|
||||
start = match.index + 1
|
||||
arr.push(str.substring(start, match.index + 1));
|
||||
start = match.index + 1;
|
||||
}
|
||||
}
|
||||
if (start < str.length) {
|
||||
arr.push(str.substring(start))
|
||||
arr.push(str.substring(start));
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
|
||||
/**
|
||||
* 将字符串按照逗号和各种括号分割成数组
|
||||
|
|
@ -66,20 +66,20 @@ export class Converter {
|
|||
* @returns 分割后的数组
|
||||
*/
|
||||
const splitByComma = (str: string): string[] => {
|
||||
const arr: string[] = []
|
||||
let start = 0
|
||||
let inBracket = false
|
||||
const arr: string[] = [];
|
||||
let start = 0;
|
||||
let inBracket = false;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
if (str[i] === ',' && !inBracket) {
|
||||
arr.push(str.substring(start, i).trim())
|
||||
start = i + 1
|
||||
arr.push(str.substring(start, i).trim());
|
||||
start = i + 1;
|
||||
} else if (str[i].match(bracketRegex)) {
|
||||
inBracket = !inBracket
|
||||
inBracket = !inBracket;
|
||||
}
|
||||
}
|
||||
arr.push(str.substring(start).trim())
|
||||
return arr
|
||||
}
|
||||
arr.push(str.substring(start).trim());
|
||||
return arr;
|
||||
};
|
||||
|
||||
/**
|
||||
* 清洗字符串并输出数组
|
||||
|
|
@ -87,20 +87,24 @@ export class Converter {
|
|||
* @returns 清洗后的数组
|
||||
*/
|
||||
const cleanStr = (str: string): string[] => {
|
||||
let arr = splitByBracket(str)
|
||||
arr = arr.flatMap((s) => splitByComma(s))
|
||||
return arr.filter((s) => s !== '')
|
||||
}
|
||||
let arr = splitByBracket(str);
|
||||
arr = arr.flatMap((s) => splitByComma(s));
|
||||
return arr.filter((s) => s !== '');
|
||||
};
|
||||
|
||||
return cleanStr(str)
|
||||
.filter((item) => {
|
||||
const pattern = /^[,\s, ]+$/
|
||||
return !pattern.test(item)
|
||||
const pattern = /^[,\s, ]+$/;
|
||||
return !pattern.test(item);
|
||||
})
|
||||
.filter(Boolean)
|
||||
.sort((a, b) => {
|
||||
return a.includes('<') && !b.includes('<') ? 1 : b.includes('<') && !a.includes('<') ? -1 : 0
|
||||
})
|
||||
return a.includes('<') && !b.includes('<')
|
||||
? 1
|
||||
: b.includes('<') && !a.includes('<')
|
||||
? -1
|
||||
: 0;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -110,17 +114,17 @@ export class Converter {
|
|||
*/
|
||||
static convertArray2Str(array: string[]): string {
|
||||
const newArray = array.map((item) => {
|
||||
if (item.includes('<')) return item
|
||||
if (item.includes('<')) return item;
|
||||
const newItem = item
|
||||
.replace(/\s+/g, ' ')
|
||||
.replace(/,|\.\|。/g, ',')
|
||||
.replace(/“|‘|”|"|\/'/g, '')
|
||||
.replace(/, /g, ',')
|
||||
.replace(/,,/g, ',')
|
||||
.replace(/,/g, ', ')
|
||||
return Converter.convertStr2Array(newItem).join(', ')
|
||||
})
|
||||
return newArray.join(', ')
|
||||
.replace(/,/g, ', ');
|
||||
return Converter.convertStr2Array(newItem).join(', ');
|
||||
});
|
||||
return newArray.join(', ');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -129,21 +133,21 @@ export class Converter {
|
|||
* @returns 转换后的字符串
|
||||
*/
|
||||
static convert(input: string): string {
|
||||
const re_attention = /\{|\[|\}|\]|[^{}[\]]+/gmu
|
||||
const re_attention = /\{|\[|\}|\]|[^{}[\]]+/gmu;
|
||||
|
||||
let text = Converter.convertStr(input)
|
||||
const textArray = Converter.convertStr2Array(text)
|
||||
text = Converter.convertArray2Str(textArray)
|
||||
let text = Converter.convertStr(input);
|
||||
const textArray = Converter.convertStr2Array(text);
|
||||
text = Converter.convertArray2Str(textArray);
|
||||
|
||||
let res: [string, number][] = []
|
||||
let res: [string, number][] = [];
|
||||
|
||||
const curly_bracket_multiplier = 1.05
|
||||
const square_bracket_multiplier = 1 / 1.05
|
||||
const curly_bracket_multiplier = 1.05;
|
||||
const square_bracket_multiplier = 1 / 1.05;
|
||||
|
||||
const brackets: Record<string, { stack: number[]; multiplier: number }> = {
|
||||
const brackets: Record<string, { multiplier: number; stack: number[] }> = {
|
||||
'{': { stack: [], multiplier: curly_bracket_multiplier },
|
||||
'[': { stack: [], multiplier: square_bracket_multiplier },
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 将指定范围内的数字乘以指定倍数
|
||||
|
|
@ -152,50 +156,50 @@ export class Converter {
|
|||
*/
|
||||
function multiply_range(start_position: number, multiplier: number) {
|
||||
for (let pos = start_position; pos < res.length; pos++) {
|
||||
res[pos][1] = Converter.round(res[pos][1] * multiplier)
|
||||
res[pos][1] = Converter.round(res[pos][1] * multiplier);
|
||||
}
|
||||
}
|
||||
|
||||
for (const match of text.matchAll(re_attention)) {
|
||||
let word = match[0]
|
||||
let word = match[0];
|
||||
|
||||
if (word in brackets) {
|
||||
brackets[word].stack.push(res.length)
|
||||
brackets[word].stack.push(res.length);
|
||||
} else if (word === '}' || word === ']') {
|
||||
const bracket = brackets[word === '}' ? '{' : '[']
|
||||
const bracket = brackets[word === '}' ? '{' : '['];
|
||||
if (bracket.stack.length > 0) {
|
||||
multiply_range(bracket.stack.pop()!, bracket.multiplier)
|
||||
multiply_range(bracket.stack.pop()!, bracket.multiplier);
|
||||
}
|
||||
} else {
|
||||
res.push([word, 1.0])
|
||||
res.push([word, 1.0]);
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(brackets).forEach((bracketType) => {
|
||||
brackets[bracketType].stack.forEach((pos) => {
|
||||
multiply_range(pos, brackets[bracketType].multiplier)
|
||||
})
|
||||
})
|
||||
multiply_range(pos, brackets[bracketType].multiplier);
|
||||
});
|
||||
});
|
||||
|
||||
if (res.length === 0) {
|
||||
res = [['', 1.0]]
|
||||
res = [['', 1.0]];
|
||||
}
|
||||
|
||||
let i = 0
|
||||
let i = 0;
|
||||
while (i + 1 < res.length) {
|
||||
if (res[i][1] === res[i + 1][1]) {
|
||||
res[i][0] += res[i + 1][0]
|
||||
res.splice(i + 1, 1)
|
||||
res[i][0] += res[i + 1][0];
|
||||
res.splice(i + 1, 1);
|
||||
} else {
|
||||
i += 1
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let result = ''
|
||||
let result = '';
|
||||
for (const [word, value] of res) {
|
||||
result += value === 1.0 ? word : `(${word}:${value.toString()})`
|
||||
result += value === 1.0 ? word : `(${word}:${value.toString()})`;
|
||||
}
|
||||
return result
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -203,9 +207,9 @@ export class Converter {
|
|||
* @param target 目标元素
|
||||
*/
|
||||
static dispatchInputEvent(target: EventTarget) {
|
||||
let inputEvent = new Event('input')
|
||||
Object.defineProperty(inputEvent, 'target', { value: target })
|
||||
target.dispatchEvent(inputEvent)
|
||||
let inputEvent = new Event('input');
|
||||
Object.defineProperty(inputEvent, 'target', { value: target });
|
||||
target.dispatchEvent(inputEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -213,22 +217,27 @@ export class Converter {
|
|||
* @param type 类型
|
||||
*/
|
||||
static onClickConvert(type: string) {
|
||||
const default_prompt = ''
|
||||
const default_negative = ''
|
||||
const default_prompt = '';
|
||||
const default_negative = '';
|
||||
|
||||
const prompt = gradioApp().querySelector(`#${type}_prompt > label > textarea`) as HTMLTextAreaElement
|
||||
const result = Converter.convert(prompt.value)
|
||||
prompt.value = result.match(/^masterpiece, best quality,/) === null ? default_prompt + result : result
|
||||
Converter.dispatchInputEvent(prompt)
|
||||
const negprompt = gradioApp().querySelector(`#${type}_neg_prompt > label > textarea`) as HTMLTextAreaElement
|
||||
const negResult = Converter.convert(negprompt.value)
|
||||
const prompt = gradioApp().querySelector(
|
||||
`#${type}_prompt > label > textarea`,
|
||||
) as HTMLTextAreaElement;
|
||||
const result = Converter.convert(prompt.value);
|
||||
prompt.value =
|
||||
result.match(/^masterpiece, best quality,/) === null ? default_prompt + result : result;
|
||||
Converter.dispatchInputEvent(prompt);
|
||||
const negprompt = gradioApp().querySelector(
|
||||
`#${type}_neg_prompt > label > textarea`,
|
||||
) as HTMLTextAreaElement;
|
||||
const negResult = Converter.convert(negprompt.value);
|
||||
negprompt.value =
|
||||
negResult.match(/^lowres,/) === null
|
||||
? negResult.length === 0
|
||||
? default_negative
|
||||
: default_negative + negResult
|
||||
: negResult
|
||||
Converter.dispatchInputEvent(negprompt)
|
||||
: negResult;
|
||||
Converter.dispatchInputEvent(negprompt);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -239,14 +248,14 @@ export class Converter {
|
|||
* @returns 新建的按钮元素
|
||||
*/
|
||||
static createButton(id: string, innerHTML: string, onClick: () => void): HTMLButtonElement {
|
||||
const button = document.createElement('button')
|
||||
button.id = id
|
||||
button.type = 'button'
|
||||
button.innerHTML = innerHTML
|
||||
button.title = 'Format prompt~🪄'
|
||||
button.className = 'lg secondary gradio-button tool svelte-1ipelgc'
|
||||
button.addEventListener('click', onClick)
|
||||
return button
|
||||
const button = document.createElement('button');
|
||||
button.id = id;
|
||||
button.type = 'button';
|
||||
button.innerHTML = innerHTML;
|
||||
button.title = 'Format prompt~🪄';
|
||||
button.className = 'lg secondary gradio-button tool svelte-1ipelgc';
|
||||
button.addEventListener('click', onClick);
|
||||
return button;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -254,18 +263,18 @@ export class Converter {
|
|||
* @param type - 组件类型
|
||||
*/
|
||||
static addPromptButton(type: string): void {
|
||||
const generateBtn: HTMLElement | null = gradioApp().querySelector(`#${type}_generate`)
|
||||
const actionsColumn: HTMLElement | null = gradioApp().querySelector(`#${type}_style_create`)
|
||||
const nai2local: HTMLElement | null = gradioApp().querySelector(`#${type}_formatconvert`)
|
||||
if (!generateBtn || !actionsColumn || nai2local) return
|
||||
const generateBtn: HTMLElement | null = gradioApp().querySelector(`#${type}_generate`);
|
||||
const actionsColumn: HTMLElement | null = gradioApp().querySelector(`#${type}_style_create`);
|
||||
const nai2local: HTMLElement | null = gradioApp().querySelector(`#${type}_formatconvert`);
|
||||
if (!generateBtn || !actionsColumn || nai2local) return;
|
||||
const convertBtn: HTMLElement = Converter.createButton(`${type}_formatconvert`, '🪄', () =>
|
||||
Converter.onClickConvert(type)
|
||||
)
|
||||
actionsColumn.parentNode?.append(convertBtn)
|
||||
Converter.onClickConvert(type),
|
||||
);
|
||||
actionsColumn.parentNode?.append(convertBtn);
|
||||
}
|
||||
}
|
||||
|
||||
export default () => {
|
||||
Converter.addPromptButton('txt2img')
|
||||
Converter.addPromptButton('img2img')
|
||||
}
|
||||
Converter.addPromptButton('txt2img');
|
||||
Converter.addPromptButton('img2img');
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
interface ErrorString {
|
||||
regex: string
|
||||
error: string
|
||||
error: string;
|
||||
regex: string;
|
||||
}
|
||||
|
||||
class BracketChecker {
|
||||
private textArea: HTMLTextAreaElement
|
||||
private counterElt: HTMLElement
|
||||
private errorStrings: ErrorString[]
|
||||
private textArea: HTMLTextAreaElement;
|
||||
private counterElt: HTMLElement;
|
||||
private errorStrings: ErrorString[];
|
||||
|
||||
constructor(textArea: HTMLTextAreaElement, counterElt: HTMLElement) {
|
||||
this.textArea = textArea
|
||||
this.counterElt = counterElt
|
||||
this.textArea = textArea;
|
||||
this.counterElt = counterElt;
|
||||
this.errorStrings = [
|
||||
{
|
||||
regex: '\\(',
|
||||
|
|
@ -24,31 +24,32 @@ class BracketChecker {
|
|||
regex: '\\{',
|
||||
error: '{...} - Different number of opening and closing curly brackets detected.\n',
|
||||
},
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文本框中的括号是否匹配,并更新计数器元素的标题和样式
|
||||
*/
|
||||
public check = (): void => {
|
||||
let title = ''
|
||||
let title = '';
|
||||
this.errorStrings.forEach(({ regex, error }) => {
|
||||
const openMatches = (this.textArea.value.match(new RegExp(regex, 'g')) || []).length
|
||||
const openMatches = (this.textArea.value.match(new RegExp(regex, 'g')) || []).length;
|
||||
const closeMatches = (
|
||||
this.textArea.value.match(new RegExp(regex.replace(/\(/g, ')').replace(/\[/g, ']').replace(/\{/g, '}'), 'g')) ||
|
||||
[]
|
||||
).length
|
||||
this.textArea.value.match(
|
||||
new RegExp(regex.replace(/\(/g, ')').replace(/\[/g, ']').replace(/\{/g, '}'), 'g'),
|
||||
) || []
|
||||
).length;
|
||||
if (openMatches !== closeMatches) {
|
||||
if (!this.counterElt.title.includes(error)) {
|
||||
title += error
|
||||
title += error;
|
||||
}
|
||||
} else {
|
||||
title = this.counterElt.title.replace(error, '')
|
||||
title = this.counterElt.title.replace(error, '');
|
||||
}
|
||||
})
|
||||
this.counterElt.title = title
|
||||
this.counterElt.classList.toggle('error', !!title)
|
||||
}
|
||||
});
|
||||
this.counterElt.title = title;
|
||||
this.counterElt.classList.toggle('error', !!title);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -57,16 +58,18 @@ class BracketChecker {
|
|||
* @param id_counter 显示计数器的元素的 ID
|
||||
*/
|
||||
const setupBracketChecking = (idPrompt: string, idCounter: string): void => {
|
||||
const textarea = gradioApp().querySelector(`#${idPrompt} > label > textarea`) as HTMLTextAreaElement
|
||||
const counter = gradioApp().getElementById(idCounter) as HTMLElement
|
||||
const bracketChecker = new BracketChecker(textarea, counter)
|
||||
textarea.addEventListener('input', bracketChecker.check)
|
||||
}
|
||||
const textarea = gradioApp().querySelector(
|
||||
`#${idPrompt} > label > textarea`,
|
||||
) as HTMLTextAreaElement;
|
||||
const counter = gradioApp().getElementById(idCounter) as HTMLElement;
|
||||
const bracketChecker = new BracketChecker(textarea, counter);
|
||||
textarea.addEventListener('input', bracketChecker.check);
|
||||
};
|
||||
|
||||
export default () => {
|
||||
const elements = ['txt2img', 'txt2img_neg', 'img2img', 'img2img_neg']
|
||||
const elements = ['txt2img', 'txt2img_neg', 'img2img', 'img2img_neg'];
|
||||
elements.forEach((prompt) => {
|
||||
setupBracketChecking(`${prompt}_prompt`, `${prompt}_token_counter`)
|
||||
setupBracketChecking(`${prompt}_prompt`, `${prompt}_negative_token_counter`)
|
||||
})
|
||||
}
|
||||
setupBracketChecking(`${prompt}_prompt`, `${prompt}_token_counter`);
|
||||
setupBracketChecking(`${prompt}_prompt`, `${prompt}_negative_token_counter`);
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
import { create } from 'zustand';
|
||||
import { devtools } from 'zustand/middleware';
|
||||
|
||||
const SETTING_KEY = 'SD-KITCHEN-SETTING'
|
||||
const SETTING_KEY = 'SD-KITCHEN-SETTING';
|
||||
|
||||
export interface WebuiSetting {
|
||||
promotTextarea: 'scroll' | 'resizable'
|
||||
sidebarExpand: boolean
|
||||
sidebarFixedMode: 'fixed' | 'float'
|
||||
sidebarWidth: number
|
||||
enableExtraNetworkSidebar: boolean
|
||||
extraNetworkSidebarExpand: boolean
|
||||
extraNetworkFixedMode: 'fixed' | 'float'
|
||||
extraNetworkSidebarWidth: number
|
||||
extraNetworkCardSize: number
|
||||
svgIcon: boolean
|
||||
enableExtraNetworkSidebar: boolean;
|
||||
extraNetworkCardSize: number;
|
||||
extraNetworkFixedMode: 'fixed' | 'float';
|
||||
extraNetworkSidebarExpand: boolean;
|
||||
extraNetworkSidebarWidth: number;
|
||||
promotTextarea: 'scroll' | 'resizable';
|
||||
sidebarExpand: boolean;
|
||||
sidebarFixedMode: 'fixed' | 'float';
|
||||
sidebarWidth: number;
|
||||
svgIcon: boolean;
|
||||
}
|
||||
|
||||
export const defaultSetting: WebuiSetting = {
|
||||
|
|
@ -27,16 +27,16 @@ export const defaultSetting: WebuiSetting = {
|
|||
extraNetworkSidebarWidth: 340,
|
||||
extraNetworkCardSize: 86,
|
||||
svgIcon: false,
|
||||
}
|
||||
};
|
||||
export interface AppState {
|
||||
themeMode: 'light' | 'dark'
|
||||
setting: WebuiSetting
|
||||
currentTab: string
|
||||
setCurrentTab: () => void
|
||||
onSetThemeMode: (themeMode: 'light' | 'dark') => void
|
||||
onLoadSetting: () => void
|
||||
onSetSetting: (setting: WebuiSetting) => void
|
||||
onInit: () => void
|
||||
currentTab: string;
|
||||
onInit: () => void;
|
||||
onLoadSetting: () => void;
|
||||
onSetSetting: (setting: WebuiSetting) => void;
|
||||
onSetThemeMode: (themeMode: 'light' | 'dark') => void;
|
||||
setCurrentTab: () => void;
|
||||
setting: WebuiSetting;
|
||||
themeMode: 'light' | 'dark';
|
||||
}
|
||||
export const useAppStore = create<AppState>()(
|
||||
devtools((set, get) => ({
|
||||
|
|
@ -44,28 +44,28 @@ export const useAppStore = create<AppState>()(
|
|||
setting: defaultSetting,
|
||||
currentTab: 'tab_txt2img',
|
||||
setCurrentTab: () => {
|
||||
const currentTab = get_uiCurrentTabContent().id
|
||||
if (currentTab !== get().currentTab) set({ currentTab }, false, 'setCurrentTab')
|
||||
const currentTab = get_uiCurrentTabContent().id;
|
||||
if (currentTab !== get().currentTab) set({ currentTab }, false, 'setCurrentTab');
|
||||
},
|
||||
onSetThemeMode: (themeMode) => {
|
||||
set(() => ({ themeMode }), false, 'onSetThemeMode')
|
||||
set(() => ({ themeMode }), false, 'onSetThemeMode');
|
||||
},
|
||||
onLoadSetting: () => {
|
||||
let setting: any = localStorage.getItem(SETTING_KEY)
|
||||
let setting: any = localStorage.getItem(SETTING_KEY);
|
||||
if (setting) {
|
||||
setting = JSON.parse(setting)
|
||||
setting = JSON.parse(setting);
|
||||
} else {
|
||||
setting = defaultSetting
|
||||
localStorage.setItem(SETTING_KEY, JSON.stringify(defaultSetting))
|
||||
setting = defaultSetting;
|
||||
localStorage.setItem(SETTING_KEY, JSON.stringify(defaultSetting));
|
||||
}
|
||||
set(() => ({ setting: { ...defaultSetting, ...setting } }), false, 'onLoadSetting')
|
||||
set(() => ({ setting: { ...defaultSetting, ...setting } }), false, 'onLoadSetting');
|
||||
},
|
||||
onSetSetting: (setting) => {
|
||||
localStorage.setItem(SETTING_KEY, JSON.stringify(setting))
|
||||
set(() => ({ setting }), false, 'onSetSetting')
|
||||
localStorage.setItem(SETTING_KEY, JSON.stringify(setting));
|
||||
set(() => ({ setting }), false, 'onSetSetting');
|
||||
},
|
||||
onInit: () => {
|
||||
get().onLoadSetting()
|
||||
get().onLoadSetting();
|
||||
},
|
||||
}))
|
||||
)
|
||||
})),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
const { theme } = require('antd')
|
||||
const { toCustomPropertiesString } = require('object-to-css-variables')
|
||||
const fs = require('fs')
|
||||
|
||||
const { defaultAlgorithm, darkAlgorithm, defaultSeed } = theme
|
||||
|
||||
const mapToken = defaultAlgorithm(defaultSeed)
|
||||
const mapDarkToken = darkAlgorithm(defaultSeed)
|
||||
|
||||
const cssVariables = toCustomPropertiesString(mapToken)
|
||||
const cssDarkVariables = toCustomPropertiesString(mapDarkToken)
|
||||
|
||||
fs.writeFileSync('src/theme/var/antd-light.less', cssVariables)
|
||||
fs.writeFileSync('src/theme/var/antd-dark.less', cssDarkVariables)
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
// 从本地文件读取CSS
|
||||
const cssFilePath = path.resolve(__dirname, 'style.css')
|
||||
const css = fs.readFileSync(cssFilePath, 'utf-8')
|
||||
|
||||
// 用于存储变量及其值的对象
|
||||
const cssVariables = {}
|
||||
|
||||
// 正则表达式用于匹配CSS变量及其值
|
||||
const cssVarRegex = /--\w+[^:]*:\s*([^;]*);/g
|
||||
const cssVarReferenceRegex = /var\((--\w+)\)/g
|
||||
|
||||
// 解析CSS变量及其值
|
||||
let match
|
||||
while ((match = cssVarRegex.exec(css))) {
|
||||
const variable = match[0].split(':')[0].trim()
|
||||
const value = match[1].trim()
|
||||
cssVariables[variable] = value
|
||||
}
|
||||
|
||||
// 对变量进行排序,确保被引用的变量在引用它的变量之前
|
||||
const sortedVariables = Object.keys(cssVariables)
|
||||
|
||||
// 按类别对变量进行分组
|
||||
const groups = {
|
||||
colors: [],
|
||||
typography: [],
|
||||
spacing: [],
|
||||
border: [],
|
||||
boxShadow: [],
|
||||
body: [],
|
||||
block: [],
|
||||
panel: [],
|
||||
button: [],
|
||||
checkbox: [],
|
||||
input: [],
|
||||
table: [],
|
||||
other: [],
|
||||
}
|
||||
|
||||
sortedVariables.forEach((variable) => {
|
||||
const value = cssVariables[variable]
|
||||
const isColor =
|
||||
value.startsWith('#') ||
|
||||
value.includes('rgb') ||
|
||||
value.includes('color') ||
|
||||
variable.includes('color') ||
|
||||
variable.includes('fill') ||
|
||||
variable.includes('neutral')
|
||||
const isTypography =
|
||||
variable.includes('text') || variable.includes('font') || variable.includes('prose') || variable.includes('link')
|
||||
const isSpacing =
|
||||
variable.includes('spacing') ||
|
||||
variable.includes('padding') ||
|
||||
variable.includes('gap') ||
|
||||
variable.includes('margin')
|
||||
value.includes('padding') || value.includes('margin')
|
||||
const isBorder = variable.includes('radius') || variable.includes('border')
|
||||
const isBoxShadow = variable.includes('shadow')
|
||||
if (variable.includes('--body')) {
|
||||
groups.body.push(variable)
|
||||
} else if (variable.includes('--block')) {
|
||||
groups.block.push(variable)
|
||||
} else if (variable.includes('--panel')) {
|
||||
groups.panel.push(variable)
|
||||
} else if (variable.includes('--button')) {
|
||||
groups.button.push(variable)
|
||||
} else if (variable.includes('--checkbox')) {
|
||||
groups.checkbox.push(variable)
|
||||
} else if (variable.includes('--input')) {
|
||||
groups.input.push(variable)
|
||||
} else if (variable.includes('--table')) {
|
||||
groups.table.push(variable)
|
||||
} else if (isBorder) {
|
||||
groups.border.push(variable)
|
||||
} else if (isBoxShadow) {
|
||||
groups.boxShadow.push(variable)
|
||||
} else if (isColor) {
|
||||
groups.colors.push(variable)
|
||||
} else if (isTypography) {
|
||||
groups.typography.push(variable)
|
||||
} else if (isSpacing) {
|
||||
groups.spacing.push(variable)
|
||||
} else {
|
||||
groups.other.push(variable)
|
||||
}
|
||||
})
|
||||
|
||||
// 生成排序后的CSS变量
|
||||
const generateGroupCss = (groupName) => {
|
||||
const cache = groups[groupName].map((variable) => `${variable}: ${cssVariables[variable]};`)
|
||||
const firstPart = cache.filter((item) => item.includes('var'))
|
||||
const secondPart = cache.filter((item) => !item.includes('var'))
|
||||
return [...secondPart, ...firstPart].join('\n')
|
||||
}
|
||||
|
||||
const sortedCss =
|
||||
'#output {\n' +
|
||||
`/* Colors */\n` +
|
||||
generateGroupCss('colors') +
|
||||
`\n\n/* Typography */\n` +
|
||||
generateGroupCss('typography') +
|
||||
`\n\n/* Spacing */\n` +
|
||||
generateGroupCss('spacing') +
|
||||
`\n\n/* Border */\n` +
|
||||
generateGroupCss('border') +
|
||||
`\n\n/* BoxShadow */\n` +
|
||||
generateGroupCss('boxShadow') +
|
||||
`\n\n/* Body */\n` +
|
||||
generateGroupCss('body') +
|
||||
`\n\n/* Block */\n` +
|
||||
generateGroupCss('block') +
|
||||
`\n\n/* Panel */\n` +
|
||||
generateGroupCss('panel') +
|
||||
`\n\n/* Button */\n` +
|
||||
generateGroupCss('button') +
|
||||
`\n\n/* Checkbox */\n` +
|
||||
generateGroupCss('checkbox') +
|
||||
`\n\n/* Input */\n` +
|
||||
generateGroupCss('input') +
|
||||
`\n\n/* Table */\n` +
|
||||
generateGroupCss('table') +
|
||||
`\n\n/* Other */\n` +
|
||||
generateGroupCss('other') +
|
||||
'}'
|
||||
|
||||
console.log(sortedCss)
|
||||
|
||||
// 将排序后的CSS变量写入文件
|
||||
fs.writeFileSync('sorted-css-variables.css', sortedCss)
|
||||
Loading…
Reference in New Issue